Exemple #1
0
def generate_aws_account(arn=None, aws_account_id=None, user=None, name=None):
    """
    Generate an AwsAccount for testing.

    Any optional arguments not provided will be randomly generated.

    Args:
        arn (str): Optional ARN.
        aws_account_id (12-digit string): Optional AWS account ID.
        user (User): Optional Django auth User to be this account's owner.
        name (str): Optional name for this account.

    Returns:
        AwsAccount: The created AwsAccount.

    """
    if arn is None:
        arn = helper.generate_dummy_arn(account_id=aws_account_id)

    if user is None:
        user = helper.generate_test_user()

    return AwsAccount.objects.create(
        account_arn=arn,
        aws_account_id=aws.AwsArn(arn).account_id,
        user=user,
        name=name,
    )
Exemple #2
0
 def validate_account_arn(self, value):
     """Validate the input account_arn."""
     if self.instance is not None and value != self.instance.account_arn:
         raise serializers.ValidationError(
             _('You cannot change this field.'))
     try:
         aws.AwsArn(value)
     except InvalidArn:
         raise serializers.ValidationError(_('Invalid ARN.'))
     return value
Exemple #3
0
 def validate_account_arn(self, value):
     """Validate the input account_arn."""
     if (
         self.instance is not None
         and value != self.instance.content_object.account_arn
     ):
         raise ValidationError(_("You cannot update account_arn."))
     try:
         aws.AwsArn(value)
     except InvalidArn:
         raise ValidationError(_("Invalid ARN."))
     return value
Exemple #4
0
def configure_customer_aws_and_create_cloud_account(username, customer_arn,
                                                    authentication_id,
                                                    application_id, source_id):
    """
    Configure the customer's AWS account and create our CloudAccount.

    This function is decorated to retry if an unhandled `RuntimeError` is
    raised, which is the exception we raise in `rewrap_aws_errors` if we
    encounter an unexpected error from AWS. This means it should keep retrying
    if AWS is misbehaving.

    Args:
        username (string): Username of the user that will own the new cloud account
        customer_arn (str): customer's ARN
        authentication_id (str): Platform Sources' Authentication object id
        application_id (str): Platform Sources' Application object id
        source_id (str): Platform Sources' Source object id
    """
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        error = error_codes.CG1000
        error.log_internal_message(logger, {
            "application_id": application_id,
            "username": username
        })
        error.notify(username, application_id)
        return
    try:
        customer_aws_account_id = aws.AwsArn(customer_arn).account_id
    except InvalidArn:
        error = error_codes.CG1004
        error.log_internal_message(logger, {"application_id": application_id})
        error.notify(username, application_id)
        return

    cloud_account_name = get_standard_cloud_account_name(
        "aws", customer_aws_account_id)
    try:
        create_aws_cloud_account(
            user,
            customer_arn,
            cloud_account_name,
            authentication_id,
            application_id,
            source_id,
        )
    except ValidationError as e:
        logger.info("Unable to create cloud account: error %s", e.detail)
Exemple #5
0
def validate_redrive_policy(source_queue_name, redrive_policy):
    """
    Validate the queue redrive policy has an accessible DLQ.

    Args:
        source_queue_name (str): the queue name that should have a DLQ
        redrive_policy (dict): the redrive policy

    Returns:
        bool: True if policy appears to be valid, else False.

    """
    logger.info(
        'SQS queue "%(queue)s" already has a redrive policy: '
        "%(policy)s",
        {
            "queue": source_queue_name,
            "policy": redrive_policy
        },
    )

    dlq_queue_arn = redrive_policy.get("deadLetterTargetArn")

    if not dlq_queue_arn:
        return False

    try:
        dlq_queue_name = aws.AwsArn(dlq_queue_arn).resource
    except InvalidArn:
        return False

    try:
        region = settings.SQS_DEFAULT_REGION
        sqs = boto3.client("sqs", region_name=region)
        sqs.get_queue_url(QueueName=dlq_queue_name)["QueueUrl"]
        queue_exists = True
    except ClientError as e:
        if e.response["Error"]["Code"].endswith(".NonExistentQueue"):
            queue_exists = False
        else:
            raise

    return queue_exists
