예제 #1
0
 def converge(self, dry_run_report, provisioner_overrides=None):
     '''
     Executes the proposed actions in the provided dry run report.
     Returns a report of actions taken.
     '''
     report = OrderedDict()
     if 'actions' not in dry_run_report:
         return report
     report['actions'] = dry_run_report['actions']
     report['changes'] = OrderedDict()
     if provisioner_overrides:
         for name, value in provisioner_overrides.items():
             self.provisioner[name] = value
     session_builder = self._get_session_builder()
     org_service = OrganizationService(session_builder=session_builder)
     if report['actions'].get('organizations', {}).get('organization', {}).get('action', None) == 'create':
         self._create_organization(report=report, org_service=org_service)
         logger.info('Executing dry run to identify changes needed to converge the newly created organization.')
         new_dry_run_report = self.dry_run()
         if 'actions' in new_dry_run_report:
             report['actions'] = OrderedDict({**report['actions'], **new_dry_run_report['actions']})
     self.updated_model = copy.deepcopy(self.aws_model)
     self._upsert_policies(report=report, org_service=org_service)
     self._upsert_root_policy_targets(report=report, org_service=org_service)
     self._upsert_accounts(report=report, org_service=org_service)
     self._upsert_orgunits(report=report, org_service=org_service)
     self._update_account_associations(report=report, org_service=org_service)
     self._delete_orgunits(report=report, org_service=org_service)
     self._delete_policies(report=report, org_service=org_service)
     return report
예제 #2
0
 def create_accounts(self, organization, accounts):
     '''
     Creates one or more accounts in the Organization. As account creation
     is asynchronous, it waits until each account finishes creating, then
     returns a changes dict for the report.
     '''
     changes = {}
     for account in accounts:
         creation_statuses = {}
         logger.info('Creating account %s', account)
         create_account_params = {
             'Email': organization.accounts[account]['owner'],
             'AccountName': organization.accounts[account]['name']
         }
         create_res = self.client.create_account(**create_account_params)
         creation_statuses[account] = create_res['CreateAccountStatus']
         self._wait_on_account_creation(creation_statuses)
         for account_name, status in creation_statuses.items():
             if status['State'] == 'SUCCEEDED':
                 change = 'created'
             elif status['State'] == 'FAILED':
                 change = 'failed'
             else:
                 change = 'unknown'
             changes[account_name] = {"change": change}
             if status == 'failed':
                 changes[account_name]['reason'] = status['FailureReason']
     return changes
예제 #3
0
 def _create_organization(self, org_model):
     logger.info('Creating organization.')
     organization_parameters = {"FeatureSet": org_model.featureset}
     self.client.create_organization(**organization_parameters)
     root_parent_id = self._get_root_parent_id(org_model.root_account_id)
     logger.info('Enabling Service Control Policy policy type.')
     self.client.enable_policy_type(RootId=root_parent_id,
                                    PolicyType='SERVICE_CONTROL_POLICY')
예제 #4
0
def _get_config_loader_for_version(version):
    if version in _CONFIG_VERSIONS_TO_LOADERS:
        logger.info('Using config loader for config version %s', version)
        loader = _CONFIG_VERSIONS_TO_LOADERS[version]()
    else:
        #pylint: disable=line-too-long
        logger.info('No config loader found for specified config version, or config version not specified. Using default.')
        loader = _CONFIG_VERSIONS_TO_LOADERS['default']()
    return loader
예제 #5
0
 def _update_orgunit(self, org_model, orgunit_name):
     logger.info('Updating orgunit %s', orgunit_name)
     orgunit_parameters = {
         'OrganizationalUnitId':
         org_model.updated_model.orgunits[orgunit_name]['id'],
         'Name':
         org_model.orgunits[orgunit_name]['name']
     }
     update_res = self.client.update_organizational_unit(
         **orgunit_parameters)
     return update_res['OrganizationalUnit']['Id']
예제 #6
0
 def _update_policy(self, org_model, policy_name):
     logger.info('Updating policy %s', policy_name)
     content_json = json.dumps(
         org_model.policies[policy_name]['document']['content'])
     policy_parameters = {
         'Content': content_json,
         'Description': org_model.policies[policy_name]['description'],
         'Name': org_model.policies[policy_name]['name'],
         'PolicyId': org_model.updated_model.policies[policy_name]['id']
     }
     update_res = self.client.update_policy(**policy_parameters)
     return update_res['Policy']['PolicySummary']['Id']
