def detach_managed_policy_from_role(role_name, policy_name): ''' Detach a managed policy from a role Arguments: - role_name: Role name to attach policy to (name, not ARN) - policy_name: Managed policy name to attach to policy (not ARN) Returns: True if successful Note: Sometime if the attach fails (like role does not exist) a botocore exception is raised. A future improvement will be to catch this and return an error code ''' managed_policy_arn = make_managed_policy_arn(policy_name) # Connect to IAM iam_client = boto3.client('iam') # Detach the policy from the role logging.info(f'Detaching policy: {policy_name} from role: {role_name}') resp = iam_client.detach_role_policy(RoleName=role_name, PolicyArn=managed_policy_arn) aws_common_utils.check_response_status(resp, check_failure=True) # If it falls through, then return success return True, {}
def attach_inline_policy_to_role(role_name, policy_name, policy_document): ''' Attach an inline policy to the role Arguments: - role_name: name of role, a string (not ARN) - policy_name: name of policy, a string (not ARN) - policy_document: A JSON of the ploic document. See below (or in tests) for example Return: - True if attach succeds Note: Sometime if the attach fails (like role does not exist) a botocore exception is raised. A future improvement will be to catch this and return an error code ''' # Connect to IAM iam_client = boto3.client('iam') logging.info( f'Attaching inline policy: {policy_name} to role: {role_name}') resp = iam_client.put_role_policy(RoleName=role_name, PolicyName=policy_name, PolicyDocument=policy_document) aws_common_utils.check_response_status(resp, check_failure=True) # If it falls through, then return success return True, {}
def list_policies(policy_scope='Local', only_attached=True, path_prefix='/', policy_usage_filter='PermissionsPolicy'): ''' Return a list of policies Arguments: - policy_scope: Choices are: All: All policies AWS: All AWS managed policies Local: All customer managed policies (default) - only_attached: If True, return only attached policies. If False return all policies (True is default) - path_prefix: The path prefix for filtering the results. Default is '/' - policy_usage_filter: Choices are: - PermissionsPolicy: Filter only permissions policy (default) - PermissionsBoundary: Filter only policies set for permissions boundary Returns: - policy_names: A list of policy names - policy_arns: A list of dicts with ploicy_name: policy_arn - full_response: Full response of the API call #### Note: Pagination is not being used for results. As such, this call can handle only upto 100 policies ''' # Connect to IAM iam_client = boto3.client('iam') # Call List policies API logging.info('Getting list of policies from AWS') resp = iam_client.list_policies(Scope=policy_scope, OnlyAttached=only_attached, PathPrefix=path_prefix, PolicyUsageFilter=policy_usage_filter) aws_common_utils.check_response_status(resp, check_failure=True) if resp['IsTruncated']: logging.error('List policies returned truncated response. ' 'Returning all policies that were returned ' 'Time to switch to pagination . . . ') # Get full list of policies policies_list_full = resp['Policies'] # Then get other needed items policies_names_list = [x['PolicyName'] for x in policies_list_full] # Do a dict comprehension to return policy_name: policy_arn policies_name_arn_dict = { x['PolicyName']: x['Arn'] for x in policies_list_full } return policies_names_list, policies_name_arn_dict, resp
def delete_role(role_name): ''' Delete Role Arguments: - role_name: Role name to be deleted Returns: - resp: Full response (minus metadata) ''' # Connect to IAM iam_client = boto3.client('iam') try: resp = iam_client.delete_role(RoleName=role_name) except iam_client.exceptions.NoSuchEntityException: logging.error(f'IAM: Delete Role: {role_name} does not exist') # Construct response err_code = aws_error_codes.AWS_IAM_ROLE_DOES_NOT_EXIST resp = aws_error_codes.construct_response(err_code) return False, resp # Check response sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to IAM delete roles failed with code: ' f'{scode}') raise aws_exceptions.AWS_API_CallFailed err_code = aws_error_codes.AWS_NO_ERROR resp = aws_error_codes.construct_response(err_code) return True, resp
def list_s3_buckets(aws_region=aws_settings.AWS_DEFAULT_REGION): ''' List all buckets in requested region Arguments: - aws_region: List buckets in this region Note: Currently only the AWS_DEFAULT_REGION is implemented Returns: - A list of bucket names ''' # If a region other than default region is specified, raise an error if aws_region != aws_settings.AWS_DEFAULT_REGION: raise aws_exceptions.AWS_RegionNotImplemented s3_client = boto3.client('s3') response = s3_client.list_buckets() # Check if call succeded status_bool, status_code = aws_common_utils.check_response_status(response) if not status_bool: logging.info(f'AWS API call to S3 list buckets failed with code: ' f'{status_code}') raise aws_exceptions.AWS_API_CallFailed full_list = response['Buckets'] names_list = [x['Name'] for x in full_list] return names_list, full_list
def delete_table(table_name, aws_region=aws_settings.AWS_DEFAULT_REGION): ''' Delete a DynamoDB table Arguments: - aws_region: AWS region from which to list tables Currently unused - table_name: Table name to delete Returns: - deleted_table_name: Name of deleted table (taken from response) - full_response: The full JSON response of the AWS call (minus the response metadata) This appears to be the table description ''' dyndb_client = boto3.client('dynamodb') resp = dyndb_client.delete_table(TableName=table_name) sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to delete DynamoDB table failed ' f'with code: {scode}') raise aws_exceptions.AWS_API_CallFailed deleted_table_name = resp['TableDescription']['TableName'] return deleted_table_name, resp['TableDescription']
def list_functions(aws_region=aws_settings.AWS_DEFAULT_REGION): ''' List all functions in requested region Arguments: - aws_region: List buckets in this region. 'ALL' can also be used Returns: - A tuple with: - index 0 being a list of function named - index 1 being a list of the entire response ''' func_name_list = [] func_full_list = [] lambda_client = boto3.client('lambda') # pagination is used in case there are > 1000 functions paginator = lambda_client.get_paginator('list_functions') # For some reason, specifying MasterRegion is paginate call returns no # functions. Debug later # pages = paginator.paginate(MasterRegion=aws_region, FunctionVersion='ALL') pages = paginator.paginate() for page in pages: # Check if call succeded sbool, scode = aws_common_utils.check_response_status(page) if not sbool: logging.info(f'AWS API call to list lambda functions failed with ' f'code: {scode}') raise aws_exceptions.AWS_API_CallFailed for func in page['Functions']: func_name_list.append(func['FunctionName']) func_full_list.append(func) return func_name_list, func_full_list
def attach_managed_policy_to_role(role_name, policy_name): ''' Attach a managed policy to a role Arguments: - role_name: Role name to attach policy to (name, not ARN) - policy_name: Managed policy name to attach to policy (not ARN) Returns: True if successful ''' managed_policy_arn = make_managed_policy_arn(policy_name) # Connect to IAM iam_client = boto3.client('iam') # Attach the policy to the role logging.info(f'Attaching policy: {policy_name} to role: {role_name}') resp = iam_client.attach_role_policy(RoleName=role_name, PolicyArn=managed_policy_arn) aws_common_utils.check_response_status(resp, check_failure=True) # If it falls through, then return success return True, {}
def create_role(role_name, trust_policy, path='/', description='', max_session_duration=3600, tags={}): ''' Create an IAM Role Arguments: - role_name: Name of the role - trust_policy: Trust relationship policy document (JSON string) - path: Path of the role. Defaults to / - description: Obvious - max_session_duration: In seconds. Default is 60 mins - tags: Tags in list of dict format Returns: - role_arn: Role ARN - full_response: The full response (minus the response meta data) ''' # Connect to IAM iam_client = boto3.client('iam') # Create role try: logging.info(f'IAM: Creating Role: {role_name}') resp = iam_client.create_role( RoleName = role_name, AssumeRolePolicyDocument = trust_policy, Path = path, Description = description, MaxSessionDuration = max_session_duration if \ max_session_duration else None, Tags = tags ) except iam_client.exceptions.EntityAlreadyExistsException: logging.error(f'IAM: Create Role: {role_name} already exists') # Construct response err_code = aws_error_codes.AWS_IAM_ROLE_ALREADY_EXISTS resp = aws_error_codes.construct_response(err_code) return False, resp # Check response sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to IAM create role failed with code: ' f'{scode}') raise aws_exceptions.AWS_API_CallFailed return resp['Role']['Arn'], resp
def delete_policy(policy_name): ''' Delete the policy specified by policy_name Note 1: Policy must be detached and all versions deleted before the policy can be deleted Note 2: Need to provide the policy arn here. You can get policy arn from get_policy_arn call ''' # Get policy arn from policy name policy_arn = get_policy_arn(policy_name) # If policy name does not exist, return error if not policy_arn: logging.error(f'IAM: Delete Policy: {policy_name} does not exist') # Construct response err_code = aws_error_codes.AWS_IAM_POLICY_DOES_NOT_EXIST resp = aws_error_codes.construct_response(err_code) return False, resp # Connect to IAM iam_client = boto3.client('iam') try: logging.info(f'Deleting policy ARN: {policy_arn}') resp = iam_client.delete_policy(PolicyArn=policy_arn) aws_common_utils.check_response_status(resp, check_failure=True) except iam_client.exceptions.NoSuchEntityException: logging.error(f'IAM: Delete Policy: {policy_arn} does not exist') # Construct response err_code = aws_error_codes.AWS_IAM_POLICY_DOES_NOT_EXIST resp = aws_error_codes.construct_response(err_code) return False, resp # If above call does not raise an exception, then call has succeded # return OK return True, {}
def create_policy(policy_name, policy_document, path='/', description=''): ''' Create a policy named: policy_name with the specification in: policy_document Arguments: - policy_name: Name of the policy to create. A string - policy_document: A JSON string that specified the policy. See below (or in tests) for an example - path: The path where policy resides. can be any valid path. - description: Description of the policy. A string Returns: - policy_arn: The ARN of the created policy. Null if failed - full_response: The full response to the API call (minus metadata) ''' # Connect to IAM iam_client = boto3.client('iam') try: # Now create the policy logging.info(f'Creating policy named {policy_name}') resp = iam_client.create_policy(PolicyName=policy_name, PolicyDocument=policy_document, Path=path, Description=description) aws_common_utils.check_response_status(resp, check_failure=True) except iam_client.exceptions.EntityAlreadyExistsException: logging.error(f'IAM: Create Policy: {policy_name} already exists') # Construct response err_code = aws_error_codes.AWS_IAM_POLICY_ALREADY_EXISTS resp = aws_error_codes.construct_response(err_code) return False, resp # Return the created policy ARN and the full response return resp['Policy']['Arn'], resp
def create_s3_bucket(bucket_name, ACL='', CreateBucketConfiguration=DEFAULT_BUCKET_REGION): ''' Create an S3 bucket if it does not already exist Arguments: - bucket_name: Bucket name (string) - ACL: Access control. See boto3 documentation - CreateBucketConfiguration: For more fine control. See boto3 documentation ''' # First check if bucket already exists buckets, _ = list_s3_buckets() if bucket_name in buckets: logging.info(f'Bucket name: {bucket_name} already exists.') return aws_s3_settings.S3_BUCKET_ALREADY_EXISTS else: logging.info(f'Creating bucket: {bucket_name}') s3_client = boto3.client('s3') try: # Try creating a bucket with given name response = s3_client.create_bucket( Bucket=bucket_name, CreateBucketConfiguration=CreateBucketConfiguration) except ClientError as e: # If bucket name exists in region (for any user), boto3 throws # a BucketAlreadyExists error and below seems to be the standard # way to handle this if e.response['Error']['Code'] == 'BucketAlreadyExists': # Then print the standard exception as it appears to have # the best explanation logging.error(f'Bucket name: {bucket_name} already exists ' 'in this region') logging.error(e) else: logging.error('Unexpected error occured') logging.error(e) return aws_s3_settings.S3_CREATE_BUCKET_FAILED sbool, scode = aws_common_utils.check_response_status(response) if not sbool: logging.info(f'AWS API call to S3 list buckets failed with code: ' f'{scode}') raise aws_exceptions.AWS_API_CallFailed return aws_s3_settings.S3_CREATE_BUCKET_FAILED else: logging.info(f'Successfully created bucket: {bucket_name}') return aws_s3_settings.S3_CREATE_BUCKET_SUCCESS
def delete_s3_bucket(bucket_name, aws_region=aws_settings.AWS_DEFAULT_REGION): ''' Delete bucket in requested region Arguments: - bucket_name: name of bucket to delete - aws_region: Delete bucket in this region Note: Currently only the AWS_DEFAULT_REGION is implemented Returns: - Success or error code ''' # If a region other than default region is specified, raise an error if aws_region != aws_settings.AWS_DEFAULT_REGION: raise aws_exceptions.AWS_RegionNotImplemented s3_client = boto3.client('s3') try: response = s3_client.delete_bucket(Bucket=bucket_name) except ClientError as e: # If an error is thrown, then this is standard way to handle this if e.response['Error']['Code'] == 'NoSuchBucket': # Then print the standard exception as it appears to have # the best explanation logging.error(f'No such bucket name: {bucket_name}') logging.error(e) else: logging.error('Unexpected error occured') logging.error(e) return aws_s3_settings.S3_BUCKET_DELETE_FAILED # Check if call succeded status_bool, status_code = aws_common_utils.check_response_status(response) if not status_bool: logging.info(f'AWS API call to S3 list buckets failed with code: ' f'{status_code}') raise aws_exceptions.AWS_API_CallFailed return aws_s3_settings.S3_BUCKET_DELETE_FAILED else: logging.info(f'Bucket: {bucket_name} successfully deleted') return aws_s3_settings.S3_BUCKET_DELETE_SUCCESS
def describe_table(table_name, aws_region=aws_settings.AWS_DEFAULT_REGION): ''' Describe a table in AWS DynamoDB Arguments: - aws_region: AWS region from which to list tables Currently unused - table_name: Name of table to describe Returns: - table_description: JSON describing the table. Pretty much the JSON returned by the AWS boto3 call ''' dyndb_client = boto3.client('dynamodb') resp = dyndb_client.describe_table(TableName=table_name) sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to describe DynamoDB table failed ' f'with code: {scode}') raise aws_exceptions.AWS_API_CallFailed return resp['Table']
def get_role_arn(role_name): ''' Get the role specified by role_name Arguments: - role_name: Name of the role Returns: - role_arn: being the ARN name - full_response: being the full response ''' # Connect to IAM and query the role iam_client = boto3.client('iam') resp = iam_client.get_role(RoleName=role_name) # Check if request worked sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to IAM get role failed with code: ' f'{scode}') raise aws_exceptions.AWS_API_CallFailed # Split up and return the response return resp['Role']['Arn'], resp
def list_roles(path_prefix='/'): ''' List IAM Roles ***** Mega Note: Pagination is not being used in this version. So if the number of roles is > 100, it will be truncated to 100 ****** Arguments: - path_prefix: The path prefix foir filtering results Returns: - roles_list: A list with role names - roles_dict: A dict keyed by role names with the attribute of said role as values - resp: Full response (minus metadata) ''' # Connect to IAM iam_client = boto3.client('iam') resp = iam_client.list_roles(PathPrefix=path_prefix) # Check response sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to IAM list roles failed with code: ' f'{scode}') raise aws_exceptions.AWS_API_CallFailed # Now make a distionary of roles roles_list_of_dicts = resp['Roles'] roles_dict = {x['RoleName']: x for x in roles_list_of_dicts} roles_list = list(roles_dict.keys()) # Delete the metata from response before returning del resp['ResponseMetadata'] return roles_list, roles_dict, resp
def list_tables(aws_region=aws_settings.AWS_DEFAULT_REGION): ''' List all DynamoDB in current AWS region ###### Mega Note: ###### This code does not use pagination. As such, the first 100 tables *ONLY* are returned. If there are more than 100 tables, code needs to be extended like the list lambda functions Arguments: - aws_region: AWS region from which to list tables Currently unused Returns: - table_names: A list of table names ''' dyndb_client = boto3.client('dynamodb') resp = dyndb_client.list_tables() sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to list DynamoDB table failed ' f'with code: {scode}') raise aws_exceptions.AWS_API_CallFailed return resp['TableNames']
def get_function(func_name): ''' Get information about func_name Arguments: - func_name: Name of the function for which information is requested Returns: A dict with keys: - Configuration: Which has info line func ARN, runtime, mem size - Code: The (zipped) function code. I beleive this is valid for 10 min - Tags: Tags for the function ''' lambda_client = boto3.client('lambda') resp = lambda_client.get_function(FunctionName=func_name) # Check for the response sbool, scode = aws_common_utils.check_response_status(resp) if not sbool: logging.info(f'AWS API call to list lambda functions failed with ' f'code: {scode}') raise aws_exceptions.AWS_API_CallFailed # If call succeded, simply delete the 'ResponseMetadata' key from the # response dict and return the remaining del (resp['ResponseMetadata']) return (resp)
def create_table(table_name, table_schema, primary_key, secondary_key=None, billing_mode='PAY_PER_REQUEST', other_attributes={}, aws_region=aws_settings.AWS_DEFAULT_REGION): ''' Create a DynamoDB table Arguments: - table_name: Name of table. Name needs to follow AWS name rules - table_schema: The schema of the table specified as a list of dicts. See example way below in the file. At the very least, the primary (and the optional secondary) key schemas need to be defines - primary_key: The indexing key - secondary_key: The optional sorting key - billing_mode: Default is PAY_PER_REQUEST. Set to a dictionary per API if other modes are needed - other_attributes: Other attributes as a dict. Currently not implemented Returns: - table_arn: The ARN of the created table - table_id: The ID of the created table - table_description: The full JSON response from AWS (minus the response metadata) ''' dyndb_client = boto3.client('dynamodb') # Define the attributes definition dict outside the create_table call. # This is because primary and secondary key types have to be specified # and then this dict updated with any other attributes # Also making implicit asumption here that pri key is string and # sec key is numerical. More coding needed if more flexibiliuty is needed # Now update it with any other attributes # attrs_definition.update(other_attributes) table = dyndb_client.create_table( TableName=table_name, KeySchema=[ { 'AttributeName': primary_key, 'KeyType': 'HASH' # HASH = primary key in AWS land }, { 'AttributeName': secondary_key, 'KeyType': 'RANGE' # RANG = sort key in AWS land } if secondary_key else {} ], AttributeDefinitions=[{ 'AttributeName': primary_key, 'AttributeType': 'S' }, { 'AttributeName': secondary_key, 'AttributeType': 'N' } if secondary_key else {}], BillingMode=billing_mode) sbool, scode = aws_common_utils.check_response_status(table) if not sbool: logging.info(f'AWS API call to create DynamoDB ' f'table {table_name} failed with code: {scode}') raise aws_exceptions.AWS_API_CallFailed # Return the table ARN, table ID and the entire response table_arn = table['TableDescription']['TableArn'] table_id = table['TableDescription']['TableId'] return table_arn, table_id, table['TableDescription']