Exemple #6
0
 def create(self, validated_data):
     """Create an AwsAccount."""
     arn = aws.AwsArn(validated_data['account_arn'])
     aws_account_id = arn.account_id
     user = self.context['request'].user
     account = AwsAccount(
         account_arn=str(arn),
         aws_account_id=aws_account_id,
         user=user,
     )
     try:
         session = aws.get_session(str(arn))
     except ClientError as error:
         if error.response.get('Error', {}).get('Code') == 'AccessDenied':
             raise serializers.ValidationError(
                 detail={
                     'account_arn':
                     [_('Permission denied for ARN "{0}"').format(arn)]
                 })
         raise
     account_verified, failed_actions = aws.verify_account_access(session)
     if account_verified:
         instances_data = aws.get_running_instances(session)
         with transaction.atomic():
             account.save()
             new_amis = create_new_machine_images(account, instances_data)
             create_initial_aws_instance_events(account, instances_data)
         messages = generate_aws_ami_messages(instances_data, new_amis)
         for message in messages:
             copy_ami_snapshot.delay(str(arn), message['image_id'],
                                     message['region'])
     else:
         failure_details = [_('Account verification failed.')]
         failure_details += [
             _('Access denied for policy action "{0}".').format(action)
             for action in failed_actions
         ]
         raise serializers.ValidationError(
             detail={'account_arn': failure_details})
     return account
Exemple #7
0
    def create(self, validated_data):
        """Create an AwsAccount."""
        arn = aws.AwsArn(validated_data['account_arn'])
        aws_account_id = arn.account_id

        account = AwsAccount.objects.filter(
            aws_account_id=aws_account_id).first()
        if account is not None:
            raise serializers.ValidationError(
                detail={
                    'account_arn': [
                        _('An ARN already exists for account "{0}"').format(
                            aws_account_id)
                    ]
                })

        user = self.context['request'].user
        name = validated_data.get('name')
        account = AwsAccount(
            account_arn=str(arn),
            aws_account_id=aws_account_id,
            name=name,
            user=user,
        )
        try:
            session = aws.get_session(str(arn))
        except ClientError as error:
            if error.response.get('Error', {}).get('Code') == 'AccessDenied':
                raise serializers.ValidationError(
                    detail={
                        'account_arn':
                        [_('Permission denied for ARN "{0}"').format(arn)]
                    })
            raise
        account_verified, failed_actions = aws.verify_account_access(session)
        if account_verified:
            instances_data = aws.get_running_instances(session)
            with transaction.atomic():
                account.save()
                try:
                    aws.configure_cloudtrail(session, aws_account_id)
                except ClientError as error:
                    if error.response.get('Error', {}).get('Code') == \
                            'AccessDeniedException':
                        raise serializers.ValidationError(
                            detail={
                                'account_arn': [
                                    _('Access denied to create CloudTrail for '
                                      'ARN "{0}"').format(arn)
                                ]
                            })
                    raise
                new_amis = create_new_machine_images(account, instances_data)
                create_initial_aws_instance_events(account, instances_data)
            messages = generate_aws_ami_messages(instances_data, new_amis)
            for message in messages:
                image = start_image_inspection(str(arn), message['image_id'],
                                               message['region'])
                self.add_openshift_tag(session, message['image_id'],
                                       message['region'], image)
        else:
            failure_details = [_('Account verification failed.')]
            failure_details += [
                _('Access denied for policy action "{0}".').format(action)
                for action in failed_actions
            ]
            raise serializers.ValidationError(
                detail={'account_arn': failure_details})
        return account
Exemple #8
0
 def test_generate_dummy_arn_random_account_id(self):
     """Assert generation of an ARN without a specified account ID."""
     arn = helper.generate_dummy_arn(generate_account_id=True)
     account_id = aws.AwsArn(arn).account_id
     self.assertIn(account_id, arn)
