Commit 153b3462 authored by stvhu's avatar stvhu
Browse files

Improvements to auth and access point support

* Ensure that certificate renewals always use the original credentials source
* Include file system ID in all client TLS certificates
* Updates to man page and README
parent d692ffe1
...@@ -102,6 +102,7 @@ $ sudo mount -t efs -o tls,iam file-system-id efs-mount-point/ ...@@ -102,6 +102,7 @@ $ sudo mount -t efs -o tls,iam file-system-id efs-mount-point/
``` ```
To mount using an access point, use the `accesspoint=` option. This option requires the `tls` option. To mount using an access point, use the `accesspoint=` option. This option requires the `tls` option.
The access point must be in the "available" state before it can be used to mount EFS.
``` ```
$ sudo mount -t efs -o tls,accesspoint=access-point-id file-system-id efs-mount-point/ $ sudo mount -t efs -o tls,accesspoint=access-point-id file-system-id efs-mount-point/
......
...@@ -11,7 +11,7 @@ set -ex ...@@ -11,7 +11,7 @@ set -ex
BASE_DIR=$(pwd) BASE_DIR=$(pwd)
BUILD_ROOT=${BASE_DIR}/build/debbuild BUILD_ROOT=${BASE_DIR}/build/debbuild
VERSION=1.20 VERSION=1.21
echo 'Cleaning deb build workspace' echo 'Cleaning deb build workspace'
rm -rf ${BUILD_ROOT} rm -rf ${BUILD_ROOT}
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
# #
[global] [global]
version=1.20 version=1.21
Package: amazon-efs-utils Package: amazon-efs-utils
Architecture: all Architecture: all
Version: 1.20 Version: 1.21
Section: utils Section: utils
Depends: python|python2, nfs-common, stunnel4 (>= 4.56) Depends: python|python2, nfs-common, stunnel4 (>= 4.56)
Priority: optional Priority: optional
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
%endif %endif
Name : amazon-efs-utils Name : amazon-efs-utils
Version : 1.20 Version : 1.21
Release : 1%{?dist} Release : 1%{?dist}
Summary : This package provides utilities for simplifying the use of EFS file systems Summary : This package provides utilities for simplifying the use of EFS file systems
......
...@@ -91,7 +91,8 @@ This option requires the \fBtls\fR option\&. ...@@ -91,7 +91,8 @@ This option requires the \fBtls\fR option\&.
.TP .TP
\fBaccesspoint\fR \fBaccesspoint\fR
Mount the EFS file system using the specified access point. This option requires the \ Mount the EFS file system using the specified access point. This option requires the \
\fBtls\fR option\&. \fBtls\fR option\&. The access point must be in the "available" state before it \
can be used to mount EFS\&.
.TP .TP
\fBawsprofile\fR \fBawsprofile\fR
Use the named profile used to lookup IAM credentials in the AWS CLI credentials file \ Use the named profile used to lookup IAM credentials in the AWS CLI credentials file \
......
...@@ -68,7 +68,7 @@ except ImportError: ...@@ -68,7 +68,7 @@ except ImportError:
from urllib.error import URLError from urllib.error import URLError
from urllib.request import urlopen from urllib.request import urlopen
VERSION = '1.20' VERSION = '1.21'
SERVICE = 'elasticfilesystem' SERVICE = 'elasticfilesystem'
CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf' CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf'
...@@ -84,6 +84,9 @@ DATE_ONLY_FORMAT = '%Y%m%d' ...@@ -84,6 +84,9 @@ DATE_ONLY_FORMAT = '%Y%m%d'
SIGV4_DATETIME_FORMAT = '%Y%m%dT%H%M%SZ' SIGV4_DATETIME_FORMAT = '%Y%m%dT%H%M%SZ'
CERT_DATETIME_FORMAT = '%y%m%d%H%M%SZ' CERT_DATETIME_FORMAT = '%y%m%d%H%M%SZ'
AWS_CREDENTIALS_FILE = os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'credentials'))
AWS_CONFIG_FILE = os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'config'))
CA_CONFIG_BODY = """dir = %s CA_CONFIG_BODY = """dir = %s
RANDFILE = $dir/database/.rand RANDFILE = $dir/database/.rand
...@@ -133,6 +136,7 @@ FS_ID_RE = re.compile('^(?P<fs_id>fs-[0-9a-f]+)$') ...@@ -133,6 +136,7 @@ FS_ID_RE = re.compile('^(?P<fs_id>fs-[0-9a-f]+)$')
EFS_FQDN_RE = re.compile(r'^(?P<fs_id>fs-[0-9a-f]+)\.efs\.(?P<region>[a-z0-9-]+)\.(?P<dns_name_suffix>[a-z0-9.]+)$') EFS_FQDN_RE = re.compile(r'^(?P<fs_id>fs-[0-9a-f]+)\.efs\.(?P<region>[a-z0-9-]+)\.(?P<dns_name_suffix>[a-z0-9.]+)$')
AP_ID_RE = re.compile('^fsap-[0-9a-f]{17}$') AP_ID_RE = re.compile('^fsap-[0-9a-f]{17}$')
ECS_TASK_METADATA_API = '169.254.170.2'
INSTANCE_METADATA_SERVICE_URL = 'http://169.254.169.254/latest/dynamic/instance-identity/document/' INSTANCE_METADATA_SERVICE_URL = 'http://169.254.169.254/latest/dynamic/instance-identity/document/'
INSTANCE_IAM_URL = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/' INSTANCE_IAM_URL = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/'
SECURITY_CREDS_ECS_URI_HELP_URL = 'https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html' SECURITY_CREDS_ECS_URI_HELP_URL = 'https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html'
...@@ -233,39 +237,40 @@ def get_region_helper(config): ...@@ -233,39 +237,40 @@ def get_region_helper(config):
return dns_name_format.split('.')[-3] return dns_name_format.split('.')[-3]
def get_aws_security_credentials(awsprofile=None): def get_aws_security_credentials(use_iam, awsprofile):
""" """
Lookup AWS security credentials (access key ID and secret access key). Adapted credentials provider chain from: Lookup AWS security credentials (access key ID and secret access key). Adapted credentials provider chain from:
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html and https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html and
https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html
""" """
aws_credentials_file = os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'credentials')) if not use_iam:
aws_config_file = os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'config')) return None, None
# attempt to lookup AWS access key ID and secret access key in AWS credentials file (~/.aws/credentials)
if os.path.exists(aws_credentials_file): # attempt to lookup AWS security credentials in AWS credentials file (~/.aws/credentials) and configs file (~/.aws/config)
credentials = credentials_file_helper(aws_credentials_file, awsprofile=awsprofile) if awsprofile:
if credentials['AccessKeyId']: for file_path in [AWS_CREDENTIALS_FILE, AWS_CONFIG_FILE]:
return credentials if os.path.exists(file_path):
credentials = credentials_file_helper(file_path, awsprofile)
# in AWS configs file (~/.aws/config) if credentials['AccessKeyId']:
if os.path.exists(aws_config_file): return credentials, os.path.basename(file_path) + ':' + awsprofile
credentials = credentials_file_helper(aws_config_file, awsprofile=awsprofile)
if credentials['AccessKeyId']: log_message = 'AWS security credentials not found in %s or %s under named profile [%s]' % \
return credentials (AWS_CREDENTIALS_FILE, AWS_CONFIG_FILE, awsprofile)
fatal_error(log_message)
keys = ['AccessKeyId', 'SecretAccessKey', 'Token']
dict_keys = ['AccessKeyId', 'SecretAccessKey', 'Token']
# through ECS security credentials uri found in AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable # through ECS security credentials uri found in AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable
ecs_uri_env = 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' ecs_uri_env = 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'
if ecs_uri_env in os.environ: if ecs_uri_env in os.environ:
ecs_uri = '169.254.170.2' + os.environ[ecs_uri_env] ecs_uri = ECS_TASK_METADATA_API + os.environ[ecs_uri_env]
ecs_unsuccessful_resp = 'Unsuccessful retrieval of AWS security credentials at %s.' % ecs_uri ecs_unsuccessful_resp = 'Unsuccessful retrieval of AWS security credentials at %s.' % ecs_uri
ecs_url_error_msg = 'Unable to reach %s to retrieve AWS security credentials. See %s for more info.', ecs_uri, \ ecs_url_error_msg = 'Unable to reach %s to retrieve AWS security credentials. See %s for more info.', ecs_uri, \
SECURITY_CREDS_ECS_URI_HELP_URL SECURITY_CREDS_ECS_URI_HELP_URL
ecs_security_dict = url_request_helper(ecs_uri, ecs_unsuccessful_resp, ecs_url_error_msg) ecs_security_dict = url_request_helper(ecs_uri, ecs_unsuccessful_resp, ecs_url_error_msg)
if ecs_security_dict and all(k in ecs_security_dict for k in keys): if ecs_security_dict and all(k in ecs_security_dict for k in dict_keys):
return ecs_security_dict return ecs_security_dict, 'ecs:' + os.environ[ecs_uri_env]
# through IAM role name security credentials lookup uri (after lookup for IAM role name attached to instance) # through IAM role name security credentials lookup uri (after lookup for IAM role name attached to instance)
iam_role_unsuccessful_resp = 'Unsuccessful retrieval of IAM role name at %s.' % INSTANCE_IAM_URL iam_role_unsuccessful_resp = 'Unsuccessful retrieval of IAM role name at %s.' % INSTANCE_IAM_URL
...@@ -279,48 +284,37 @@ def get_aws_security_credentials(awsprofile=None): ...@@ -279,48 +284,37 @@ def get_aws_security_credentials(awsprofile=None):
security_creds_lookup_url, SECURITY_CREDS_IAM_ROLE_HELP_URL security_creds_lookup_url, SECURITY_CREDS_IAM_ROLE_HELP_URL
iam_security_dict = url_request_helper(security_creds_lookup_url, unsuccessful_resp, url_error_msg) iam_security_dict = url_request_helper(security_creds_lookup_url, unsuccessful_resp, url_error_msg)
if iam_security_dict and all(k in iam_security_dict for k in keys): if iam_security_dict and all(k in iam_security_dict for k in dict_keys):
return iam_security_dict return iam_security_dict, 'metadata:'
error_msg = 'AWS Access Key ID and Secret Access Key are not found in AWS credentials file (%s), config file (%s), ' \ error_msg = 'AWS Access Key ID and Secret Access Key are not found in AWS credentials file (%s), config file (%s), ' \
'from ECS credentials relative uri, or from the instance security credentials service' % \ 'from ECS credentials relative uri, or from the instance security credentials service' % \
(aws_credentials_file, aws_config_file) (AWS_CREDENTIALS_FILE, AWS_CONFIG_FILE)
fatal_error(error_msg, error_msg) fatal_error(error_msg, error_msg)
def credentials_file_helper(file_path, awsprofile=None): def credentials_file_helper(file_path, awsprofile):
aws_credentials_configs = read_config(file_path) aws_credentials_configs = read_config(file_path)
credentials = {'AccessKeyId': None, 'SecretAccessKey': None, 'Token': None} credentials = {'AccessKeyId': None, 'SecretAccessKey': None, 'Token': None}
profile = awsprofile or get_correct_default_case_combination(aws_credentials_configs)
try: try:
access_key = aws_credentials_configs.get(profile, 'aws_access_key_id') access_key = aws_credentials_configs.get(awsprofile, 'aws_access_key_id')
secret_key = aws_credentials_configs.get(profile, 'aws_secret_access_key') secret_key = aws_credentials_configs.get(awsprofile, 'aws_secret_access_key')
session_token = aws_credentials_configs.get(profile, 'aws_session_token') session_token = aws_credentials_configs.get(awsprofile, 'aws_session_token')
credentials['AccessKeyId'] = access_key credentials['AccessKeyId'] = access_key
credentials['SecretAccessKey'] = secret_key credentials['SecretAccessKey'] = secret_key
credentials['Token'] = session_token credentials['Token'] = session_token
except NoOptionError as e: except NoOptionError as e:
if 'aws_access_key_id' in str(e) or 'aws_secret_access_key' in str(e): if 'aws_access_key_id' in str(e) or 'aws_secret_access_key' in str(e):
log_message = 'aws_access_key_id or aws_secret_access_key not found in %s under named profile [%s]' % \ logging.debug('aws_access_key_id or aws_secret_access_key not found in %s under named profile [%s]', file_path,
(file_path, profile) awsprofile)
if awsprofile:
fatal_error(log_message)
else:
logging.debug(log_message)
return credentials
if 'aws_session_token' in str(e): if 'aws_session_token' in str(e):
logging.debug('aws_session_token not found in %s', file_path) logging.debug('aws_session_token not found in %s', file_path)
credentials['AccessKeyId'] = aws_credentials_configs.get(profile, 'aws_access_key_id') credentials['AccessKeyId'] = aws_credentials_configs.get(awsprofile, 'aws_access_key_id')
credentials['SecretAccessKey'] = aws_credentials_configs.get(profile, 'aws_secret_access_key') credentials['SecretAccessKey'] = aws_credentials_configs.get(awsprofile, 'aws_secret_access_key')
except NoSectionError: except NoSectionError:
log_message = 'No [%s] section found in config file %s' % (profile, file_path) logging.debug('No [%s] section found in config file %s', awsprofile, file_path)
if awsprofile:
fatal_error(log_message)
else:
logging.debug(log_message)
return credentials return credentials
...@@ -335,7 +329,19 @@ def get_correct_default_case_combination(aws_credentials_configs): ...@@ -335,7 +329,19 @@ def get_correct_default_case_combination(aws_credentials_configs):
except (NoSectionError, NoOptionError): except (NoSectionError, NoOptionError):
continue continue
return default_str return None
def get_aws_profile(options, use_iam):
awsprofile = options.get('awsprofile')
if not awsprofile and use_iam:
for file_path in [AWS_CREDENTIALS_FILE, AWS_CONFIG_FILE]:
aws_credentials_configs = read_config(file_path)
awsprofile = get_correct_default_case_combination(aws_credentials_configs)
if awsprofile:
break
return awsprofile
def url_request_helper(url, unsuccessful_resp, url_error_msg): def url_request_helper(url, unsuccessful_resp, url_error_msg):
...@@ -675,17 +681,20 @@ def create_required_directory(config, directory): ...@@ -675,17 +681,20 @@ def create_required_directory(config, directory):
@contextmanager @contextmanager
def bootstrap_tls(config, init_system, dns_name, fs_id, ap_id, mountpoint, options, state_file_dir=STATE_FILE_DIR): def bootstrap_tls(config, init_system, dns_name, fs_id, mountpoint, options, state_file_dir=STATE_FILE_DIR):
tls_port = choose_tls_port(config, options) tls_port = choose_tls_port(config, options)
# override the tlsport option so that we can later override the port the NFS client uses to connect to stunnel. # override the tlsport option so that we can later override the port the NFS client uses to connect to stunnel.
# if the user has specified tlsport=X at the command line this will just re-set tlsport to X. # if the user has specified tlsport=X at the command line this will just re-set tlsport to X.
options['tlsport'] = tls_port options['tlsport'] = tls_port
use_iam = 'iam' in options use_iam = 'iam' in options
awsprofile = options.get('awsprofile') ap_id = options.get('accesspoint')
cert_details = {} cert_details = {}
if use_iam or ap_id: if use_iam or ap_id:
awsprofile = get_aws_profile(options, use_iam)
security_credentials, credentials_source = get_aws_security_credentials(use_iam, awsprofile)
# additional symbol appended to avoid naming collisions # additional symbol appended to avoid naming collisions
cert_details['mountStateDir'] = get_mount_specific_filename(fs_id, mountpoint, tls_port) + '+' cert_details['mountStateDir'] = get_mount_specific_filename(fs_id, mountpoint, tls_port) + '+'
# common name for certificate signing request is max 64 characters # common name for certificate signing request is max 64 characters
...@@ -693,18 +702,16 @@ def bootstrap_tls(config, init_system, dns_name, fs_id, ap_id, mountpoint, optio ...@@ -693,18 +702,16 @@ def bootstrap_tls(config, init_system, dns_name, fs_id, ap_id, mountpoint, optio
cert_details['region'] = get_region_helper(config) cert_details['region'] = get_region_helper(config)
cert_details['certificateCreationTime'] = create_certificate(config, cert_details['mountStateDir'], cert_details['certificateCreationTime'] = create_certificate(config, cert_details['mountStateDir'],
cert_details['commonName'], cert_details['region'], fs_id, cert_details['commonName'], cert_details['region'], fs_id,
use_iam, ap_id=ap_id, awsprofile=awsprofile, security_credentials, ap_id=ap_id, base_path=state_file_dir)
base_path=state_file_dir)
cert_details['certificate'] = os.path.join(state_file_dir, cert_details['mountStateDir'], 'certificate.pem') cert_details['certificate'] = os.path.join(state_file_dir, cert_details['mountStateDir'], 'certificate.pem')
cert_details['privateKey'] = get_private_key_path() cert_details['privateKey'] = get_private_key_path()
cert_details['fsId'] = fs_id cert_details['fsId'] = fs_id
cert_details['useIam'] = use_iam
if awsprofile: if credentials_source:
cert_details['awsprofile'] = awsprofile cert_details['awsCredentialsMethod'] = credentials_source
if ap_id: if ap_id:
cert_details['accessPoint'] = ap_id cert_details['accessPoint'] = ap_id
start_watchdog(init_system) start_watchdog(init_system)
...@@ -827,8 +834,7 @@ def parse_arguments(config, args=None): ...@@ -827,8 +834,7 @@ def parse_arguments(config, args=None):
return fs_id, path, mountpoint, options return fs_id, path, mountpoint, options
def create_certificate(config, mount_name, common_name, region, fs_id, use_iam, ap_id=None, awsprofile=None, def create_certificate(config, mount_name, common_name, region, fs_id, security_credentials, ap_id, base_path=STATE_FILE_DIR):
base_path=STATE_FILE_DIR):
current_time = get_utc_now() current_time = get_utc_now()
tls_paths = tls_paths_dictionary(mount_name, base_path) tls_paths = tls_paths_dictionary(mount_name, base_path)
...@@ -841,12 +847,12 @@ def create_certificate(config, mount_name, common_name, region, fs_id, use_iam, ...@@ -841,12 +847,12 @@ def create_certificate(config, mount_name, common_name, region, fs_id, use_iam,
private_key = check_and_create_private_key(base_path) private_key = check_and_create_private_key(base_path)
if use_iam: if security_credentials:
public_key = os.path.join(tls_paths['mount_dir'], 'publicKey.pem') public_key = os.path.join(tls_paths['mount_dir'], 'publicKey.pem')
create_public_key(private_key, public_key) create_public_key(private_key, public_key)
create_ca_conf(certificate_config, common_name, tls_paths['mount_dir'], private_key, current_time, region, fs_id, use_iam, create_ca_conf(certificate_config, common_name, tls_paths['mount_dir'], private_key, current_time, region, fs_id,
ap_id=ap_id, awsprofile=awsprofile) security_credentials, ap_id)
create_certificate_signing_request(certificate_config, private_key, certificate_signing_request) create_certificate_signing_request(certificate_config, private_key, certificate_signing_request)
not_before = get_certificate_timestamp(current_time, minutes=-NOT_BEFORE_MINS) not_before = get_certificate_timestamp(current_time, minutes=-NOT_BEFORE_MINS)
...@@ -911,13 +917,13 @@ def create_certificate_signing_request(config_path, private_key, csr_path): ...@@ -911,13 +917,13 @@ def create_certificate_signing_request(config_path, private_key, csr_path):
subprocess_call(cmd, 'Failed to create certificate signing request (csr)') subprocess_call(cmd, 'Failed to create certificate signing request (csr)')
def create_ca_conf(config_path, common_name, directory, private_key, date, region, fs_id, use_iam, ap_id=None, awsprofile=None): def create_ca_conf(config_path, common_name, directory, private_key, date, region, fs_id, security_credentials, ap_id):
"""Populate ca/req configuration file with fresh configurations at every mount since SigV4 signature can change""" """Populate ca/req configuration file with fresh configurations at every mount since SigV4 signature can change"""
public_key_path = os.path.join(directory, 'publicKey.pem') public_key_path = os.path.join(directory, 'publicKey.pem')
credentials = get_aws_security_credentials(awsprofile=awsprofile) if use_iam else '' ca_extension_body = ca_extension_builder(ap_id, security_credentials, fs_id) if security_credentials or ap_id else ''
ca_extension_body = ca_extension_builder(ap_id, use_iam, fs_id) if use_iam or ap_id else '' efs_client_auth_body = efs_client_auth_builder(public_key_path, security_credentials['AccessKeyId'],
efs_client_auth_body = efs_client_auth_builder(public_key_path, credentials['AccessKeyId'], credentials['SecretAccessKey'], security_credentials['SecretAccessKey'], date, region, fs_id,
date, region, fs_id, credentials['Token']) if use_iam else '' security_credentials['Token']) if security_credentials else ''
full_config_body = CA_CONFIG_BODY % (directory, private_key, common_name, ca_extension_body, efs_client_auth_body) full_config_body = CA_CONFIG_BODY % (directory, private_key, common_name, ca_extension_body, efs_client_auth_body)
with open(config_path, 'w') as f: with open(config_path, 'w') as f:
...@@ -926,13 +932,14 @@ def create_ca_conf(config_path, common_name, directory, private_key, date, regio ...@@ -926,13 +932,14 @@ def create_ca_conf(config_path, common_name, directory, private_key, date, regio
return full_config_body return full_config_body
def ca_extension_builder(ap_id, use_iam, fs_id): def ca_extension_builder(ap_id, security_credentials, fs_id):
ca_extension_str = '[ v3_ca ]\nsubjectKeyIdentifier = hash' ca_extension_str = '[ v3_ca ]\nsubjectKeyIdentifier = hash'
if ap_id: if ap_id:
ca_extension_str += '\n1.3.6.1.4.1.4843.7.1 = ASN1:UTF8String:' + ap_id ca_extension_str += '\n1.3.6.1.4.1.4843.7.1 = ASN1:UTF8String:' + ap_id
if use_iam: if security_credentials:
ca_extension_str += '\n1.3.6.1.4.1.4843.7.2 = ASN1:SEQUENCE:efs_client_auth' ca_extension_str += '\n1.3.6.1.4.1.4843.7.2 = ASN1:SEQUENCE:efs_client_auth'
ca_extension_str += '\n1.3.6.1.4.1.4843.7.3 = ASN1:UTF8String:' + fs_id
ca_extension_str += '\n1.3.6.1.4.1.4843.7.3 = ASN1:UTF8String:' + fs_id
return ca_extension_str return ca_extension_str
...@@ -1291,8 +1298,8 @@ def match_device(config, device): ...@@ -1291,8 +1298,8 @@ def match_device(config, device):
% (remote, 'https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html')) % (remote, 'https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html'))
def mount_tls(config, init_system, dns_name, path, fs_id, ap_id, mountpoint, options): def mount_tls(config, init_system, dns_name, path, fs_id, mountpoint, options):
with bootstrap_tls(config, init_system, dns_name, fs_id, ap_id, mountpoint, options) as tunnel_proc: with bootstrap_tls(config, init_system, dns_name, fs_id, mountpoint, options) as tunnel_proc:
mount_completed = threading.Event() mount_completed = threading.Event()
t = threading.Thread(target=poll_tunnel_process, args=(tunnel_proc, fs_id, mount_completed)) t = threading.Thread(target=poll_tunnel_process, args=(tunnel_proc, fs_id, mount_completed))
t.daemon = True t.daemon = True
...@@ -1357,10 +1364,9 @@ def main(): ...@@ -1357,10 +1364,9 @@ def main():
check_network_status(fs_id, init_system) check_network_status(fs_id, init_system)
dns_name = get_dns_name(config, fs_id) dns_name = get_dns_name(config, fs_id)
ap_id = options.get('accesspoint')
if 'tls' in options: if 'tls' in options:
mount_tls(config, init_system, dns_name, path, fs_id, ap_id, mountpoint, options) mount_tls(config, init_system, dns_name, path, fs_id, mountpoint, options)
else: else:
mount_nfs(dns_name, path, mountpoint, options) mount_nfs(dns_name, path, mountpoint, options)
......
...@@ -11,7 +11,6 @@ import base64 ...@@ -11,7 +11,6 @@ import base64
import errno import errno
import hashlib import hashlib
import hmac import hmac
import itertools
import json import json
import logging import logging
import logging.handlers import logging.handlers
...@@ -46,7 +45,7 @@ except ImportError: ...@@ -46,7 +45,7 @@ except ImportError:
from urllib.error import URLError from urllib.error import URLError
from urllib.request import urlopen from urllib.request import urlopen
VERSION = '1.20' VERSION = '1.21'
SERVICE = 'elasticfilesystem' SERVICE = 'elasticfilesystem'
CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf' CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf'
...@@ -65,6 +64,11 @@ DATE_ONLY_FORMAT = '%Y%m%d' ...@@ -65,6 +64,11 @@ DATE_ONLY_FORMAT = '%Y%m%d'
SIGV4_DATETIME_FORMAT = '%Y%m%dT%H%M%SZ' SIGV4_DATETIME_FORMAT = '%Y%m%dT%H%M%SZ'
CERT_DATETIME_FORMAT = '%y%m%d%H%M%SZ' CERT_DATETIME_FORMAT = '%y%m%d%H%M%SZ'
AWS_CREDENTIALS_FILES = {
'credentials': os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'credentials')),
'config': os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'config')),
}
CA_CONFIG_BODY = """dir = %s CA_CONFIG_BODY = """dir = %s
RANDFILE = $dir/database/.rand RANDFILE = $dir/database/.rand
...@@ -112,6 +116,7 @@ REQUEST_PAYLOAD = '' ...@@ -112,6 +116,7 @@ REQUEST_PAYLOAD = ''
AP_ID_RE = re.compile('^fsap-[0-9a-f]{17}$') AP_ID_RE = re.compile('^fsap-[0-9a-f]{17}$')
ECS_TASK_METADATA_API = '169.254.170.2'
INSTANCE_IAM_URL = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/' INSTANCE_IAM_URL = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/'
SECURITY_CREDS_ECS_URI_HELP_URL = 'https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html' SECURITY_CREDS_ECS_URI_HELP_URL = 'https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html'
SECURITY_CREDS_IAM_ROLE_HELP_URL = 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html' SECURITY_CREDS_IAM_ROLE_HELP_URL = 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html'
...@@ -128,45 +133,57 @@ def fatal_error(user_message, log_message=None): ...@@ -128,45 +133,57 @@ def fatal_error(user_message, log_message=None):
sys.exit(1) sys.exit(1)
def get_aws_security_credentials(awsprofile=None): def get_aws_security_credentials(credentials_source):
""" """
Lookup AWS security credentials (access key ID and secret access key). Adapted credentials provider chain from: Lookup AWS security credentials (access key ID and secret access key). Adapted credentials provider chain from:
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html and https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html and
https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html
""" """
aws_credentials_file = os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'credentials')) method, value = credentials_source.split(':', 1)
aws_config_file = os.path.expanduser(os.path.join('~' + pwd.getpwuid(os.getuid()).pw_name, '.aws', 'config'))
# attempt to lookup AWS access key ID and secret access key in AWS credentials file (~/.aws/credentials) if method == 'credentials':
if os.path.exists(aws_credentials_file): return get_aws_security_credentials_from_file('credentials', value)
credentials = credentials_file_helper(aws_credentials_file, awsprofile=awsprofile) elif method == 'config':
if credentials['AccessKeyId']: return get_aws_security_credentials_from_file('config', value)
return credentials elif method == 'ecs':
return get_aws_security_credentials_from_ecs(value)
elif method == 'metadata':
return get_aws_security_credentials_from_instance_metadata()
else:
logging.error('Improper credentials source string "%s" found from mount state file', credentials_source)
return None
# in AWS configs file (~/.aws/config) def get_aws_security_credentials_from_file(file_name, awsprofile):
if os.path.exists(aws_config_file): # attempt to lookup AWS security credentials in AWS credentials file (~/.aws/credentials) and configs file (~/.aws/config)
credentials = credentials_file_helper(aws_config_file, awsprofile=awsprofile) file_path = AWS_CREDENTIALS_FILES.get(file_name)
if file_path and os.path.exists(file_path):
credentials = credentials_file_helper(file_path, awsprofile)
if credentials['AccessKeyId']: if credentials['AccessKeyId']:
return credentials return credentials
# return None if option 'awsprofile' is present - this indicates no credentials were found in the config files logging.error('AWS security credentials not found in %s under named profile [%s]', file_path, awsprofile)
if awsprofile: return None
return None
keys = ['AccessKeyId', 'SecretAccessKey', 'Token']
def get_aws_security_credentials_from_ecs(uri):
# through ECS security credentials uri found in AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable # through ECS security credentials uri found in AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable
ecs_uri_env = 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' dict_keys = ['AccessKeyId', 'SecretAccessKey', 'Token']
if ecs_uri_env in os.environ: ecs_uri = ECS_TASK_METADATA_API + uri