예제 #7
0
 def _create_policy(self, org_model, policy_name):
     logger.info('Creating policy %s', policy_name)
     content_json = json.dumps(
         org_model.policies[policy_name]['document']['content'])
     policy_parameters = {
         'Content': content_json,
         'Description': org_model.policies[policy_name]['description'],
         'Name': org_model.policies[policy_name]['name'],
         'Type': 'SERVICE_CONTROL_POLICY'
     }
     create_res = self.client.create_policy(**policy_parameters)
     return create_res['Policy']['PolicySummary']['Id']
예제 #8
0
    def delete_policy(self, organization, policy_name):
        '''
        Deletes a Service Control Policy from the organization and returns a
        changes dict for the converge report.

        The caller is responsible for ensuring that all policy attachments have
        already been removed to ensure successful deletion.
        '''
        logger.info('Deleting policy %s', policy_name)
        policy_id = organization.aws_model.policies[policy_name]['id']
        self.client.delete_policy(PolicyId=policy_id)
        return {'change': 'deleted', 'id': policy_id}
예제 #9
0
def run():
    '''
    Parses arguments and executes from the command line.
    '''
    args = _parse_args()
    provisioner_overrides = {}
    if args.profile:
        provisioner_overrides['profile'] = args.profile
    log_handling.enable_console_logging(level=args.log_level)
    org_model = config_loader.load_organization_from_yaml_file(
        args.config_file)
    dry_run_report = org_model.dry_run(
        provisioner_overrides=provisioner_overrides)
    dry_run_report[
        'configured_organization'] = config_loader.dump_organization_to_config(
            dry_run_report['configured_organization'])
    dry_run_report[
        'actual_organization'] = config_loader.dump_organization_to_config(
            dry_run_report['actual_organization'])
    dry_run_report_yaml = helpers.ordered_yaml_dump(dry_run_report)
    logger.info('Dry run report follows.')
    logger.info(dry_run_report_yaml)
    if args.dry_run_report_file:
        helpers.write_report(report=dry_run_report_yaml,
                             output_file=args.dry_run_report_file)
    if args.converge:
        converge_report = org_model.converge(
            dry_run_report=dry_run_report,
            provisioner_overrides=provisioner_overrides)
        converge_report_yaml = helpers.ordered_yaml_dump(converge_report)
        logger.info('Converge report follows.')
        logger.info(converge_report_yaml)
        if args.converge_report_file:
            helpers.write_report(report=converge_report_yaml,
                                 output_file=args.converge_report_file)
예제 #10
0
 def _rebuild_orgunits(self, org_service):
     logger.info("Rebuilding the OrganizationalUnit hierarchy.")
     for orgunit in self.aws_model.orgunits.values():
         self._remove_accounts_from_orgunit(orgunit_name=orgunit['name'],
                                            org_service=org_service)
     self._remove_orgunits(org_service=org_service)
     self._create_orgunits(org_service=org_service)
     self.updated_model.reload_orgunits(org_service=org_service)
     self.updated_model.reload_policies(org_service=org_service)
     for orgunit in self.orgunits:
         org_service.update_orgunit_policies(organization=self, orgunit_name=orgunit)
         for account in self.orgunits[orgunit].get('accounts', []):
             if not account in self.updated_model.accounts:
                 continue
             org_service.move_account(organization=self, account_name=account,
                                      parent_name=orgunit)
예제 #11
0
 def _wait_on_account_creation(self, creation_statuses):
     logger.info('Waiting on account creation to complete.')
     waiting_accounts = creation_statuses.keys()
     while waiting_accounts:
         # Wait 10 seconds between checking account statuses
         time.sleep(10)
         for account in waiting_accounts:
             request_id = creation_statuses[account]['Id']
             creation_statuses[
                 account] = self.client.describe_create_account_status(
                     CreateAccountRequestId=request_id
                 )['CreateAccountStatus']
         waiting_accounts = [
             account for account in waiting_accounts
             if creation_statuses[account]['State'] == 'IN_PROGRESS'
         ]
예제 #12
0
 def dry_run(self, provisioner_overrides=None):
     '''
     Loads a corresponding model from AWS and generates a report of a comparison
     against it and actions that would be taken to converge the AWS resources to
     match this model.
     '''
     if provisioner_overrides:
         for name, value in provisioner_overrides.items():
             self.provisioner[name] = value
     report = OrderedDict()
     self.raise_if_invalid()
     logger.info("Loading model from existing AWS resources.")
     self.initialize_aws_model()
     aws_model_problems = self.aws_model.validate()
     if aws_model_problems:
         report['aws_model_problems'] = aws_model_problems
     report = self.compare_against_aws_model(report=report)
     return report