Exemple #9
0
def update_aws_cloud_account(
    cloud_account,
    customer_arn,
    account_number,
    authentication_id,
    source_id,
):
    """
    Update aws_cloud_account with the new arn.

    Args:
        cloud_account (api.models.CloudAccount)
        customer_arn (str): customer's ARN
        account_number (str): customer's account number
        authentication_id (str): Platform Sources' Authentication object id
        source_id (str): Platform Sources' Source object id
    """
    logger.info(
        _("Updating an AwsCloudAccount. "
          "cloud_account=%(cloud_account)s, "
          "customer_arn=%(customer_arn)s, "
          "account_number=%(account_number)s, "
          "authentication_id=%(authentication_id)s, "
          "source_id=%(source_id)s"),
        {
            "cloud_account": cloud_account,
            "customer_arn": customer_arn,
            "account_number": account_number,
            "authentication_id": authentication_id,
            "source_id": source_id,
        },
    )
    application_id = cloud_account.platform_application_id

    try:
        customer_aws_account_id = aws.AwsArn(customer_arn).account_id
    except InvalidArn:
        error = error_codes.CG1004
        error.log_internal_message(logger, {"application_id": application_id})
        error.notify(account_number, application_id)
        return

    # If the aws_account_id is different, then we disable the account,
    # delete all related instances, and then enable the account.
    # Otherwise just update the account_arn.
    if cloud_account.content_object.aws_account_id != customer_aws_account_id:
        logger.info(
            _("Cloud Account with ID %(clount_id)s and aws_account_id "
              "%(old_aws_account_id)s has received an update request for ARN "
              "%(new_arn)s and aws_account_id %(new_aws_account_id)s. "
              "Since the aws_account_id is different, Cloud Account ID "
              "%(clount_id)s will be deleted. A new Cloud Account will be created "
              "with aws_account_id %(new_aws_account_id)s and arn %(new_arn)s."
              ),
            {
                "clount_id": cloud_account.id,
                "old_aws_account_id":
                cloud_account.content_object.aws_account_id,
                "new_aws_account_id": customer_aws_account_id,
                "new_arn": customer_arn,
            },
        )

        cloud_account.disable(power_off_instances=False)

        # Remove instances associated with the clount
        Instance.objects.filter(cloud_account=cloud_account).delete()

        try:
            customer_aws_account_id = aws.AwsArn(customer_arn).account_id
        except InvalidArn:
            error = error_codes.CG1004
            error.log_internal_message(logger,
                                       {"application_id": application_id})
            error.notify(account_number, application_id)
            return

        # Verify that no AwsCloudAccount already exists with the same ARN.
        if AwsCloudAccount.objects.filter(account_arn=customer_arn).exists():
            error_code = error_codes.CG1001
            existing_user_id = (AwsCloudAccount.objects.get(
                account_arn=customer_arn).cloud_account.get().user.id)
            # If the CloudAccount with the duplicate ARN belongs to the same user,
            # we want to give the error code in addition to the generic message
            _notify_error_with_generic_message_for_different_user(
                error_code,
                existing_user_id,
                account_number,
                application_id,
                customer_arn,
            )
            return

        # Verify that no AwsCloudAccount already exists with the same AWS Account ID.
        if AwsCloudAccount.objects.filter(
                aws_account_id=customer_aws_account_id).exists():
            error_code = error_codes.CG1002
            existing_user_id = (AwsCloudAccount.objects.get(
                aws_account_id=customer_aws_account_id).cloud_account.get().
                                user.id)
            _notify_error_with_generic_message_for_different_user(
                error_code,
                existing_user_id,
                account_number,
                application_id,
                customer_arn,
            )
            return
        cloud_account.content_object.account_arn = customer_arn
        cloud_account.content_object.aws_account_id = customer_aws_account_id
        cloud_account.content_object.save()

        cloud_account.enable()

    else:
        try:
            cloud_account.content_object.account_arn = customer_arn
            cloud_account.content_object.save()
            verify_permissions(customer_arn)
            cloud_account.enable()
        except ValidationError as e:
            logger.info(
                _("ARN %s failed validation. The Cloud Account will still be updated."
                  ),
                customer_arn,
            )
            # Tell the cloud account why we're disabling it
            cloud_account.disable(message=str(e.detail))

        logger.info(
            _("Cloud Account with ID %s has been updated with arn %s. "),
            cloud_account.id,
            customer_arn,
        )
