How do I get an OIDC provider URL from AWS::EKS::Cluster in AWS CloudFormation?

Last updated: 2020-08-20

I want to fetch the OpenID Connect (OIDC) provider URL from AWS::EKS::Cluster. Then, I want to use the URL in an AWS Identity and Access Management (IAM) role for a service account trust relationship using AWS CloudFormation.

Short description

AWS::EKS::Cluster doesn't provide a return value to get the OIDC provider URL. You must use AWS Lambda-backed custom resources in AWS CloudFormation to get the cluster OIDC provider URL from an existing Amazon Elastic Kubernetes Service (Amazon EKS) cluster. Then, you can use the value returned from the cluster to create an IAM role. The IAM role allows you to establish a trust relationship with OIDC.

Note: The following resolution uses Python 3.7 and is applicable for Kubernetes version 1.14 and later.

Resolution

The following example demonstrates how to associate a service account in an Amazon EKS cluster with an IAM role. The IAM role provides fine-grained permissions to Amazon EKS pods and secure access to the AWS API for the service accounts in the Amazon EKS cluster. Then, the stack creates an IAM role associated with the Amazon EKS cluster's OIDC value.

1.    Create an AWS CloudFormation template called CustomLambdaEksOidc.template based on the following example:

Parameters:
  EKSClusterName:
    Type: String
    Description: Provide the name for EKS Cluster
Resources:
  ClusterName:
      Type: Custom::ClusterName
      Properties:
        ServiceToken: !GetAtt LambdaFunction.Arn
        cluster_name:
          Ref: EKSClusterName
  MyIAMRole:
      Type: AWS::IAM::Role
      DependsOn: ClusterName
      Properties:
        AssumeRolePolicyDocument: !Sub |
          {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Principal": {
                  "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/${ClusterName.oidc}"
                },
                "Action": "sts:AssumeRoleWithWebIdentity",
                "Condition": {
                  "StringEquals": {
                    "${ClusterName.oidc}:aud": "system:serviceaccount:default:my-serviceaccount"
                  }
                }
              }
            ]
          }
  LambdaIAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      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:
              - Effect: Allow
                Action:
                  - 'eks:DescribeCluster'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: 'arn:aws:logs:*:*:*'
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import cfnresponse
          import boto3
          import json
          def lambda_handler(event, context):
              print("Received event: " + json.dumps(event, indent=2))
              oidc_response = ''
              responseData = {}
              try:
                  if event['RequestType'] == 'Delete':
                      print("Request Type:",event['RequestType'])
                      print("Delete Request - No Physical resources to delete")
                  elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
                      print("Request Type:",event['RequestType'])
                      cluster_name = event['ResourceProperties']['cluster_name']
                      oidc_response_url = fetchClusterOIDC(cluster_name)
                      oidc_response=oidc_response_url.split("https://")[1]
                      responseData['oidc'] = oidc_response
                  print("Sending response to custom resource for event type " + event['RequestType'])
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
                  return oidc_response
              except Exception as e:
                  print('Failed to process:', e)
                  responseStatus = 'FAILURE'
                  responseData = {'Failure': 'Something bad happened.'}
                  cfnresponse.send(event, context, cfnresponse.FAILED, responseData, "CustomResourcePhysicalID")
          def fetchClusterOIDC(cluster_name):
              print("Getting Cluster OIDC value for cluster name "+ cluster_name)
              oidc = ''
              client = boto3.client('eks')
              try:
                  response = client.describe_cluster(
                      name=cluster_name
                  )
                  if response['ResponseMetadata']['HTTPStatusCode'] == 200:
                      print("Success response recieved for describing cluster "+ cluster_name)
                      oidc = (response['cluster']['identity']['oidc']['issuer'])
                      print('OIDC output recieved '+ oidc + ' for Cluster Name ' + cluster_name)
                  return oidc
              except Exception as e:
                  print('Failed to fetch Cluster OIDC value for cluster name ' + cluster_name, e)
      Handler: index.lambda_handler
      MemorySize: 1024
      Role: !GetAtt LambdaIAMRole.Arn
      Runtime: python3.7
      Timeout: 300
Outputs:
  OIDC:
    Description: EKS Cluster OIDC Value
    Value:
      Fn::GetAtt:
      - ClusterName
      - oidc

2.    To launch an AWS CloudFormation stack with the CustomLambdaEksOidc.template file, use the AWS CloudFormation console or the AWS Command Line Interface (AWS CLI).

To use the AWS CLI, run the following command:

aws cloudformation create-stack --stack-name lambda-eks-oidc --template-body file://CustomLambdaEksOidc.template --parameters ParameterKey=EKSClusterName,ParameterValue=demo-newsblog --capabilities CAPABILITY_NAMED_IAM --region us-east-1

Note: When you launch your AWS CloudFormation stack, replace demo-newsblog with your Amazon EKS cluster name. Replace us-east-1 with your AWS Region.


Did this article help?


Do you need billing or technical support?