def search_ruleset(self, environment_name, no_prompt = False): """ :param environment_name: :return: """ ruleset_found = False if environment_name != 'default': ruleset_file_name = 'ruleset-%s.json' % environment_name ruleset_file_path = os.path.join(os.getcwd(), ruleset_file_name) if os.path.exists(ruleset_file_path): if no_prompt or prompt_4_yes_no("A ruleset whose name matches your environment name was found in %s. Would you like to use it instead of the default one" % ruleset_file_name): ruleset_found = True self.filename = ruleset_file_path if not ruleset_found: self.filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rulesets/default.json')
def search_ruleset(self, environment_name, no_prompt=False): """ :param environment_name: :return: """ ruleset_found = False if environment_name != 'default': ruleset_file_name = 'ruleset-%s.json' % environment_name ruleset_file_path = os.path.join(self.rules_data_path, 'rulesets/%s' % ruleset_file_name) if os.path.exists(ruleset_file_path): if no_prompt or prompt_4_yes_no( "A ruleset whose name matches your environment name was found in %s. Would you like to use it instead of the default one" % ruleset_file_name): ruleset_found = True self.filename = ruleset_file_path if not ruleset_found: self.filename = os.path.join(self.rules_data_path, 'rulesets/default.json')
def main(): # Parse arguments parser = OpinelArgumentParser() parser.add_argument('debug') parser.add_argument('profile') parser.add_argument('csv-credentials') parser.add_argument('mfa-serial') parser.add_argument('mfa-code') parser.parser.add_argument('--role-arn', dest='role_arn', default=None, help='ARN of the assumed role.') parser.parser.add_argument('--external-id', dest='external_id', default=None, help='External ID to use when assuming the role.') args = parser.parse_args() # Configure the debug level configPrintException(args.debug) # Check version of opinel if not check_requirements(os.path.realpath(__file__)): return 42 # Arguments profile_name = args.profile[0] if args.csv_credentials: # Read credentials from a CSV file credentials = {} credentials['AccessKeyId'], credentials['SecretAccessKey'], credentials['SerialNumber'] = read_creds_from_csv(args.csv_credentials) if not credentials['AccessKeyId'] or not credentials['SecretAccessKey']: printError('Failed to read credentials from %s' % args.csv_credentials) return 42 use_found_credentials = True else: # Check for migration from existing profile to no-mfa profile use_found_credentials = False credentials = read_creds_from_aws_credentials_file(profile_name) if 'AccessKeyId' in credentials and credentials['AccessKeyId'] != None and credentials['SecretAccessKey'] != None and credentials['SerialNumber'] == None and credentials['SessionToken'] == None: if prompt_4_yes_no('Found long-lived credentials for the profile \'%s\'. Do you want to use those when configuring MFA' % profile_name): use_found_credentials = True iam_client = connect_service('iam', credentials) try: printInfo('Trying to read the MFA serial number associated with this IAM user...') user_name = iam_client.get_user()['User']['UserName'] mfa_devices = iam_client.list_mfa_devices(UserName = user_name)['MFADevices'] credentials['SerialNumber'] = mfa_devices[0]['SerialNumber'] except Exception as e: printException(e) pass if not use_found_credentials: # Get values credentials['AccessKeyId'] = prompt_4_value('AWS Access Key ID: ', no_confirm = True) credentials['SecretAccessKey'] = prompt_4_value('AWS Secret Access Key: ', no_confirm = True) if 'SerialNumber' not in credentials or not credentials['SerialNumber']: credentials['SerialNumber'] = prompt_4_mfa_serial() # Check for overwrite while True: c = read_creds_from_aws_credentials_file(profile_name) if 'AccessKeyId' in c and c['AccessKeyId']: if not prompt_4_yes_no('The profile \'%s\' already exists. Do you want to overwrite the existing values' % profile_name): if not prompt_4_yes_no('Do you want to create a new profile with these credentials'): printError('Configuration aborted.') return profile_name = prompt_4_value('Profile name: ') else: break else: break # Write values to credentials file write_creds_to_aws_credentials_file(profile_name, credentials) # Delete CSV file? if args.csv_credentials and prompt_4_yes_no('Do you want to delete the CSV file that contains your long-lived credentials?'): os.remove(args.csv_credentials)
def main(): # Parse arguments parser = OpinelArgumentParser() parser.add_argument('debug') parser.add_argument('profile') parser.add_argument('force') parser.add_argument('dry-run') parser.add_argument('regions') parser.add_argument('partition-name') parser.parser.add_argument('--interactive', dest='interactive', default=False, action='store_true', help='Interactive prompt to manually enter CIDRs.') parser.parser.add_argument('--csv-ip-ranges', dest='csv_ip_ranges', default=[], nargs='+', help='CSV file(s) containing CIDRs information.') parser.parser.add_argument('--skip-first-line', dest='skip_first_line', default=False, action='store_true', help='Skip first line when parsing CSV file.') parser.parser.add_argument('--attributes', dest='attributes', default=[], nargs='+', help='Name of the attributes to enter for each CIDR.') parser.parser.add_argument('--mappings', dest='mappings', default=[], nargs='+', help='Column number matching attributes when headers differ.') args = parser.parse_args() # Configure the debug level configPrintException(args.debug) # Check version of opinel if not check_requirements(os.path.realpath(__file__)): return 42 # Initialize the list of regions to work with regions = build_region_list('ec2', args.regions, args.partition_name) # For each profile/environment... for profile_name in args.profile: # Interactive mode if args.interactive: # Initalize prefixes attributes = args.attributes filename = 'ip-ranges-%s.json' % profile_name if os.path.isfile(filename): printInfo('Loading existing IP ranges from %s' % filename) prefixes = read_ip_ranges(filename) # Initialize attributes from existing values if attributes == []: for prefix in prefixes: for key in prefix: if key not in attributes: attributes.append(key) else: prefixes = [] # IP prefix does not need to be specified as an attribute attributes = [a for a in attributes if a != 'ip_prefix'] # Prompt for new entries while prompt_4_yes_no('Add a new IP prefix to the ip ranges'): ip_prefix = prompt_4_value('Enter the new IP prefix:') obj = {} for a in attributes: obj[a] = prompt_4_value('Enter the \'%s\' value:' % a) prefixes.append(new_prefix(ip_prefix, obj)) # Support loading from CSV file elif len(args.csv_ip_ranges) > 0: # Initalize prefixes prefixes = [] # Load CSV file contents for filename in args.csv_ip_ranges: with open(filename, 'rt') as f: csv_contents = f.readlines() # Initialize mappings attributes = args.attributes mappings = {} if attributes == []: # Follow structure of first line headers = csv_contents.pop(0).strip().split(',') for index, attribute in enumerate(headers): mappings[attribute] = index elif attributes and args.mappings == []: # Follow structure of first line but only map a subset of fields headers = csv_contents.pop(0).strip().split(',') attributes.append('ip_prefix') for attribute in set(attributes): mappings[attribute] = headers.index(attribute) else: # Indices of columns are provided as an argument for index, attribute in enumerate(attributes): mappings[attribute] = int(args.mappings[index]) if args.skip_first_line: csv_contents.pop(0) # For each line... for line in csv_contents: ip_prefix = {} values = line.strip().split(',') if len(values) < len(mappings): continue for attribute in mappings: ip_prefix[attribute] = values[mappings[attribute]] if 'ip_prefix' in mappings and 'mask' in mappings: ip = ip_prefix.pop('ip_prefix') mask = ip_prefix.pop('mask') ip_prefix['ip_prefix'] = '%s/%s' % (ip, mask.replace('/','')) prefixes.append(ip_prefix) # AWS mode else: # Initialize IP addresses printInfo('Fetching public IP information for the \'%s\' environment...' % profile_name) ip_addresses = {} # Search for AWS credentials credentials = read_creds(profile_name) if not credentials['AccessKeyId']: return 42 # For each region... for region in regions: # Connect to EC2 ec2_client = connect_service('ec2', credentials, region) if not ec2_client: continue # Get public IP addresses associated with EC2 instances printInfo('...in %s: EC2 instances' % region) reservations = handle_truncated_response(ec2_client.describe_instances, {}, ['Reservations']) for reservation in reservations['Reservations']: for i in reservation['Instances']: if 'PublicIpAddress' in i: ip_addresses[i['PublicIpAddress']] = new_ip_info(region, i['InstanceId'], False) get_name(i, ip_addresses[i['PublicIpAddress']], 'InstanceId') if 'NetworkInterfaces' in i: for eni in i['NetworkInterfaces']: if 'Association' in eni: ip_addresses[eni['Association']['PublicIp']] = new_ip_info(region, i['InstanceId'], False) # At that point, we don't know whether it's an EIP or not... get_name(i, ip_addresses[eni['Association']['PublicIp']], 'InstanceId') # Get all EIPs (to handle unassigned cases) printInfo('...in %s: Elastic IP addresses' % region) eips = handle_truncated_response(ec2_client.describe_addresses, {}, ['Addresses']) for eip in eips['Addresses']: instance_id = eip['InstanceId'] if 'InstanceId' in eip else None # EC2-Classic non associated EIPs have an empty string for instance ID (instead of lacking the attribute in VPC) if instance_id == '': instance_id = None ip_addresses[eip['PublicIp']] = new_ip_info(region, instance_id, True) ip_addresses[eip['PublicIp']]['name'] = instance_id # Format prefixes = [] for ip in ip_addresses: prefixes.append(new_prefix(ip, ip_addresses[ip])) # Generate an ip-ranges-<profile>.json file save_ip_ranges(profile_name, prefixes, args.force_write, args.debug)
def main(): # Parse arguments parser = OpinelArgumentParser() parser.add_argument('debug') parser.add_argument('profile') parser.add_argument('managed', dest='is_managed', default=False, action='store_true', help='Create a managed policy.') parser.add_argument( 'type', default=[None], nargs='+', choices=['group', 'managed', 'role', 'user'], help='Type of target that the policy will apply or be attached to.') parser.add_argument( 'targets', default=[], nargs='+', help= 'Name of the IAM entity the policy will be added to (required for inline policies).' ) parser.add_argument( 'templates', default=[], nargs='+', help='Path to the template IAM policies that will be created.') parser.add_argument('save', dest='save_locally', default=False, action='store_true', help='Generates the policies and store them locally.') args = parser.parse_args() # Configure the debug level configPrintException(args.debug) # Check version of opinel if not check_requirements(os.path.realpath(__file__)): return 42 # Arguments profile_name = args.profile[0] target_type = args.type[0] if len(args.templates) == 0: printError( 'Error: you must specify the path the template IAM policies.') return 42 if not args.is_managed and target_type == None: printError( 'Error: you must either create a managed policy or specify the type of IAM entity the policy will be attached to.' ) return 42 if not args.is_managed and target_type == None and len(args.targets) < 1: printError( 'Error: you must provide the name of at least one IAM %s you will attach this inline policy to.' % target_type) return 42 # Read creds credentials = read_creds(args.profile[0]) if not credentials['AccessKeyId']: return 42 # Connect to IAM APIs iam_client = connect_service('iam', credentials) if not iam_client: return 42 # Get AWS account ID aws_account_id = get_aws_account_id(credentials) # Create the policies for template in args.templates: if not os.path.isfile(template): printError('Error: file \'%s\' does not exist.' % template) continue with open(template, 'rt') as f: policy = f.read() policy = re_aws_account_id.sub(aws_account_id, policy) policy_name = os.path.basename(template).split('.')[0] if not args.is_managed: callback = getattr(iam_client, 'put_' + target_type + '_policy') params = {} params['PolicyName'] = policy_name params['PolicyDocument'] = policy for target in args.targets: params[target_type.title() + 'Name'] = target try: printInfo( 'Creating policy \'%s\' for the \'%s\' IAM %s...' % (policy_name, target, target_type)) callback(**params) except Exception as e: printException(e) pass else: params = {} params['PolicyDocument'] = policy params['PolicyName'] = policy_name description = '' # Search for a description file descriptions_dir = os.path.join(os.path.dirname(template), 'descriptions') if os.path.exists(descriptions_dir): description_file = os.path.join( descriptions_dir, os.path.basename(template).replace('.json', '.txt')) if os.path.isfile(description_file): with open(description_file, 'rt') as f: params['Description'] = f.read() elif prompt_4_yes_no( 'Do you want to add a description to the \'%s\' policy' % policy_name): params['Description'] = prompt_4_value( 'Enter the policy description:') params['Description'] = params['Description'].strip() printInfo('Creating policy \'%s\'...' % (policy_name)) new_policy = iam_client.create_policy(**params) if len(args.targets): callback = getattr(iam_client, 'attach_' + target_type + '_policy') for target in args.targets: printInfo('Attaching policy to the \'%s\' IAM %s...' % (target, target_type)) params = {} params['PolicyArn'] = new_policy['Policy']['Arn'] params[target_type.title() + 'Name'] = target callback(**params) if args.save_locally: with open('%s-%s.json' % (policy_name, profile_name), 'wt') as f: f.write(policy) f.close()
def main(): # Parse arguments parser = OpinelArgumentParser() parser.add_argument('debug') parser.add_argument('profile') parser.add_argument('regions', help='Regions where the stack(s) will be created.') parser.add_argument('partition-name') parser.parser.add_argument('--template', dest='template', default=None, required=True, help='Path to the CloudFormation template.') parser.parser.add_argument('--parameters', dest='parameters', default=None, nargs='+', help='Optional parameters for the stack.') args = parser.parse_args() # Configure the debug level configPrintException(args.debug) # Check version of opinel if not check_requirements(os.path.realpath(__file__)): return 42 # Get profile name profile_name = args.profile[0] # Search for AWS credentials credentials = read_creds(profile_name) if __name__ == '__main__': if not credentials['AccessKeyId']: return 42 # Validate the regions regions = build_region_list('cloudformation', args.regions, args.partition_name) if len(args.regions) == 0 and not prompt_4_yes_no( 'You didn\'t specify a region for this stack, do you want to create it in all regions ?' ): return 42 for region in regions: try: # Create stack api_client = connect_service('cloudformation', credentials, region) params = {} params['api_client'] = api_client if not args.template.startswith('/'): params['template_path'] = os.path.join( (os.path.dirname(os.path.realpath(__file__))), args.template) else: params['template_path'] = args.template if args.parameters: params['template_parameters'] = args.parameters params['stack_name'] = make_awsrecipes_stack_name( params['template_path']) create_or_update_stack(**params) except Exception as e: printException(e)
def main(): # Parse arguments parser = OpinelArgumentParser() parser.add_argument('debug') parser.add_argument('profile') parser.add_argument('user-name', help='Name of user(s) to be created.') parser.add_argument('group-name', help='Name of group(s) the user(s) will belong to.') parser.add_argument( 'force-common-group', default=False, action='store_true', help='Automatically add user(s) to the common group(s)') parser.add_argument('no-mfa', default=False, action='store_true', help='Do not configure and enable MFA.') parser.add_argument('no-password', default=False, action='store_true', help='Do not create a password and login') parser.add_argument('no-access-key', default=False, action='store_true', help='Do not generate an access key') parser.add_argument('always-trust', default=False, action='store_true', help='A not generate an access key') parser.add_argument('allow-plaintext', default=False, action='store_true', help='') parser.add_argument('no-prompt-before-plaintext', dest='prompt_before_plaintext', default=True, action='store_false', help='') args = parser.parse_args() # Configure the debug level configPrintException(args.debug) # Check version of opinel if not check_requirements(os.path.realpath(__file__)): return 42 # Arguments profile_name = args.profile[0] if not len(args.user_name): printError("Error, you need to provide at least one user name") return 42 # Search for AWS credentials credentials = read_creds(profile_name) if not credentials['AccessKeyId']: return 42 # Connect to IAM iam_client = connect_service('iam', credentials) if not iam_client: return 42 # Initialize and compile the list of regular expression for category groups #if 'category_groups' in default_args and 'category_regex' in default_args: # category_regex = init_iam_group_category_regex(default_args['category_groups'], default_args['category_regex']) # Iterate over users for user in args.user_name: # Search for the GPG key abort = False gpg_key = get_gpg_key(user) if not gpg_key: printInfo('No PGP key found for user matching %s' % user) if args.allow_plaintext: if args.prompt_before_plaintext and not prompt_4_yes_no( 'Save unencrypted value'): abort = True else: abort = True if abort: printError( 'Will not create user %s as credentials cannot be saved. Use --allow-plaintext to enable storage of unencrypted credentials.' ) continue # Prepare the output folder try: user_dir = 'users/%s' % user os.makedirs(user_dir) except Exception as e: printError( 'Error, failed to create a temporary folder for user %s.' % user) continue # Determine the groups Groups groups = args.group_name if args.force_common_group: groups += args.common_groups # Add user to a category group # if 'category_groups' in default_args and len(default_args['category_groups']) > 0: # add_user_to_category_group(iam_client, args.group_name, default_args['category_groups'], category_regex, user) # Create the user user_data = create_user(iam_client, user, groups, not args.no_password, not args.no_mfa, not args.no_access_key) if 'errors' in user_data and len(user_data['errors']) > 0: printError('Error doing the following actions:\n%s' % '\n'.join(' - %s' % action for action in user_data['errors'])) # Save data if 'password' in user_data: gpg_and_write('%s/password.txt' % user_dir, user_data['password'], gpg_key, args.always_trust) if 'AccessKeyId' in user_data: credentials = '[%s]\naws_access_key_id = %s\naws_secret_access_key = %s\n' % ( profile_name, user_data['AccessKeyId'], user_data['SecretAccessKey']) # TODO: mfa gpg_and_write('%s/credentials' % user_dir, credentials, gpg_key, args.always_trust) # Create a zip archive f = zipfile.ZipFile('users/%s.zip' % user, 'w') for root, dirs, files in os.walk(user_dir): for file in files: f.write(os.path.join(root, file)) f.close() shutil.rmtree(user_dir)
def main(): # Parse arguments parser = OpinelArgumentParser() parser.add_argument('debug') parser.add_argument('profile') parser.parser.add_argument('--role-name', dest='role_name', default=[], nargs='+', required=True, help='Name of the role to be assumed in each child account.') parser.parser.add_argument('--ou', dest='org_unit', default=[], nargs='+', help='') parser.parser.add_argument('--profile-prefix', dest='profile_prefix', default=None, help='') args = parser.parse_args() # Configure the debug level configPrintException(args.debug) # Check version of opinel if not check_requirements(os.path.realpath(__file__)): return 42 # Arguments source_profile = AWSProfiles.get(args.profile)[0] credentials = read_creds(args.profile[0]) if not credentials['AccessKeyId']: return 42 # Get all accounts to setup api_client = connect_service('organizations', credentials) if len(args.org_unit) == 0: if prompt_4_yes_no('Do you want to specify a particular organizational unit'): ous = get_organizational_units(api_client) choice = prompt_4_value('Which OU do you want to configure IAM for', choices = [ou['Name'] for ou in ous], display_indices = True, no_confirm = True, return_index = True) account_list = list_accounts_for_parent(api_client, ous[choice]) else: account_list = get_organization_accounts(api_client) # Setup the accounts organization_profiles = {'ready': [], 'notready': []} for account in account_list: printInfo('Validating role name in %s...' % account['Name'], newLine = False) profile_name = account['Name'].lower().replace(' ', '_') if args.profile_prefix: profile_name = '%s-%s' % (args.profile_prefix, profile_name) profile = AWSProfile(filename = aws_config_file, name = profile_name, account_id = account['Id']) profile.set_attribute('source_profile', source_profile.name) success = False for role_name in args.role_name: try: role_arn = 'arn:aws:iam::%s:role/%s' % (account['Id'], role_name) role_credentials = assume_role(role_name, credentials, role_arn, 'aws-recipes', silent = True ) profile.set_attribute('role_arn', 'arn:aws:iam::%s:role/%s' % (account['Id'], role_name)) profile.set_attribute('source_profile', source_profile.name) organization_profiles['ready'].append(profile) printInfo(' success') success = True break except Exception as e: pass if not success: printInfo(' failure') organization_profiles['notready'].append(profile) for profile in organization_profiles['ready']: profile.write() for profile in organization_profiles['notready']: printError('Failed to determine a valid role in %s (%s)' % (profile.name, profile.account_id))