AWSTemplateFormatVersion: "2010-09-09"

Description:
  Add Okta as an identity provider to your AWS account

Parameters:
  Metadata:
    Type: String
    Description: The XML metadata from Okta
  IDP:
    Type: String
    Description: Unique name for identity provider in IAM
    Default: Okta

Resources:
  IdentityProvider:
    Type: Custom::IdentityProvider
    Properties:
      ServiceToken: !GetAtt ProviderCreator.Arn
      Region: !Ref "AWS::Region"
      Metadata: !Ref Metadata
      Name: !Ref IDP

  ProviderCreator:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python2.7
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaExecutionRole.Arn
      Timeout: 30
      Code:
        ZipFile: !Sub |
          import boto3
          from botocore.exceptions import ClientError
          import json
          import cfnresponse

          iam = boto3.client("iam")

          def create_provider(name, doc):
            try:
              resp = iam.create_saml_provider(SAMLMetadataDocument=doc,Name=name)
              return(True, resp['SAMLProviderArn'])
            except Exception as e:
              return (False, "Cannot create SAML provider: " + str(e))

          def delete_provider(arn):
            try:
              resp = iam.delete_saml_provider(SAMLProviderArn=arn)
              return (True, "SAML provider with ARN " + arn + " deleted")
            except ClientError as e:
              if e.response['Error']['Code'] == "NoSuchEntity":
                # no need to delete a thing that doesn't exist
                return (True, "SAML provider with ARN " + arn + " does not exist, deletion succeeded")
              else:
                return (False, "Cannot delete SAML provider with ARN " + arn + ": " + str(e))
            except Exception as e:
              return (False, "Cannot delete SAML provider with ARN " + arn + ": " + str(e))

          def update_provider(arn, doc):
            # Need to create the ARN from the name
            arn = "arn:aws:iam::${AWS::AccountId}:saml-provider/" + name
            try:
              resp = iam.update_saml_provider(SAMLMetadataDocument=doc, SAMLProviderArn=arn)
              return (True, "SAML provider " + arn + " updated")
            except Exception as e:
              return (False, "Cannot update SAML provider " + arn + ": " + str(e))

          def lambda_handler(event, context):
            provider_xml = event['ResourceProperties']['Metadata']
            provider_name = event['ResourceProperties']['Name']
            # create a default ARN from the name; will be overwritten if we are creating
            provider_arn = "arn:aws:iam::${AWS::AccountId}:saml-provider/" + provider_name

            if event['RequestType'] == 'Create':
              res, provider_arn = create_provider(provider_name, provider_xml)
              reason = "Creation succeeded"
            elif event['RequestType'] == 'Update':
              res, reason = update_provider(provider_arn, provider_xml)
            elif event['RequestType'] == 'Delete':
              res, reason = delete_provider(provider_arn)
            else:
              res = False
              resp = "Unknown operation: " + event['RequestType']

            responseData = {}
            responseData['Reason'] = reason
            if res:
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, provider_arn)
            else:
              cfnresponse.send(event, context, cfnresponse.FAILED, responseData, provider_arn)

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - iam:*SamlProvider
                Resource: "*"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"

  OktaSSOlistRolesPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: allow the Okta service user to get list of roles
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - iam:ListRoles
              - iam:ListAccountAliases
            Effect: Allow
            Resource: "*"
      ManagedPolicyName: OktaSSOlistRoles

  OktaSSOlistRolesUser:
    Type: AWS::IAM::User
    Properties:
      ManagedPolicyArns:
        - !Ref OktaSSOlistRolesPolicy
      UserName: !Join 
        - _
        - - OktaSSOserviceUser
          - !Ref AWS::Region

  OktaAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref OktaSSOlistRolesUser

  ExampleRoleAllowingOktaSSO:
    Type: AWS::IAM::Role
    Properties:
      RoleName: okta_sso_s3_read_only
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Federated: !Ref IdentityProvider
            Action:
              - sts:AssumeRoleWithSAML
            Condition:
              StringEquals:
                'SAML:aud': 'https://signin.aws.amazon.com/saml'

  PolicyToTestOktaSSO:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: OktaS3ReadOnly
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action:
          - s3:Get*
          - s3:List*
          Resource: "*"
      Roles:
        - !Ref ExampleRoleAllowingOktaSSO

Outputs:
  IdentityProviderArn:
    Description: ARN of identity provider (Okta)
    Value: !Ref IdentityProvider
  ExampleRoleAllowingOktaSSO:
    Value: !Ref ExampleRoleAllowingOktaSSO
  AccessKey:
    Value: !Ref OktaAccessKey
  SecretKey:
    Value: !GetAtt OktaAccessKey.SecretAccessKey