예제 #13
0
    def load_orgunits(self, organization):
        '''
        Loads information about OrganizationalUnits into an initialized
        AWS Organization model from AWS.

        The caller is responsible for already having loaded accounts
        into the Organization model so that account names can be loaded into
        organizations from account IDs.

        It doesn't return anything useful as it's modifying the provided Organization
        model directly.
        '''
        logger.debug("Organization model ids_to_children follows.")
        logger.debug(helpers.pretty_format(organization.ids_to_children))
        for orgunit_id in organization.ids_to_children[
                organization.root_parent_id]['orgunits']:
            self._load_orgunit(org_model=organization, orgunit_id=orgunit_id)
        self._add_orgunit_children_to_parents(org_model=organization)
예제 #14
0
 def load_accounts(self, organization):
     '''
     Loads existing accounts into organization.accounts. It also creates
     organization.account_ids_to_names with a mapping of account Id values
     to account Name values, for use by load_orgunits in loading the names
     of child accounts.
     '''
     paginator = self.client.get_paginator('list_accounts')
     for page in paginator.paginate():
         logger.debug("list_accounts page response follows")
         logger.debug(helpers.pretty_format(page))
         for account in page['Accounts']:
             account_model = {
                 "name": account["Name"],
                 "owner": account["Email"],
                 "id": str(account["Id"]),
                 "regions": []
             }
             organization.accounts[account["Name"]] = account_model
             organization.account_ids_to_names[
                 account["Id"]] = account["Name"]
예제 #15
0
    def create_orgunit(self, org_model, orgunit_name, parent_name):
        '''
        Creates the specified orgunit from the configured Organization model.

        The caller is responsible for ensuring that the Organization is in
        the appropriate state for the Organization to be created (e.g.,
        parent Orgunits have already been created.
        '''
        logger.info('Creating orgunit %s', orgunit_name)
        if parent_name == 'root':
            parent_id = org_model.updated_model.root_parent_id
        else:
            parent_id = org_model.updated_model.orgunits[parent_name]['id']
        orgunit_parameters = {
            # Set the parent ID to the root ID, we'll update it later.
            'ParentId': parent_id,
            'Name': org_model.orgunits[orgunit_name]['name']
        }
        create_res = self.client.create_organizational_unit(
            **orgunit_parameters)
        return create_res['OrganizationalUnit']['Id']
예제 #16
0
 def update_entity_policy_attachments(self, org_model, target_id,
                                      old_policies, new_policies):
     '''
     Updates the policies associations for the target entity
     '''
     for policy in new_policies:
         if policy not in old_policies:
             logger.info('Adding policy association for %s to target %s',
                         policy, target_id)
             policy_id = org_model.updated_model.policies[policy]['id']
             try:
                 self.client.attach_policy(PolicyId=policy_id,
                                           TargetId=target_id)
             except botocore.exceptions.ClientError as err:
                 if 'DuplicatePolicyAttachmentException' in str(err):
                     logger.warning(
                         'Policy %s is already attached to target %s',
                         policy, target_id)
                 else:
                     raise
     for policy in old_policies:
         if policy not in new_policies:
             logger.info('Removing policy association for %s to target %s',
                         policy, target_id)
             policy_id = org_model.updated_model.policies[policy]['id']
             self.client.detach_policy(PolicyId=policy_id,
                                       TargetId=target_id)
예제 #17
0
 def load_organization_from_config(self, config):
     '''
     Creates a new organization from the provided configuration datastructure.
     Returns the created organization.
     '''
     organization = Organization(root_account_id=str(config['root']))
     organization.source = "config"
     organization.raw_config = config
     if 'root_policies' in config:
         organization.root_policies = config['root_policies']
     else:
         organization.root_policies = self._default_root_policies
     if 'featureset' in config:
         organization.featureset = config['featureset']
     else:
         logger.info('featureset parameter not present on organization, assuming "ALL"')
         organization.featureset = self._default_featureset
     if 'accounts' in config:
         organization.accounts = self._load_accounts(config['accounts'])
     if 'policies' in config:
         organization.policies = self._load_policies(config['policies'])
     if 'orgunits' in config:
         organization.orgunits = self._load_orgunits(config['orgunits'])
     return organization
