def find_gateway(aws_syncr, configuration): amazon = configuration['amazon'] stage = aws_syncr.stage gateway = aws_syncr.artifact if 'apigateway' not in configuration: raise AwsSyncrError( "Please define apigateway in your configuration before trying to deploy a gateway" ) if not gateway: raise AwsSyncrError( "Please specify --artifact for the gateway function to deploy") wanted = ['apigateway', gateway] if wanted not in configuration: raise AwsSyncrError("Couldn't find specified api gateway", available=list( configuration["apigateway"].items.keys())) gateway = configuration['apigateway'].items[gateway] if not stage: raise AwsSyncrError("Please specify --stage", available=list(gateway.stage_names)) return aws_syncr, amazon, stage, gateway
def test(self, aws_syncr, amazon, stage): endpoint = aws_syncr.extra.strip() if not endpoint or " " not in endpoint: options = sorted( "{0} {1}".format(m, e) for m, e in list(self.available_methods_and_endpoints())) raise AwsSyncrError("{0}\n".format( dedent(""" Please specify ' -- <http_method> <endpoint> ' at the end of the command For example: {0} -- <http_method> <endpoint> Where the available options are: {1} """.format(' '.join(sys.argv), '\n\t\t'.join(options)).strip()))) method, endpoint = endpoint.split(" ", 1) sample_event, desired_output_for_test = self.find_sample_event( amazon, method, endpoint) self.validate_stage(amazon, stage) return amazon.apigateway.test_stage(self.gateway_info(amazon), self.location, stage, method, endpoint, sample_event, desired_output_for_test)
def modify_bucket(self, bucket_info, name, permission_document, location, tags): current_location = self.client.get_bucket_location(Bucket=name)['LocationConstraint'] if current_location != location: raise AwsSyncrError("Sorry, can't change the location of a bucket!", wanted=location, currently=current_location, bucket=name) # Make sure we use the correct endpoint to get info from the bucket # So that website buckets don't complain bucket_info.meta.client = self.amazon.session.client("s3", location) bucket_document = "" with self.ignore_missing(): bucket_document = bucket_info.Policy().policy if bucket_document or permission_document: if permission_document and not bucket_document: with self.catch_boto_400("Couldn't add policy", "Bucket {0} policy".format(name), permission_document, bucket=name): for _ in self.change("+", "bucket_policy", bucket=name, changes=list(Differ.compare_two_documents("{}", permission_document))): bucket_info.Policy().put(Policy=permission_document) elif bucket_document and not permission_document: with self.catch_boto_400("Couldn't remove policy", "Bucket {0} policy".format(name), permission_document, bucket=name): for _ in self.change("-", "bucket_policy", bucket=name, changes=list(Differ.compare_two_documents(bucket_document, "{}"))): bucket_info.Policy().delete() else: changes = list(Differ.compare_two_documents(bucket_document, permission_document)) if changes: with self.catch_boto_400("Couldn't modify policy", "Bucket {0} policy".format(name), permission_document, bucket=name): for _ in self.change("M", "bucket_policy", bucket=name, changes=changes): bucket_info.Policy().put(Policy=permission_document) self.modify_tags(bucket_info, name, tags)
def execute_as(collector): """Execute a command (after the --) as an assumed role (specified by --artifact)""" # Gonna assume role anyway... collector.configuration['amazon']._validated = True # Find the arn we want to assume account_id = collector.configuration['accounts'][ collector.configuration['aws_syncr'].environment] arn = "arn:aws:iam::{0}:role/{1}".format( account_id, collector.configuration['aws_syncr'].artifact) # Determine the command to run parts = shlex.split(collector.configuration["aws_syncr"].extra) if not parts: suggestion = " ".join(sys.argv) + " -- /path/to/command_to_run" msg = "No command was provided. Try something like:\n\t\t{0}".format( suggestion) raise AwsSyncrError(msg) # Get our aws credentials environment variables from the assumed role env = dict(os.environ) env.update( collector.configuration['amazon'].iam.assume_role_credentials(arn)) # Turn into the command we want to execute os.execvpe(parts[0], parts, env)
def test_gateway(collector): """Specify <method> <endpoint> after -- from the commandline and that gateway endpoint will be requested""" collector.configuration['amazon']._validated = True configuration = collector.configuration aws_syncr = configuration['aws_syncr'] aws_syncr, amazon, stage, gateway = find_gateway(aws_syncr, configuration) if not gateway.test(aws_syncr, amazon, stage): raise AwsSyncrError("Failed to test the gateway")
def test_lambda(collector): """Invoke a lambda function with the defined sample_event and compare against desired_output_for_test""" amazon = collector.configuration['amazon'] amazon._validated = True aws_syncr = collector.configuration['aws_syncr'] if not find_lambda_function(aws_syncr, collector.configuration).test( aws_syncr, amazon): raise AwsSyncrError("Failed to test the lambda")
def find_lambda_function(aws_syncr, configuration): lambda_function = aws_syncr.artifact if 'lambda' not in configuration: raise AwsSyncrError( "Please define lambda functions under the 'lambda' section of your configuration" ) if not lambda_function: raise AwsSyncrError( "Please specify --artifact for the lambda function to deploy") wanted = ['lambda', lambda_function] if wanted not in configuration: raise AwsSyncrError("Couldn't find specified lambda function", available=list( configuration["lambda"].items.keys())) return configuration['lambda'].items[lambda_function]
def find_certificate_source(configuration, gateway, certificate): source = configuration.source_for(['apigateway', gateway, 'domain_names']) location = ["apigateway", gateway, 'domain_names'] domain_names = configuration.get(location, ignore_converters=True) for name, domain in domain_names.items(): if 'zone' in domain: zone = MergedOptionStringFormatter( configuration, '.'.join(location + ['zone']), value=domain.get('zone')).format() domain_name = "{0}.{1}".format(name, zone) if domain_name == certificate: if 'certificate' not in domain: domain['certificate'] = {} var = domain['certificate'] class StickyChain(object): def __init__(self): self.lst = [] def __add__(self, other): self.lst.extend(other) return self.lst def __contains__(self, item): return item in self.lst def __getitem__(self, index): return self.lst[index] chain = StickyChain() if isinstance(var, six.string_types): result = MergedOptionStringFormatter(configuration, '.'.join(location), value=var, chain=chain).format() if not isinstance(result, dict) and not isinstance( result, MergedOptions) and (not hasattr( result, 'is_dict') or not result.is_dict): raise AwsSyncrError( "certificate should be pointing at a dictionary", got=result, chain=['.'.join(location)] + chain) location = chain[-1] source = configuration.source_for(location) for info in configuration.storage.get_info( location, ignore_converters=True): location = [str(part) for part in info.path.path] return location, source
def test_all_gateway_endpoints(collector): """Do a test on all the available gateway endpoints""" collector.configuration['amazon']._validated = True configuration = collector.configuration aws_syncr = configuration['aws_syncr'] aws_syncr, amazon, stage, gateway = find_gateway(aws_syncr, configuration) failure = False for method, resource in gateway.available_methods_and_endpoints(): combination = "{0} {1}".format(method, resource) print(combination) print("=" * len(combination)) aws_syncr.extra = combination if not gateway.test(aws_syncr, amazon, stage): failure = True print("") if failure: raise AwsSyncrError("Atleast one of the endpoints failed the test")
def validate_account(self): """Make sure we are able to connect to the right account""" self._validating = True with self.catch_invalid_credentials(): log.info("Finding a role to check the account id") a_role = list(self.iam.resource.roles.limit(1)) if not a_role: raise AwsSyncrError( "Couldn't find an iam role, can't validate the account...." ) account_id = a_role[0].meta.data['Arn'].split(":", 5)[4] chosen_account = self.accounts[self.environment] if chosen_account != account_id: raise BadCredentials( "Don't have credentials for the correct account!", wanted=chosen_account, got=account_id) self._validating = False self._validated = True
def cname_for(self, gateway_location, record): with self.ignore_missing(): return self.client(gateway_location).get_domain_name(domainName=record)['distributionDomainName'] raise AwsSyncrError("Please do a sync first!")
def encrypt_certificate(collector): configuration = collector.configuration amazon = configuration['amazon'] aws_syncr = configuration['aws_syncr'] certificate = aws_syncr.artifact available = [] for gateway_name, gateway in configuration.get( 'apigateway', {}, ignore_converters=True).items(): for name, options in gateway.get("domain_names", {}).items(): if "zone" in options: location = '.'.join( ['apigateway', gateway_name, 'domain_names']) formatter = MergedOptionStringFormatter(configuration, location, value=options['zone']) available.append( (gateway_name, "{0}.{1}".format(name, formatter.format()))) if not available: raise AwsSyncrError( "Please specify apigateway.<gateway_name>.domain_names.<domain_name>.name in the configuration" ) if not certificate: raise AwsSyncrError( "Please specify certificate to encrypt with --artifact", available=[a[1] for a in available]) if certificate not in [a[1] for a in available]: raise AwsSyncrError("Unknown certificate", available=[a[1] for a in available], got=certificate) gateway = [name for name, cert in available if cert == certificate][0] location, source = find_certificate_source(configuration, gateway, certificate) log.info("Gonna edit {0} in {1}".format(location, source)) current = MergedOptions.using(yaml.load(open(source))) dest = current[location] try: key_id = input("Which kms key do you want to use? ") region = input("What region is this key in? ") except EOFError: raise UserQuit() # Make the filename completion work setup_completer() # Create the datakey to encrypt with data_key = amazon.kms.generate_data_key(region, key_id) plaintext_data_key = data_key["Plaintext"] encrypted_data_key = base64.b64encode( data_key["CiphertextBlob"]).decode('utf-8') # Encrypt our secrets secrets = {} for name, desc in (("body", "certificate's crt file"), ("key", "private key file"), ("chain", "certificate chain")): location = None while not location or not os.path.isfile(location): location = os.path.expanduser( filename_prompt("Where is the {0}? ".format(desc))) if not location or not os.path.isfile(location): print("Please give a location to a file that exists!") data = open(location).read() counter = Counter.new(128) encryptor = AES.new(plaintext_data_key[:32], AES.MODE_CTR, counter=counter) secrets[name] = base64.b64encode( encryptor.encrypt(data)).decode('utf-8') # Add in the encrypted values dest['body'] = { "kms": secrets['body'], "location": region, "kms_data_key": encrypted_data_key } dest['key'] = { "kms": secrets['key'], "location": region, "kms_data_key": encrypted_data_key } dest['chain'] = { "kms": secrets['chain'], "location": region, "kms_data_key": encrypted_data_key } # And write to the file! yaml.dump(current.as_dict(), open(source, 'w'), explicit_start=True, indent=2, default_flow_style=False)