Esempio n. 1
0
    def last_operation(self, instance_id: str, operation_data: Optional[str],
                       **kwargs) -> LastOperation:
        """
        Further readings `CF Broker API#LastOperation
        <https://docs.cloudfoundry.org/services/api.html#polling>`_

        :param instance_id: Instance id provided by the platform
        :param operation_data: Operation data received from async operation
        :param kwargs: May contain additional information, improves
                       compatibility with upstream versions
        :rtype: LastOperation
        """

        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrInstanceDoesNotExist

        if not operation_data:
            raise errors.ErrBadRequest(msg="Missing operation ID")

        operation = instance.operations.filter_by(
            id=int(operation_data)).first()

        if not operation:
            raise errors.ErrBadRequest(
                msg=
                f"Invalid operation id {operation_data} for service {instance_id}"
            )

        return LastOperation(
            state=Operation.States(operation.state),
            description=operation.step_description,
        )
Esempio n. 2
0
    def deprovision(
        self,
        instance_id: str,
        details: DeprovisionDetails,
        async_allowed: bool,
        **kwargs,
    ) -> DeprovisionServiceSpec:
        if not async_allowed:
            raise errors.ErrAsyncRequired()
        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrInstanceDoesNotExist
        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.DEPROVISION.value,
        )

        db.session.add(operation)
        db.session.commit()
        queue_all_deprovision_tasks_for_operation(
            operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())

        return DeprovisionServiceSpec(is_async=True,
                                      operation=str(operation.id))
Esempio n. 3
0
    def provision(self, instance_id: str, details: ProvisionDetails,
                  async_allowed: bool, **kwargs) -> ProvisionedServiceSpec:
        self.logger.info("starting provision request")
        if not async_allowed:
            raise errors.ErrAsyncRequired()

        params = details.parameters or {}

        domain_names = parse_domain_options(params)
        if not domain_names:
            raise errors.ErrBadRequest("'domains' parameter required.")

        self.logger.info("validating CNAMEs")
        validators.CNAME(domain_names).validate()
        self.logger.info("validating unique domains")
        if not config.IGNORE_DUPLICATE_DOMAINS:
            validators.UniqueDomains(domain_names).validate()

        if details.plan_id == CDN_PLAN_ID:
            instance = provision_cdn_instance(instance_id, domain_names,
                                              params)
            queue = queue_all_cdn_provision_tasks_for_operation
        elif details.plan_id == ALB_PLAN_ID:
            instance = ALBServiceInstance(id=instance_id,
                                          domain_names=domain_names)
            queue = queue_all_alb_provision_tasks_for_operation
        elif details.plan_id == MIGRATION_PLAN_ID:
            instance = MigrationServiceInstance(id=instance_id,
                                                domain_names=domain_names)
            db.session.add(instance)
            db.session.commit()
            return ProvisionedServiceSpec(
                state=ProvisionState.SUCCESSFUL_CREATED)
        else:
            raise NotImplementedError()

        self.logger.info("setting origin hostname")
        self.logger.info("creating operation")

        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.PROVISION.value,
            step_description="Queuing tasks",
        )

        db.session.add(instance)
        db.session.add(operation)
        self.logger.info("committing db session")
        db.session.commit()
        self.logger.info("queueing tasks")
        queue(operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())
        self.logger.info("all done. Returning provisioned service spec")

        return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC,
                                      operation=str(operation.id))
Esempio n. 4
0
    def provision(self, instance_id: str, details: ProvisionDetails,
                  async_allowed: bool, **kwargs) -> ProvisionedServiceSpec:
        self.logger.info("starting provision request")
        if not async_allowed:
            raise errors.ErrAsyncRequired()

        params = details.parameters or {}

        if params.get("domains"):
            domain_names = [
                d.strip().lower() for d in params["domains"].split(",")
            ]
        else:
            raise errors.ErrBadRequest("'domains' parameter required.")

        self.logger.info("validating CNAMEs")
        validators.CNAME(domain_names).validate()
        self.logger.info("validating unique domains")
        validators.UniqueDomains(domain_names).validate()

        instance = ServiceInstance(id=instance_id, domain_names=domain_names)

        self.logger.info("setting origin hostname")
        instance.cloudfront_origin_hostname = params.get(
            "origin", config.DEFAULT_CLOUDFRONT_ORIGIN)
        instance.cloudfront_origin_path = params.get("path", "")
        self.logger.info("creating operation")

        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.PROVISION.value,
        )

        db.session.add(instance)
        db.session.add(operation)
        self.logger.info("committing db session")
        db.session.commit()
        self.logger.info("queueing tasks")
        queue_all_provision_tasks_for_operation(
            operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())
        self.logger.info("all done. Returning provisioned service spec")

        return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC,
                                      operation=str(operation.id))
Esempio n. 5
0
    def deprovision(
        self,
        instance_id: str,
        details: DeprovisionDetails,
        async_allowed: bool,
        **kwargs,
    ) -> DeprovisionServiceSpec:
        if not async_allowed:
            raise errors.ErrAsyncRequired()
        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrInstanceDoesNotExist
        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.DEPROVISION.value,
            step_description="Queuing tasks",
        )

        db.session.add(operation)
        db.session.commit()
        if details.plan_id == CDN_PLAN_ID:
            queue_all_cdn_deprovision_tasks_for_operation(
                operation.id,
                cf_logging.FRAMEWORK.context.get_correlation_id())
        elif details.plan_id == ALB_PLAN_ID:
            queue_all_alb_deprovision_tasks_for_operation(
                operation.id,
                cf_logging.FRAMEWORK.context.get_correlation_id())
        elif details.plan_id == MIGRATION_PLAN_ID:
            for o in instance.operations:
                if o.action == Operation.Actions.UPDATE.value:
                    raise errors.ErrBadRequest(
                        msg="Can't delete migration with update operations")
            queue_all_migration_deprovision_tasks_for_operation(
                operation.id,
                cf_logging.FRAMEWORK.context.get_correlation_id())
        else:
            raise NotImplementedError()

        return DeprovisionServiceSpec(is_async=True,
                                      operation=str(operation.id))
Esempio n. 6
0
def scan_for_expiring_certs():
    with huey.huey.flask_app.app_context():
        logger.info("Scanning for expired certificates")
        # TODO: skip SIs with active operations
        certificates = Certificate.query.filter(
            Certificate.expires_at - datetime.timedelta(days=30)
            < datetime.datetime.now()
        ).all()
        instances = [
            c.service_instance
            for c in certificates
            if not c.service_instance.deactivated_at
            and not c.service_instance.has_active_operations()
        ]
        cdn_renewals = []
        alb_renewals = []
        for instance in instances:
            if instance.has_active_operations():
                continue
            logger.info("Instance %s needs renewal", instance.id)
            renewal = Operation(
                state=Operation.States.IN_PROGRESS.value,
                service_instance=instance,
                action=Operation.Actions.RENEW.value,
                step_description="Queuing tasks",
            )
            db.session.add(renewal)
            if instance.instance_type == "cdn_service_instance":
                cdn_renewals.append(renewal)
            else:
                alb_renewals.append(renewal)
        db.session.commit()
        for renewal in cdn_renewals:
            queue_all_cdn_renewal_tasks_for_operation(renewal.id)
        for renewal in alb_renewals:
            queue_all_alb_renewal_tasks_for_operation(renewal.id)

        renew_instances = cdn_renewals + alb_renewals
        # n.b. this return is only for testing - huey ignores it.
        return [instance.service_instance_id for instance in renew_instances]