예제 #18
0
    def move_account(self, organization, account_name, parent_name):
        '''
        Reassociates an account with a new parent and returns a changes dict
        for the converge report.

        No change will be made if the account is currently associated with the
        specified parent.
        '''
        if parent_name == 'root':
            dest_parent_id = organization.aws_model.root_parent_id
        else:
            dest_parent_id = organization.updated_model.orgunits[parent_name][
                'id']
        account_id = organization.updated_model.accounts[account_name]['id']
        list_parents_res = self.client.list_parents(ChildId=account_id)
        source_parent_id = list_parents_res['Parents'][0]['Id']
        if dest_parent_id != source_parent_id:
            logger.info('Associating account %s with parent %s', account_name,
                        parent_name)
            self.client.move_account(AccountId=account_id,
                                     SourceParentId=source_parent_id,
                                     DestinationParentId=dest_parent_id)
            return {"changes": "reassociated", "parent": dest_parent_id}
        return {}
예제 #19
0
    def delete_orgunit(self, organization, orgunit_name):
        '''
        Deletes an OrganizationalUnit from the organization and returns a
        changes dict for the converge report.

        The caller is responsible for ensuring that the Orgunit to be deleted has
        no child Accounts or Orgunits to ensure successful deletion.
        '''
        logger.info('Deleting orgunit %s', orgunit_name)
        orgunit_id = organization.aws_model.orgunits[orgunit_name]['id']
        try:
            self.client.delete_organizational_unit(
                OrganizationalUnitId=orgunit_id)
            return {'change': 'deleted', 'id': orgunit_id}
        except botocore.exceptions.ClientError as err:
            logger.info('Orgunit %s already deleted.', orgunit_name)
            logger.info(str(err))
            return None
예제 #20
0
    def load_organization(self, organization):
        '''
        Initializes an Organization model with information about the Organization
        and its OrganizationUnit and Account hierarchy.

        It doesn't return anything useful as it's modifying the provided Organization
        model directly.
        '''
        try:
            describe_org_response = self.client.describe_organization()
            if not describe_org_response['Organization']:
                organization.exists = False
            logger.debug("describe_organization response follows.")
            logger.debug(helpers.pretty_format(describe_org_response))
        except botocore.exceptions.ClientError:
            logger.info(
                "Got an error trying to describe the organization, assuming organization does not exist."
            )
            organization.exists = False
        if organization.exists:
            self._set_organization_attributes(
                org_model=organization,
                describe_org_response=describe_org_response['Organization'])
예제 #21
0
 def _remove_accounts_from_orgunit(self, orgunit_name, org_service):
     logger.info("Moving accounts associated with %s to the organization root temporarily", orgunit_name)
     for account in self.updated_model.orgunits[orgunit_name].get('accounts', []):
         org_service.move_account(organization=self, account_name=account,
                                  parent_name='root')
예제 #22
0
    def _set_org_ids_to_children(self, org_model, parent):
        logger.debug("Enumerating children for parent %s", parent)
        orgunit_children = {"Children": []}
        paginator = self.client.get_paginator('list_children')
        list_orgunit_children_params = {
            "ParentId": parent,
            "ChildType": "ORGANIZATIONAL_UNIT"
        }
        logger.debug("list_children orgunit call params follow")
        logger.debug(helpers.pretty_format(list_orgunit_children_params))
        for page in paginator.paginate(**list_orgunit_children_params):
            logger.debug("Page response follows.")
            logger.debug(helpers.pretty_format(page))
            if page['Children']:
                orgunit_children['Children'] += page['Children']

        account_children = {"Children": []}
        list_account_children_params = {
            "ParentId": parent,
            "ChildType": "ACCOUNT"
        }
        logger.debug("list_children account call params follow")
        logger.debug(helpers.pretty_format(list_account_children_params))
        for page in paginator.paginate(**list_account_children_params):
            logger.debug("Page response follows.")
            logger.debug(helpers.pretty_format(page))
            if page['Children']:
                account_children['Children'] += page['Children']

        logger.debug("orgunit children response follows:")
        logger.debug(helpers.pretty_format(orgunit_children))
        logger.debug("account children response follows:")
        logger.debug(helpers.pretty_format(account_children))
        if not parent in org_model.ids_to_children:
            org_model.ids_to_children[parent] = {
                "accounts": [],
                "orgunits": []
            }
        if account_children['Children']:
            for account in account_children['Children']:
                org_model.ids_to_children[parent]['accounts'].append(
                    account['Id'])
        if orgunit_children['Children']:
            for orgunit in orgunit_children['Children']:
                org_model.ids_to_children[parent]['orgunits'].append(
                    orgunit['Id'])
                self._set_org_ids_to_children(org_model=org_model,
                                              parent=orgunit['Id'])