From b9a7352e0a8e2cd8a72e29a7cc9a800bc83155c8 Mon Sep 17 00:00:00 2001 From: Will Thames Date: Wed, 5 Feb 2014 21:11:06 +1000 Subject: [PATCH] Work to allow security tokens and profiles to work with Ansible Allow security tokens and profiles to be used as arguments to the 'common' ec2 modules Mostly refactoring to provide two new methods, `get_aws_connection_info`, which results in a dict that can be passed through to the boto `connect_to_region` calls, and `connect_to_aws` that can pass that dict through to the `connect_to_region` method of the appropriate module. Tidied up some variable names Works around boto/boto#2100 profiles don't work with boto < 2.24, but this detects for that and fails with an appropriate message. It is designed to work if profile is not passed but boto < 2.24 is installed. Modifications to allow empty aws auth variables to be passed (this is useful if wanting to have the keys as an optional parameter in ec2 calls - if set, use this value, if not set, use boto config or env variables) Reworked validate_certs improvements to work with refactoring Added documentation for profile and security_token to affected modules --- lib/ansible/module_utils/ec2.py | 113 +++++++++++++++++++++++--------- library/cloud/ec2 | 14 ++++ library/cloud/ec2_ami | 14 ++++ library/cloud/ec2_eip | 14 ++++ library/cloud/ec2_group | 14 ++++ library/cloud/ec2_key | 14 ++++ library/cloud/ec2_snapshot | 19 +++++- library/cloud/ec2_tag | 14 ++++ library/cloud/ec2_vol | 14 ++++ 9 files changed, 198 insertions(+), 32 deletions(-) diff --git a/lib/ansible/module_utils/ec2.py b/lib/ansible/module_utils/ec2.py index 9156df766b2..ab6c1d27e9d 100644 --- a/lib/ansible/module_utils/ec2.py +++ b/lib/ansible/module_utils/ec2.py @@ -14,33 +14,44 @@ AWS_REGIONS = ['ap-northeast-1', 'us-west-2'] -def ec2_argument_keys_spec(): +def aws_common_argument_spec(): return dict( + ec2_url=dict(), aws_secret_key=dict(aliases=['ec2_secret_key', 'secret_key'], no_log=True), aws_access_key=dict(aliases=['ec2_access_key', 'access_key']), + validate_certs=dict(default=True, type='bool'), + security_token=dict(no_log=True), + profile=dict(), ) + return spec def ec2_argument_spec(): - spec = ec2_argument_keys_spec() + spec = aws_common_argument_spec() spec.update( dict( region=dict(aliases=['aws_region', 'ec2_region'], choices=AWS_REGIONS), - validate_certs=dict(default=True, type='bool'), - ec2_url=dict(), ) ) return spec -def get_ec2_creds(module): +def boto_supports_profile_name(): + return hasattr(boto.ec2.EC2Connection, 'profile_name') + + +def get_aws_connection_info(module): # Check module args for credentials, then check environment vars + # access_key ec2_url = module.params.get('ec2_url') - ec2_secret_key = module.params.get('aws_secret_key') - ec2_access_key = module.params.get('aws_access_key') + access_key = module.params.get('aws_access_key') + secret_key = module.params.get('aws_secret_key') + security_token = module.params.get('security_token') region = module.params.get('region') + profile_name = module.params.get('profile') + validate_certs = module.params.get('validate_certs') if not ec2_url: if 'EC2_URL' in os.environ: @@ -48,21 +59,27 @@ def get_ec2_creds(module): elif 'AWS_URL' in os.environ: ec2_url = os.environ['AWS_URL'] - if not ec2_access_key: + if not access_key: if 'EC2_ACCESS_KEY' in os.environ: - ec2_access_key = os.environ['EC2_ACCESS_KEY'] + access_key = os.environ['EC2_ACCESS_KEY'] elif 'AWS_ACCESS_KEY_ID' in os.environ: - ec2_access_key = os.environ['AWS_ACCESS_KEY_ID'] + access_key = os.environ['AWS_ACCESS_KEY_ID'] elif 'AWS_ACCESS_KEY' in os.environ: - ec2_access_key = os.environ['AWS_ACCESS_KEY'] + access_key = os.environ['AWS_ACCESS_KEY'] + else: + # in case access_key came in as empty string + access_key = None - if not ec2_secret_key: + if not secret_key: if 'EC2_SECRET_KEY' in os.environ: - ec2_secret_key = os.environ['EC2_SECRET_KEY'] + secret_key = os.environ['EC2_SECRET_KEY'] elif 'AWS_SECRET_ACCESS_KEY' in os.environ: - ec2_secret_key = os.environ['AWS_SECRET_ACCESS_KEY'] + secret_key = os.environ['AWS_SECRET_ACCESS_KEY'] elif 'AWS_SECRET_KEY' in os.environ: - ec2_secret_key = os.environ['AWS_SECRET_KEY'] + secret_key = os.environ['AWS_SECRET_KEY'] + else: + # in case secret_key came in as empty string + secret_key = None if not region: if 'EC2_REGION' in os.environ: @@ -71,39 +88,75 @@ def get_ec2_creds(module): region = os.environ['AWS_REGION'] else: # boto.config.get returns None if config not found - region = boto.config.get('Boto', 'aws_region') + region = boto.config.get('Boto', 'aws_region') if not region: region = boto.config.get('Boto', 'ec2_region') - return ec2_url, ec2_access_key, ec2_secret_key, region + if not security_token: + if 'AWS_SECURITY_TOKEN' in os.environ: + security_token = os.environ['AWS_SECURITY_TOKEN'] + else: + # in case security_token came in as empty string + security_token = None + + boto_params = dict(aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + security_token=security_token) + + # profile_name only works as a key in boto >= 2.24 + # so only set profile_name if passed as an argument + if profile_name: + if not boto_supports_profile_name(): + module.fail_json("boto does not support profile_name before 2.24") + boto_params['profile_name'] = profile_name + + if validate_certs and HAS_LOOSE_VERSION and LooseVersion(boto.Version) >= LooseVersion("2.6.0"): + boto_params['validate_certs'] = validate_certs + + return region, ec2_url, boto_params + + +def get_ec2_creds(module): + ''' for compatibility mode with old modules that don't/can't yet + use ec2_connect method ''' + region, ec2_url, boto_params = get_aws_connection_info(module) + return ec2_url, boto_params['aws_access_key_id'], boto_params['aws_secret_access_key'], region + + +def boto_fix_security_token_in_profile(conn, profile_name): + ''' monkey patch for boto issue boto/boto#2100 ''' + profile = 'profile ' + profile_name + if boto.config.has_option(profile, 'aws_security_token'): + conn.provider.set_security_token(boto.config.get(profile, 'aws_security_token')) + return conn + + +def connect_to_aws(aws_module, region, **params): + conn = aws_module.connect_to_region(region, **params) + if params.get('profile_name'): + conn = boto_fix_security_token_in_profile(conn, params['profile_name']) + return conn def ec2_connect(module): """ Return an ec2 connection""" - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - validate_certs = module.params.get('validate_certs', True) + region, ec2_url, boto_params = get_aws_connection_info(module) # If we have a region specified, connect to its endpoint. if region: try: - if HAS_LOOSE_VERSION and LooseVersion(boto.Version) >= LooseVersion("2.6.0"): - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key, validate_certs=validate_certs) - else: - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) + ec2 = connect_to_aws(boto.ec2, region, **boto_params) except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) + module.fail_json(msg=str(e)) # Otherwise, no region so we fallback to the old connection method elif ec2_url: try: - if HAS_LOOSE_VERSION and LooseVersion(boto.Version) >= LooseVersion("2.6.0"): - ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key, validate_certs=validate_certs) - else: - ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key) + ec2 = boto.connect_ec2_endpoint(ec2_url, **boto_params) except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) + module.fail_json(msg=str(e)) else: module.fail_json(msg="Either region or ec2_url must be specified") - return ec2 + return ec2 diff --git a/library/cloud/ec2 b/library/cloud/ec2 index e050611fcf8..54fc9eea467 100644 --- a/library/cloud/ec2 +++ b/library/cloud/ec2 @@ -220,6 +220,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] author: Seth Vidal, Tim Gerla, Lester Wade diff --git a/library/cloud/ec2_ami b/library/cloud/ec2_ami index 866f2caf767..94c1e864a85 100644 --- a/library/cloud/ec2_ami +++ b/library/cloud/ec2_ami @@ -109,6 +109,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] author: Evan Duffield diff --git a/library/cloud/ec2_eip b/library/cloud/ec2_eip index de041f42227..4d6d24eaa34 100644 --- a/library/cloud/ec2_eip +++ b/library/cloud/ec2_eip @@ -61,6 +61,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] author: Lorin Hochstein diff --git a/library/cloud/ec2_group b/library/cloud/ec2_group index bbbb0fc24e0..5d72c009acc 100644 --- a/library/cloud/ec2_group +++ b/library/cloud/ec2_group @@ -65,6 +65,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] ''' diff --git a/library/cloud/ec2_key b/library/cloud/ec2_key index 5e6950d2c8b..6523c70e95c 100644 --- a/library/cloud/ec2_key +++ b/library/cloud/ec2_key @@ -56,6 +56,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] author: Vincent Viallet diff --git a/library/cloud/ec2_snapshot b/library/cloud/ec2_snapshot index b5d9df3b525..81cf3554b3d 100644 --- a/library/cloud/ec2_snapshot +++ b/library/cloud/ec2_snapshot @@ -59,17 +59,32 @@ options: default: null aliases: [] instance_id: - description: + description: - instance that has a the required volume to snapshot mounted required: false default: null aliases: [] device_name: - description: + description: - device name of a mounted volume to be snapshotted required: false default: null aliases: [] + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" + requirements: [ "boto" ] author: Will Thames ''' diff --git a/library/cloud/ec2_tag b/library/cloud/ec2_tag index ca5a337646f..7e3e4776d0b 100644 --- a/library/cloud/ec2_tag +++ b/library/cloud/ec2_tag @@ -67,6 +67,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] author: Lester Wade diff --git a/library/cloud/ec2_vol b/library/cloud/ec2_vol index bdd2eae3822..fef476a2165 100644 --- a/library/cloud/ec2_vol +++ b/library/cloud/ec2_vol @@ -90,6 +90,20 @@ options: choices: ["yes", "no"] aliases: [] version_added: "1.5" + profile: + description: + - uses a boto profile. Only works with boto >= 2.24.0 + required: false + default: null + aliases: [] + version_added: "1.5" + security_token: + description: + - security token to authenticate against AWS + required: false + default: null + aliases: [] + version_added: "1.5" requirements: [ "boto" ] author: Lester Wade