Exemple #10
0
def create_aws_cloud_account(
    user,
    customer_role_arn,
    cloud_account_name,
    platform_authentication_id,
    platform_application_id,
    platform_source_id,
):
    """
    Create AwsCloudAccount for the customer user.

    This function may raise ValidationError if certain verification steps fail.

    We call CloudAccount.enable after creating it, and that effectively verifies AWS
    permission and configures CloudTrail. If that fails, we must abort this creation.
    That is why we put almost everything here in a transaction.atomic() context.

    Args:
        user (django.contrib.auth.models.User): user to own the CloudAccount
        customer_role_arn (str): ARN to access the customer's AWS account
        cloud_account_name (str): the name to use for our CloudAccount
        platform_authentication_id (str): Platform Sources' Authentication object id
        platform_application_id (str): Platform Sources' Application object id
        platform_source_id (str): Platform Sources' Source object id

    Returns:
        CloudAccount the created cloud account.

    """
    logger.info(
        _("Creating an AwsCloudAccount. "
          "user=%(user)s, "
          "customer_role_arn=%(customer_role_arn)s, "
          "cloud_account_name=%(cloud_account_name)s, "
          "platform_authentication_id=%(platform_authentication_id)s, "
          "platform_application_id=%(platform_application_id)s, "
          "platform_source_id=%(platform_source_id)s"),
        {
            "user": user.username,
            "customer_role_arn": customer_role_arn,
            "cloud_account_name": cloud_account_name,
            "platform_authentication_id": platform_authentication_id,
            "platform_application_id": platform_application_id,
            "platform_source_id": platform_source_id,
        },
    )
    aws_account_id = aws.AwsArn(customer_role_arn).account_id
    arn_str = str(customer_role_arn)

    with transaction.atomic():
        # Verify that no AwsCloudAccount already exists with the same ARN.
        if AwsCloudAccount.objects.filter(account_arn=arn_str).exists():
            error_code = error_codes.CG1001
            existing_user_id = (AwsCloudAccount.objects.get(
                account_arn=arn_str).cloud_account.get().user.id)
            # If the CloudAccount with the duplicate ARN belongs to the same user,
            # we want to give the error code in addition to the generic message
            error_message = _notify_error_with_generic_message_for_different_user(
                error_code, existing_user_id, user, platform_application_id,
                arn_str)
            raise ValidationError({"account_arn": error_message})

        # Verify that no AwsCloudAccount already exists with the same AWS Account ID.
        if AwsCloudAccount.objects.filter(
                aws_account_id=aws_account_id).exists():
            error_code = error_codes.CG1002
            existing_user_id = (AwsCloudAccount.objects.get(
                aws_account_id=aws_account_id).cloud_account.get().user.id)
            error_message = _notify_error_with_generic_message_for_different_user(
                error_code, existing_user_id, user, platform_application_id,
                arn_str)
            raise ValidationError({"account_arn": error_message})

        # Verify that no CloudAccount exists with the same name.
        if CloudAccount.objects.filter(user=user,
                                       name=cloud_account_name).exists():
            error_code = error_codes.CG1003
            error_code.log_internal_message(
                logger,
                {
                    "application_id": platform_application_id,
                    "name": cloud_account_name,
                },
            )
            error_code.notify(user.username, platform_application_id)
            raise ValidationError({"name": error_code.get_message()})

        try:
            # Use get_or_create here in case there is another task running concurrently
            # that created the AwsCloudAccount at the same time.
            aws_cloud_account, created = AwsCloudAccount.objects.get_or_create(
                aws_account_id=aws_account_id, account_arn=arn_str)
        except IntegrityError:
            # get_or_create can throw integrity error in the case that
            # aws_account_id xor arn already exists in an account.
            error_code = error_codes.CG1002
            existing_user_id = (AwsCloudAccount.objects.get(
                aws_account_id=aws_account_id).cloud_account.get().user.id)
            error_message = _notify_error_with_generic_message_for_different_user(
                error_code, existing_user_id, user, platform_application_id,
                arn_str)
            raise ValidationError({"account_arn": error_message})

        if not created:
            # If aws_account_id and arn already exist in an account because a
            # another task created it, notify the user.
            error_code = error_codes.CG1002
            existing_user_id = (AwsCloudAccount.objects.get(
                aws_account_id=aws_account_id).cloud_account.get().user.id)
            error_message = _notify_error_with_generic_message_for_different_user(
                error_code, existing_user_id, user, platform_application_id,
                arn_str)
            raise ValidationError({"account_arn": error_message})

        cloud_account = CloudAccount.objects.create(
            user=user,
            name=cloud_account_name,
            content_object=aws_cloud_account,
            platform_application_id=platform_application_id,
            platform_authentication_id=platform_authentication_id,
            platform_source_id=platform_source_id,
        )

        # This enable call *must* be inside the transaction because we need to
        # know to rollback the transaction if anything related to enabling fails.
        # Yes, this means holding the transaction open while we wait on calls
        # to AWS.
        if cloud_account.enable() is False:
            # Enabling of cloud account failed, rolling back.
            transaction.set_rollback(True)
            raise ValidationError({
                "is_enabled":
                "Could not enable cloud account. "
                "Please check your credentials."
            })

    return cloud_account
