2021. november 11., csütörtök

Terraform, Cloudformation és Cognito

Némi IT és némi felhő újra

IaC eszközökkel dolgozom már nem is igazán tudom mióta. Az AWS CloudFormation-nel kezdtem valamikor 2014 körül (nem biztos, ez a ködös múltba vész)


Amikor elkezdtem a Terraformról tanulni, bennem volt a félelem, hogy nem fogok teljes AWS funkcionalitást kapni. A CloudFormation natív AWS eszköz. Valami amit egy külső gyártó fejleszt - elméletben - csak követheti a szolgáltató natív eszközét.

Elkezdtem Terraformot használni, mert ez volt az ügyfél elvárása, tehát a váltás nem az én döntésem volt. A fenti félelmem akkor vált kicsit valósággá amikor az ügyfél nem követte az AWS Provider verzióit megfelelően a saját deployment agent-jein (tehát ez nem róható fel sem a HashiCorpnak, sem a Terraform közösségnek). Tehát tulajdonképpen nem látszott valós lemaradás a Terraform oldalán. Emellett Terraform kódot írni sokkal kényelmesebb, sokkal több lehetőséget nyújt amikor feldolgozni, konvertálni kell, a rendelkezésre álló információt.

Egy pár hete, kb. három év kizárólagos Terraform munka után belecsöppentem újra egy CloudFormation projectbe.

Van egy - azt hittem - szuper egyszerű feladat. Publikálni egy Cognito User Pool Client titkos kulcsot az SSM Parameter Store-ba, titkosítva.

Terraformban ez így néz ki:

// Create Cognito User Pool
resource "aws_cognito_user_pool" "pool" {
  name = "pool"
}

// Create Cognito User Pool Client
resource "aws_cognito_user_pool_client" "client" {
  name = "client"
  user_pool_id = aws_cognito_user_pool.pool.id
  generate_secret     = true
}

// Publish to SSM Parameter Store
resource "aws_ssm_parameter" "secret" {
  name        = "/production/cognito/clientSecret"
  type        = "SecureString"
  value       = ${aws_cognito_user_pool_client.client.client_secret}
}

És kész is vagyunk.

Próbáljuk meg ugyanezt a CloudFormation-ben.

Vajon a AWS::Cognito::UserPoolClient objektum, ki tudja-e exportálni a titkos kulcsot? Nem. Forrás: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html

Vajon a AWS::SSM::Parameter objektum tud-e SecureString értéket létrehozni? Nem. Forrás: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-parameter.html

Itt van ahol a fenti teóriám "A CloudFormation-nek előrébb kell járnia mint a Terraformnak" megbukik.

Sok mindennel próbálkoztam sikertelenül, hogy megoldjam ezt. Végülis a következő kódrészlet működött (egy megjegyzés: a Python kódban, az import cfnresponse nem az én trehányságom miatt van külön sorban. Ez kell az AWS lambdának, hogy észrevegye, kell neki a cfnresponse.py és belerakja a deployment során a zip fájlba, ugyanis nem része a pip repónak):

Resources:
  # Create Cognito User Pool
  UserPool:
    Type: "AWS::Cognito::UserPool"
    Properties:
      UserPoolName: pool

  # Create Cognito User Pool Client
  UserPoolClient:
    Type: "AWS::Cognito::UserPoolClient"
    Properties:
      AccessTokenValidity: 15
      AllowedOAuthFlowsUserPoolClient: false
      ClientName: client
      GenerateSecret: true

  # You need a lambda to
  #     - read from the cognito user pool client
  #     - write to the parameter store
  #     - report back to the CloudFormation when finished
  CognitoSecretExporterLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: CognitoSecretExporter
      Runtime: python3.9
      Role: !GetAtt CognitoSecretExporterExecutionRole.Arn
      Handler: index.handler
      Timeout: 30
      Environment:
        Variables:
          CognitoUserPoolId: !Ref UserPool
          CognitoUserPoolClientId: !Ref UserPoolClient
          ssmParameterName: /production/cognito/ClientSecret
      Code:
        ZipFile: |
          import boto3, os
          import cfnresponse

          def handler(event, context):
              CognitoUserPoolId = os.environ['CognitoUserPoolId']
              CognitoUserPoolClientId = os.environ['CognitoUserPoolClientId']
              ssmParameterName = os.environ['ssmParameterName']

              # Read Cognito
              cognito = boto3.client('cognito-idp')
              response = cognito.describe_user_pool_client(
                  UserPoolId = CognitoUserPoolId,
                  ClientId = CognitoUserPoolClientId
              )
              cognitoClientSecret = response['UserPoolClient']['ClientSecret']
              print(cognitoClientSecret)
              ssm = boto3.client('ssm')

              # Write to parameter store
              response = ssm.put_parameter(
                  Name = ssmParameterName,
                  Description = '[CF] Cognito Client Secret used by the WebApp',
                  Value = cognitoClientSecret,
                  Type = 'SecureString',
                  KeyId = 'alias/aws/ssm',
                  Overwrite = True,
                  Tier='Standard'
              )

              # Report success to CloudFormation
              responseData = {}
              responseData['Data'] = 120
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) 

  # Trigger the lambda from the CloudFormation stack
  CognitoSecretExporterInvoke:
    Type: AWS::CloudFormation::CustomResource
    DependsOn: CognitoSecretExporterLambda
    Version: "1.0"
    Properties:
      ServiceToken: !GetAtt CognitoSecretExporterLambda.Arn 

  # IAM Role for the lambda above
  CognitoSecretExporterExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: CognitoSecretExporterExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
              - lambda.amazonaws.com
          Action:
            - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
      PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Sid: EnablePutLogEvents
              Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource:
                - '*'
            - Sid: ReadCognito
              Effect: Allow
              Action:
                - cognito-idp:DescribeUserPoolClient
              Resource:
                - !GetAtt UserPool.Arn
            - Sid: ParamStore
              Effect: Allow
              Action:
                - ssm:DeleteParameter
                - ssm:PutParameter
                - ssm:GetParameter
              Resource: 
                - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/production/cognito/ClientSecret

2 megjegyzés:

  1. Használtad már a Terraform-t Azure-el?

    VálaszTörlés
    Válaszok
    1. Igen, Terraformozom az Azure-ban, most napi munka szinten. Ahonnan a fenti cikk jön az egy community project az AWS-ben.

      Törlés

Megjegyzés: Megjegyzéseket csak a blog tagjai írhatnak a blogba.