Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SAMLIdentityProvider custom resource is broken #567

Open
devvick opened this issue Jan 9, 2025 · 4 comments
Open

SAMLIdentityProvider custom resource is broken #567

devvick opened this issue Jan 9, 2025 · 4 comments
Labels
bug Something isn't working

Comments

@devvick
Copy link

devvick commented Jan 9, 2025

Describe the bug
I cannot deploy the solution using IdentityType: "SAML"
The identity_provider.py function fails because it's missing the ClientSecretArn from custom resource properties.

[ERROR]	2025-01-09T09:29:13.969Z	2da03583-6ebc-4030-a7c1-75591f68a068	'ClientSecretArn'
Traceback (most recent call last):
  File "/opt/python/crhelper/resource_helper.py", line 204, in _wrap_function
    self.PhysicalResourceId = func(self._event, self._context) if func else ''
                              ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/task/identity_provider.py", line 47, in create
    client_secret_arn = props['ClientSecretArn']
                        ~~~~~^^^^^^^^^^^^^^^^^^^
KeyError: 'ClientSecretArn'

To Reproduce
Steps to reproduce the behavior:

  1. First deploy of the solution.
  2. Set IdentityType parameter to "SAML" (and other Cognito and SAML parameters of the template)
  3. Don't provide Google or OIDC optional template parameters.
  4. Cognito nested stack fails to create because of the above error.

Additional context
I wanted to setup SAML using AzureAD on the first deploy.

@devvick devvick added the bug Something isn't working label Jan 9, 2025
@svozza
Copy link
Contributor

svozza commented Jan 9, 2025

Yeah, this looks like a bug. I will investigate further and report back if I can give you a workaround so you don't have to wait on a patch.

@svozza
Copy link
Contributor

svozza commented Jan 15, 2025

I am not sure if this will work because of the way CFN rollbacks work but it's worth a go:

  1. Deploy the Workload Discovery CFN template and ensure on the Configure stack options page, under Stack failure options, that the Behavior on provisioning failure radio button is set to Preserve successfully provisioned resources.
  2. When the deployment fails, go to the lambda console and find the identity provider lambda. It will have a name with a pattern similar to this: <stack-name>-Cognit-IdentityProviderCrFuncti-<ID-string>.
  3. Replace all the code in the identity_provider.py file with the following:
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import boto3
import json
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities import parameters
from crhelper import CfnResource
from typing import TypedDict, Optional


logger = Logger(service='IdentityProviderCustomResource')


helper = CfnResource(json_logging=False, log_level='INFO',
                     boto_level='CRITICAL')

cognito_client = boto3.client('cognito-idp')

ssm_provider = parameters.SecretsProvider()


class IdentityProviderProperties(TypedDict):
    UserPoolId: str
    ProviderName: str
    ProviderType: str
    ProviderDetails: dict
    AttributeMapping: str
    IdpIdentifiers: Optional[list[str]]


class Event(TypedDict):
    RequestType: str
    ResponseURL: str
    StackId: str
    RequestId: str
    ResourceType: str
    LogicalResourceId: str
    ResourceProperties: IdentityProviderProperties


@helper.create
def create(event: Event, _) -> None:
    logger.info('Creating identity provider')
    props: IdentityProviderProperties = event['ResourceProperties']
    provider_name = props['ProviderName']
    provider_type = props['ProviderType']
    attribute_mappings = json.loads(props['AttributeMapping'])

    if provider_type == 'SAML':
        provider_details = props['ProviderDetails']
    else:
        client_secret_arn = props['ClientSecretArn']
        client_secret = ssm_provider.get(client_secret_arn)
        provider_details = props['ProviderDetails'] | {'client_secret': client_secret}

    resp = cognito_client.create_identity_provider(
        UserPoolId=props['UserPoolId'],
        ProviderName=provider_name,
        ProviderType=props['ProviderType'],
        ProviderDetails=provider_details,
        AttributeMapping=attribute_mappings,
        IdpIdentifiers=props['IdpIdentifiers']
    )

    logger.info('Identity provider created')
    logger.info(resp['IdentityProvider'])

    helper.Data.update({'ProviderName': provider_name})


@helper.update
def update(event: Event, _) -> None:
    logger.info('Updating identity provider')
    props: IdentityProviderProperties = event['ResourceProperties']
    provider_name = props['ProviderName']
    provider_type = props['ProviderType']
    attribute_mappings = json.loads(props['AttributeMapping'])

    if provider_type == 'SAML':
        provider_details = props['ProviderDetails']
    else:
        client_secret_arn = props['ClientSecretArn']
        client_secret = ssm_provider.get(client_secret_arn)
        provider_details = props['ProviderDetails'] | {'client_secret': client_secret}

    resp = cognito_client.update_identity_provider(
        UserPoolId=props['UserPoolId'],
        ProviderName=provider_name,
        ProviderDetails=provider_details,
        AttributeMapping=attribute_mappings,
        IdpIdentifiers=props['IdpIdentifiers']
    )

    logger.info('Identity provider updated.')
    logger.info(resp['IdentityProvider'])

    helper.Data.update({'ProviderName': provider_name})


@helper.delete
def delete(event: Event, _) -> None:
    logger.info('Deleting identity provider')
    props: IdentityProviderProperties = event['ResourceProperties']

    cognito_client.delete_identity_provider(
        UserPoolId=props['UserPoolId'],
        ProviderName=props['ProviderName']
    )

    logger.info('Identity provider deleted.')


@logger.inject_lambda_context
def handler(event, _) -> None:
    helper(event, _)
  1. Go back to the main stack that should be in a failed state and select the Retry option.

If this workaround doesn't work, you can send me an email to my GitHub username at amazon dot com and we'll try to work something out.

@svozza
Copy link
Contributor

svozza commented Jan 17, 2025

The fix for this turned out to be more involved than just changing the lambda unfortunately so the above workaround won't work.

@devvick
Copy link
Author

devvick commented Jan 19, 2025

I hadn't had time to look over this yet. It's not a big priority for me right now, so I'm content to wait. If I get any free time, I'll try looking for a solution too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants