Beispiel #1
0
def update_deployment_account_output_parameters(
        deployment_account_region,
        region,
        deployment_account_role,
        cloudformation):
    """
    Update parameters on the deployment account across target
    regions based on the output of CloudFormation base stacks
    in the deployment account.
    """
    deployment_account_parameter_store = ParameterStore(
        deployment_account_region, deployment_account_role
    )
    parameter_store = ParameterStore(
        region, deployment_account_role
    )

    for key, value in cloudformation.get_stack_regional_outputs().items():
        deployment_account_parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region),
            value
        )
        parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region),
            value
        )
Beispiel #2
0
def configure_generic_account(sts, event, region, role):
    """
    Fetches the kms_arn from the deployment account main region
    and adds the it plus the deployment_account_id parameter to the
    target account so it can be consumed in CloudFormation. These
    are required for the global.yml in all target accounts.
    """
    try:
        deployment_account_role = sts.assume_cross_account_role(
            'arn:aws:iam::{0}:role/{1}'.format(
                event['deployment_account_id'],
                event['cross_account_access_role']
            ), 'configure_generic'
        )
        parameter_store_deployment_account = ParameterStore(
            event['deployment_account_region'],
            deployment_account_role
        )
        parameter_store_target_account = ParameterStore(
            region,
            role
        )
        kms_arn = parameter_store_deployment_account.fetch_parameter('/cross_region/kms_arn/{0}'.format(region))
    except (ClientError, ParameterNotFoundError):
        raise GenericAccountConfigureError(
            'Account {0} cannot yet be bootstrapped '
            'as the Deployment Account has not yet been bootstrapped. '
            'Have you moved your Deployment account into the deployment OU?'.format(event['account_id'])
        )
    parameter_store_target_account.put_parameter('kms_arn', kms_arn)
    parameter_store_target_account.put_parameter('deployment_account_id', event['deployment_account_id'])
Beispiel #3
0
def update_deployment_account_output_parameters(
        deployment_account_region,
        region,
        kms_and_bucket_dict,
        deployment_account_role,
        cloudformation):
    """
    Update parameters on the deployment account across target
    regions based on the output of CloudFormation base stacks
    in the deployment account.
    """
    deployment_account_parameter_store = ParameterStore(
        deployment_account_region, deployment_account_role
    )
    parameter_store = ParameterStore(
        region, deployment_account_role
    )
    outputs = cloudformation.get_stack_regional_outputs()
    kms_and_bucket_dict[region] = {}
    kms_and_bucket_dict[region]['kms'] = outputs['kms_arn']
    kms_and_bucket_dict[region]['s3_regional_bucket'] = outputs['s3_regional_bucket']
    for key, value in outputs.items():
        deployment_account_parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region),
            value
        )
        parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region),
            value
        )

    return kms_and_bucket_dict
def update_master_account_parameters(parsed_event, parameter_store):
    """
    Update the Master account parameter store in us-east-1 with the deployment_account_id
    then updates the main deployment region with that same value
    """
    parameter_store.put_parameter('deployment_account_id', parsed_event.account_id)
    parameter_store = ParameterStore(parsed_event.deployment_account_region, boto3)
    parameter_store.put_parameter('deployment_account_id', parsed_event.account_id)
Beispiel #5
0
def configure_master_account_parameters(event):
    """
    Update the Master account parameter store in us-east-1 with the deployment_account_id
    then updates the main deployment region with that same value
    """
    parameter_store_master_account_region = ParameterStore(os.environ["AWS_REGION"], boto3)
    parameter_store_master_account_region.put_parameter('deployment_account_id', event['account_id'])

    parameter_store_deployment_account_region = ParameterStore(event['deployment_account_region'], boto3)
    parameter_store_deployment_account_region.put_parameter('deployment_account_id', event['account_id'])
def configure_deployment_account(parsed_event, role):
    for region in list(set([parsed_event.deployment_account_region] + parsed_event.regions)):
        parameters = ParameterStore(region, role)
        if region == parsed_event.deployment_account_region:
            for key, value in parsed_event.create_deployment_account_parameters().items():
                parameters.put_parameter(
                    key,
                    value
                )
                continue
        parameters.put_parameter('organization_id', os.environ["ORGANIZATION_ID"])
Beispiel #7
0
def configure_deployment_account_parameters(event, role):
    """
    Applies the Parameters from adfconfig plus other essential
    Parameters to the Deployment Account in each region as defined in
    adfconfig.yml
    """
    for region in list(
            set([event["deployment_account_region"]] + event["regions"])):
        parameter_store = ParameterStore(region, role)
        for key, value in event['deployment_account_parameters'].items():
            parameter_store.put_parameter(key, value)
Beispiel #8
0
def worker_thread(account_id, sts, config, s3, cache, kms_dict):
    """
    The Worker thread function that is created for each account
    in which CloudFormation create_stack is called
    """
    LOGGER.debug("%s - Starting new worker thread", account_id)

    organizations = Organizations(role=boto3, account_id=account_id)
    ou_id = organizations.get_parent_info().get("ou_parent_id")

    account_state = is_account_in_invalid_state(ou_id, config.config)
    if account_state:
        LOGGER.info("%s %s", account_id, account_state)
        return

    account_path = organizations.build_account_path(
        ou_id,
        [],  # Initial empty array to hold OU Path,
        cache)
    try:
        role = ensure_generic_account_can_be_setup(sts, config, account_id)

        # Regional base stacks can be updated after global
        for region in list(
                set([config.deployment_account_region] +
                    config.target_regions)):
            # Ensuring the kms_arn on the target account is up-to-date
            parameter_store = ParameterStore(region, role)
            parameter_store.put_parameter('kms_arn', kms_dict[region])

            cloudformation = CloudFormation(
                region=region,
                deployment_account_region=config.deployment_account_region,
                role=role,
                wait=True,
                stack_name=None,
                s3=s3,
                s3_key_path=account_path,
                account_id=account_id)
            try:
                cloudformation.create_stack()
            except GenericAccountConfigureError as error:
                if 'Unable to fetch parameters' in str(error):
                    LOGGER.error(
                        '%s - Failed to update its base stack due to missing parameters (deployment_account_id or kms_arn), '
                        'ensure this account has been bootstrapped correctly by being moved from the root '
                        'into an Organizational Unit within AWS Organizations.',
                        account_id)
                raise Exception from error

    except GenericAccountConfigureError as generic_account_error:
        LOGGER.info(generic_account_error)
        return
Beispiel #9
0
def update_deployment_account_output_parameters(deployment_account_region,
                                                region,
                                                deployment_account_role,
                                                cloudformation):
    deployment_account_parameter_store = ParameterStore(
        deployment_account_region, deployment_account_role)
    parameter_store = ParameterStore(region, deployment_account_role)

    for key, value in cloudformation.get_stack_regional_outputs().items():
        deployment_account_parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region), value)
        parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region), value)
def configure_deployment_account(parsed_event, role):
    """
    Applies the Parameters from adfconfig plus other essential
    Parameters to the Deployment Account in each region as defined in
    adfconfig.yml
    """
    for region in list(
            set([parsed_event.deployment_account_region] +
                parsed_event.regions)):
        parameters = ParameterStore(region, role)
        if region == parsed_event.deployment_account_region:
            for key, value in parsed_event.create_deployment_account_parameters(
            ).items():
                if value:
                    parameters.put_parameter(key, value)

        parameters.put_parameter('organization_id',
                                 os.environ["ORGANIZATION_ID"])
def configure_generic_account(sts, event, region, role):
    """
    Fetches the kms_arn from the deployment account main region
    and adds the it plus the deployment_account_id parameter to the
    target account so it can be consumed in CloudFormation. These
    are required for the global.yml in all target accounts.
    """
    deployment_account_role = sts.assume_cross_account_role(
        'arn:aws:iam::{0}:role/{1}'.format(event['deployment_account_id'],
                                           event['cross_account_access_role']),
        'configure_generic')
    parameter_store_deployment_account = ParameterStore(
        event['deployment_account_region'], deployment_account_role)
    parameter_store_target_account = ParameterStore(region, role)
    kms_arn = parameter_store_deployment_account.fetch_parameter(
        '/cross_region/kms_arn/{0}'.format(region))
    parameter_store_target_account.put_parameter('kms_arn', kms_arn)
    parameter_store_target_account.put_parameter(
        'deployment_account_id', event['deployment_account_id'])
def prepare_deployment_account(sts, deployment_account_id, config):
    """
    Ensures configuration is up to date on the deployment account
    and returns the role that can be assumed by the master account
    to access the deployment account
    """
    deployment_account_role = sts.assume_cross_account_role(
        'arn:aws:iam::{0}:role/{1}'.format(deployment_account_id,
                                           config.cross_account_access_role),
        'master')
    for region in list(
            set([config.deployment_account_region] + config.target_regions)):
        deployment_account_parameter_store = ParameterStore(
            region, deployment_account_role)
        deployment_account_parameter_store.put_parameter(
            'organization_id', os.environ["ORGANIZATION_ID"])

    deployment_account_parameter_store = ParameterStore(
        config.deployment_account_region, deployment_account_role)
    deployment_account_parameter_store.put_parameter(
        'deployment_account_bucket', DEPLOYMENT_ACCOUNT_S3_BUCKET_NAME)
    if '@' not in config.notification_endpoint:
        config.notification_channel = config.notification_endpoint
        config.notification_endpoint = "arn:aws:lambda:{0}:{1}:function:SendSlackNotification".format(
            config.deployment_account_region, deployment_account_id)
    for item in ('cross_account_access_role', 'notification_type',
                 'notification_endpoint', 'notification_channel'):
        if getattr(config, item) is not None:
            deployment_account_parameter_store.put_parameter(
                '/notification_endpoint/main'
                if item == 'notification_channel' else item,
                str(getattr(config, item)))

    return deployment_account_role