Exemple #11
0
def verify_permissions(customer_role_arn):
    """
    Verify AWS permissions.

    This function may raise ValidationError if certain verification steps fail.

    Args:
        customer_role_arn (str): ARN to access the customer's AWS account

    Note:
        This function not only verifies; it also has the side effect of configuring
        the AWS CloudTrail. This should be refactored into a more explicit operation,
        but at the time of this writing, there is no "dry run" check for CloudTrail
        operations. Callers should be aware of the risk that we may configure CloudTrail
        but somewhere else rollback our transaction, leaving that Trail orphaned.

    Returns:
        boolean indicating if the arn being verified is good.

    """
    aws_account_id = aws.AwsArn(customer_role_arn).account_id
    arn_str = str(customer_role_arn)

    try:
        session = aws.get_session(arn_str)
        account_verified, failed_actions = aws.verify_account_access(session)
    except ClientError as error:
        if error.response.get("Error", {}).get("Code") in (
                "AccessDenied",
                "AccessDeniedException",
                "UnrecognizedClientException",
        ):
            raise ValidationError(
                detail={
                    "account_arn":
                    [_('Permission denied for ARN "{0}"').format(arn_str)]
                })
        raise
    if account_verified:
        try:
            aws.configure_cloudtrail(session, aws_account_id)
        except ClientError as error:
            if error.response.get("Error", {}).get("Code") in (
                    "AccessDenied",
                    "AccessDeniedException",
                    "UnrecognizedClientException",
            ):
                logger.debug(_("Trying to throw a CG3000."))
                cloud_account = CloudAccount.objects.get(
                    aws_cloud_account__aws_account_id=aws_account_id)

                error_code = error_codes.CG3000
                error_code.log_internal_message(logger, {
                    "cloud_account_id": aws_account_id,
                    "exception": error
                })
                error_code.notify(cloud_account.user.username,
                                  cloud_account.platform_application_id)
                logger.debug(
                    _("CG3000 notify called, raising ValidationError."))

                raise ValidationError(
                    detail={
                        "account_arn": [
                            _("Access denied to create CloudTrail for "
                              'ARN "{0}"').format(arn_str)
                        ]
                    })
            raise
        except MaximumNumberOfTrailsExceededException as error:
            logger.debug(_("Trying to throw a CG3001."))
            cloud_account = CloudAccount.objects.get(
                aws_cloud_account__account_arn=arn_str)

            error_code = error_codes.CG3001
            error_code.log_internal_message(logger, {
                "cloud_account_id": cloud_account.id,
                "exception": error
            })
            error_code.notify(cloud_account.user.username,
                              cloud_account.platform_application_id)
            logger.debug(_("CG3001 notify called, raising ValidationError."))
            raise ValidationError(
                detail={"account_arn": error_code.get_message()})

    else:
        failure_details = [_("Account verification failed.")]
        failure_details += [
            _('Access denied for policy action "{0}".').format(action)
            for action in failed_actions
        ]
        raise ValidationError(detail={"account_arn": failure_details})
    return account_verified