Esempio n. 7
0
    def update(self, instance_id: str, details: UpdateDetails,
               async_allowed: bool, **kwargs) -> UpdateServiceSpec:
        if not async_allowed:
            raise errors.ErrAsyncRequired()

        params = details.parameters or {}

        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrBadRequest("Service instance does not exist")

        if instance.deactivated_at:
            raise errors.ErrBadRequest(
                "Cannot update instance because it was already canceled")

        if instance.has_active_operations():
            raise errors.ErrBadRequest(
                "Instance has an active operation in progress")

        domain_names = parse_domain_options(params)
        noop = True
        if domain_names is not None:
            self.logger.info("validating CNAMEs")
            validators.CNAME(domain_names).validate()

            self.logger.info("validating unique domains")
            validators.UniqueDomains(domain_names).validate(instance)
            noop = noop and (sorted(domain_names) == sorted(
                instance.domain_names))
            if instance.instance_type == "cdn_service_instance" and noop:
                instance.new_certificate = instance.current_certificate
            instance.domain_names = domain_names

        if instance.instance_type == "cdn_service_instance":
            # N.B. we're using "param" in params rather than
            # params.get("param") because the OSBAPI spec
            # requires we do not mess with params that were not
            # specified, so unset and set to None have different meanings
            noop = False

            if details.plan_id != CDN_PLAN_ID:
                raise ClientError("Updating service plan is not supported")

            if "origin" in params:
                if params["origin"]:
                    origin_hostname = params["origin"]
                    validators.Hostname(origin_hostname).validate()
                else:
                    origin_hostname = config.DEFAULT_CLOUDFRONT_ORIGIN
                instance.cloudfront_origin_hostname = origin_hostname

            if "path" in params:
                if params["path"]:
                    cloudfront_origin_path = params["path"]
                else:
                    cloudfront_origin_path = ""
                instance.cloudfront_origin_path = cloudfront_origin_path
            if "forward_cookies" in params:
                forward_cookie_policy, forwarded_cookies = parse_cookie_options(
                    params)
                instance.forward_cookie_policy = forward_cookie_policy
                instance.forwarded_cookies = forwarded_cookies

            if "forward_headers" in params:
                forwarded_headers = parse_header_options(params)
            else:
                # .copy() so sqlalchemy recognizes the field has changed
                forwarded_headers = instance.forwarded_headers.copy()
            if instance.cloudfront_origin_hostname == config.DEFAULT_CLOUDFRONT_ORIGIN:
                forwarded_headers.append("HOST")
            forwarded_headers = normalize_header_list(forwarded_headers)
            instance.forwarded_headers = forwarded_headers
            if "insecure_origin" in params:
                origin_protocol_policy = "https-only"
                if params["insecure_origin"]:
                    if (instance.cloudfront_origin_hostname ==
                            config.DEFAULT_CLOUDFRONT_ORIGIN):
                        raise errors.ErrBadRequest(
                            "Cannot use insecure_origin with default origin")
                    origin_protocol_policy = "http-only"
                instance.origin_protocol_policy = origin_protocol_policy
            if "error_responses" in params:
                instance.error_responses = params["error_responses"]
                validators.ErrorResponseConfig(
                    instance.error_responses).validate()

            queue = queue_all_cdn_update_tasks_for_operation
        elif instance.instance_type == "alb_service_instance":
            if details.plan_id != ALB_PLAN_ID:
                raise ClientError("Updating service plan is not supported")
            queue = queue_all_alb_update_tasks_for_operation
        elif instance.instance_type == "migration_service_instance":
            if details.plan_id == CDN_PLAN_ID:
                noop = False
                validate_migration_to_cdn_params(params)
                instance = change_instance_type(instance, CDNServiceInstance,
                                                db.session)
                update_cdn_params_for_migration(instance, params)
                db.session.add(instance.current_certificate)
                queue = queue_all_cdn_broker_migration_tasks_for_operation
            elif details.plan_id == ALB_PLAN_ID:
                noop = False
                validate_migration_to_alb_params(params)
                instance = change_instance_type(instance, ALBServiceInstance,
                                                db.session)
                update_alb_params_for_migration(instance, params)
                db.session.add(instance.current_certificate)
                queue = queue_all_domain_broker_migration_tasks_for_operation
            else:
                raise ClientError(
                    "Updating to this service plan is not supported")
        if noop:
            return UpdateServiceSpec(False)

        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.UPDATE.value,
            step_description="Queuing tasks",
        )
        db.session.add(operation)
        db.session.add(instance)
        db.session.commit()

        queue(operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())

        return UpdateServiceSpec(True, operation=str(operation.id))