Beispiel #13
0
def configure_generic_account(sts, event, region, role):
    """
    Fetches the kms_arn from the deployment account main region
    and adds the it plus the deployment_account_id parameter to the
    target account so it can be consumed in CloudFormation. These
    are required for the global.yml in all target accounts.
    """
    try:
        deployment_account_id = event['deployment_account_id']
        cross_account_access_role = event['cross_account_access_role']
        role_arn = f'arn:{PARTITION}:iam::{deployment_account_id}:role/{cross_account_access_role}'

        deployment_account_role = sts.assume_cross_account_role(
            role_arn=role_arn, role_session_name='configure_generic')
        parameter_store_deployment_account = ParameterStore(
            event['deployment_account_region'], deployment_account_role)
        parameter_store_target_account = ParameterStore(region, role)
        kms_arn = parameter_store_deployment_account.fetch_parameter(
            f'/cross_region/kms_arn/{region}')
        bucket_name = parameter_store_deployment_account.fetch_parameter(
            f'/cross_region/s3_regional_bucket/{region}')
    except (ClientError, ParameterNotFoundError):
        raise GenericAccountConfigureError(
            f'Account {event["account_id"]} cannot yet be bootstrapped '
            'as the Deployment Account has not yet been bootstrapped. '
            'Have you moved your Deployment account into the deployment OU?'
        ) from None
    parameter_store_target_account.put_parameter('kms_arn', kms_arn)
    parameter_store_target_account.put_parameter('bucket_name', bucket_name)
    parameter_store_target_account.put_parameter(
        'deployment_account_id', event['deployment_account_id'])
def update_deployment_account_output_parameters(deployment_account_region,
                                                region,
                                                deployment_account_role,
                                                cloudformation):

    deployment_account_parameter_store = ParameterStore(
        deployment_account_region, deployment_account_role)
    regional_parameter_store = ParameterStore(region, deployment_account_role)
    # Regions needs to know to organization ID for Bucket Policy
    regional_parameter_store.put_parameter("organization_id",
                                           os.environ['ORGANIZATION_ID'])
    # Regions needs to store its kms arn and s3 bucket in master and regional
    for key, value in cloudformation.get_stack_regional_outputs().items():
        LOGGER.info('Updating %s on deployment account in %s', key, region)
        deployment_account_parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region), value)
        regional_parameter_store.put_parameter(
            "/cross_region/{0}/{1}".format(key, region), value)
Beispiel #15
0
def prepare_deployment_account(sts, deployment_account_id, config):
    deployment_account_role = sts.assume_cross_account_role(
        'arn:aws:iam::{0}:role/{1}'.format(deployment_account_id,
                                           config.cross_account_access_role),
        'master')
    for region in list(
            set([config.deployment_account_region] + config.target_regions)):
        deployment_account_parameter_store = ParameterStore(
            region, deployment_account_role)
        deployment_account_parameter_store.put_parameter(
            'organization_id', os.environ["ORGANIZATION_ID"])

    deployment_account_parameter_store = ParameterStore(
        config.deployment_account_region, deployment_account_role)
    deployment_account_parameter_store.put_parameter(
        'cross_account_access_role', str(config.cross_account_access_role))
    deployment_account_parameter_store.put_parameter(
        'deployment_account_bucket', DEPLOYMENT_ACCOUNT_S3_BUCKET)
    return deployment_account_role