Exemple #12
0
def generate_cloud_account(  # noqa: C901
    arn=None,
    aws_account_id=None,
    user=None,
    name=None,
    created_at=None,
    platform_authentication_id=None,
    platform_application_id=None,
    platform_source_id=None,
    is_enabled=True,
    enabled_at=None,
    verify_task=None,
    generate_verify_task=True,
    cloud_type=AWS_PROVIDER_STRING,
    azure_subscription_id=None,
    azure_tenant_id=None,
):
    """
    Generate an CloudAccount for testing.

    Any optional arguments not provided will be randomly generated.

    Args:
        arn (str): Optional ARN.
        aws_account_id (12-digit string): Optional AWS account ID.
        user (User): Optional Django auth User to be this account's owner.
        name (str): Optional name for this account.
        created_at (datetime): Optional creation datetime for this account.
        platform_authentication_id (int): Optional platform source authentication ID.
        platform_application_id (int): Optional platform source application ID.
        platform_source_id (int): Optional platform source source ID.
        is_enabled (bool): Optional should the account be enabled.
        enabled_at (datetime): Optional enabled datetime for this account.
        verify_task (PeriodicTask): Optional Celery verify task for this account.
        generate_verify_task (bool): Optional should a verify_task be generated here.
        cloud_type (str): Str denoting cloud type, defaults to "aws"
        azure_subscription_id (str): optional uuid str for azure subscription id
        azure_tenant_id (str): optional uuid str for azure tenant id

    Returns:
        CloudAccount: The created Cloud Account.

    """
    if user is None:
        user = helper.generate_test_user()

    if name is None:
        name = str(uuid.uuid4())

    if created_at is None:
        created_at = get_now()

    if enabled_at is None:
        enabled_at = created_at

    if platform_authentication_id is None:
        platform_authentication_id = _faker.pyint()

    if platform_application_id is None:
        platform_application_id = _faker.pyint()

    if platform_source_id is None:
        platform_source_id = _faker.pyint()

    if cloud_type == AZURE_PROVIDER_STRING:
        if azure_subscription_id is None:
            azure_subscription_id = uuid.uuid4()

        if azure_tenant_id is None:
            azure_tenant_id = uuid.uuid4()
        cloud_provider_account = AzureCloudAccount.objects.create(
            subscription_id=azure_subscription_id, tenant_id=azure_tenant_id)

    # default to AWS
    else:
        if arn is None:
            arn = helper.generate_dummy_arn(account_id=aws_account_id)

        if verify_task is None and generate_verify_task:
            schedule, _ = IntervalSchedule.objects.get_or_create(
                every=settings.SCHEDULE_VERIFY_VERIFY_TASKS_INTERVAL,
                period=IntervalSchedule.SECONDS,
            )
            verify_task, _ = PeriodicTask.objects.get_or_create(
                interval=schedule,
                name=f"Verify {arn}.",
                task="api.clouds.aws.tasks.verify_account_permissions",
                kwargs=json.dumps({
                    "account_arn": arn,
                }),
                defaults={"start_time": created_at},
            )

        cloud_provider_account = AwsCloudAccount.objects.create(
            account_arn=arn,
            aws_account_id=aws.AwsArn(arn).account_id,
            verify_task=verify_task,
        )

    cloud_provider_account.created_at = created_at
    cloud_provider_account.save()

    cloud_account = CloudAccount.objects.create(
        user=user,
        name=name,
        content_object=cloud_provider_account,
        platform_authentication_id=platform_authentication_id,
        platform_application_id=platform_application_id,
        platform_source_id=platform_source_id,
        is_enabled=is_enabled,
    )
    cloud_account.created_at = created_at
    cloud_account.save()
    if enabled_at:
        cloud_account.enabled_at = enabled_at
        cloud_account.save()

    return cloud_account