def prepare_deployment_account(sts, deployment_account_id, config):
    """
    Ensures configuration is up to date on the deployment account
    and returns the role that can be assumed by the master account
    to access the deployment account
    """
    deployment_account_role = sts.assume_cross_account_role(
        f'arn:{PARTITION}:iam::{deployment_account_id}:role/'
        f'{config.cross_account_access_role}', 'master')
    for region in list(
            set([config.deployment_account_region] + config.target_regions)):
        deployment_account_parameter_store = ParameterStore(
            region, deployment_account_role)
        deployment_account_parameter_store.put_parameter(
            'organization_id', os.environ["ORGANIZATION_ID"])

    deployment_account_parameter_store = ParameterStore(
        config.deployment_account_region, deployment_account_role)
    deployment_account_parameter_store.put_parameter('adf_version',
                                                     ADF_VERSION)
    deployment_account_parameter_store.put_parameter('adf_log_level',
                                                     ADF_LOG_LEVEL)
    deployment_account_parameter_store.put_parameter(
        'deployment_account_bucket', DEPLOYMENT_ACCOUNT_S3_BUCKET_NAME)
    deployment_account_parameter_store.put_parameter(
        'default_scm_branch',
        config.config.get('scm', {}).get(
            'default-scm-branch',
            ADF_DEFAULT_SCM_FALLBACK_BRANCH,
        ))
    auto_create_repositories = config.config.get(
        'scm', {}).get('auto-create-repositories')
    if auto_create_repositories is not None:
        deployment_account_parameter_store.put_parameter(
            'auto_create_repositories', str(auto_create_repositories))
    if '@' not in config.notification_endpoint:
        config.notification_channel = config.notification_endpoint
        config.notification_endpoint = (
            f"arn:{PARTITION}:lambda:{config.deployment_account_region}:"
            f"{deployment_account_id}:function:SendSlackNotification")
    for item in ('cross_account_access_role', 'notification_type',
                 'notification_endpoint', 'notification_channel'):
        if getattr(config, item) is not None:
            deployment_account_parameter_store.put_parameter(
                '/notification_endpoint/main'
                if item == 'notification_channel' else item,
                str(getattr(config, item)))

    return deployment_account_role
Beispiel #17
0
class Config:
    """Class used for modeling dfconfig and its properties
    """
    def __init__(self, parameter_store=None, config_path=None):
        self.parameters_client = parameter_store or ParameterStore(
            os.environ["AWS_REGION"], boto3)
        self.config_path = config_path or './adfconfig.yml'
        self.organization_id = os.environ["ORGANIZATION_ID"]
        self.client_deployment_region = None
        self.notification_type = None
        self.notification_endpoint = None
        self.config_contents = None
        self.config = None
        self.deployment_account_region = None
        self.notification_channel = None
        self.protected = None
        self.target_regions = None
        self.cross_account_access_role = None
        self._load_config_file()

    def store_config(self):
        self._store_config()
        self._store_cross_region_config()

    def _validate(self):
        """
        Validates the adfconfig.yml file
        """
        if None in (self.cross_account_access_role, self.config,
                    self.deployment_account_region, self.organization_id,
                    self.target_regions, self.config.get('moves'),
                    self.config.get('main-notification-endpoint')):
            raise InvalidConfigException(
                'adf_config.yml is missing required properties. '
                'Please see the documentation.')

        if not len(self.target_regions) >= 1:
            raise InvalidConfigException(
                'ADF requires you to have at least 1 target region '
                'for deployments')
        if isinstance(self.deployment_account_region, list):
            if len(self.deployment_account_region) > 1:
                raise InvalidConfigException(
                    'ADF currently only supports a single '
                    'Deployment Account region')
            [self.deployment_account_region] = self.deployment_account_region

        if not isinstance(self.target_regions, list):
            self.target_regions = [self.target_regions]

    def _load_config_file(self):
        """
        Loads the adfconfig.yml file and executes _parse_config
        """
        with open(self.config_path) as config:
            self.config_contents = yaml.load(config, Loader=yaml.FullLoader)
            self._parse_config()

    def _parse_config(self):
        """
        Parses the adfconfig.yml file and executes _validate
        """

        self.deployment_account_region = self.config_contents.get(
            'regions', None).get('deployment-account', None)
        self.target_regions = self.config_contents.get('regions', None).get(
            'targets', None)
        self.cross_account_access_role = self.config_contents.get(
            'roles', None).get('cross-account-access', None)
        self.config = self.config_contents.get('config', None)
        self.protected = self.config.get('protected', [])
        self.notification_type = 'lambda' if self.config.get(
            'main-notification-endpoint')[0].get(
                'type') == 'slack' else 'email'
        self.notification_endpoint = self.config.get(
            'main-notification-endpoint')[0].get('target')
        self.notification_channel = None if self.notification_type == 'email' else self.notification_endpoint

        self._validate()

    def _store_cross_region_config(self):
        """
        Stores cross_account_access_role Parameter
        in Parameter Store on the master account
        in deployment account main region.
        """
        self.client_deployment_region = ParameterStore(
            self.deployment_account_region, boto3)

        self.client_deployment_region.put_parameter(
            'cross_account_access_role', self.cross_account_access_role)

    def _store_config(self):
        """
        Stores the required configuration in Parameter Store on
        The master account in us-east-1.
        """
        for key, value in self.__dict__.items():
            if key not in ("client", "client_deployment_region",
                           "parameters_client", "config_contents",
                           "config_path", "notification_endpoint",
                           "notification_type"):
                self.parameters_client.put_parameter(key, str(value))
Beispiel #18
0
 def save_ssm(self, json_string):
     parameter_store = ParameterStore()
     resp = parameter_store.put_parameter(self.path, json_string)
     LOGGER.debug(resp)
     return resp