AWSTemplateFormatVersion: 2010-09-09

Description: |
  nOps.io integration role and policy (updated October 21 2022)
  For more information visit http://help.nops.io

Mappings:
 attributes:
  Nops:
   AuthURL: https://app.nops.io/

Parameters:
  ApiKey:
    Type: String
    Description: |
      nOps API Key

  PayerAccount:
    Type: String
    Default: 'true'
    AllowedValues:
    - true
    - false
    Description: |
      Leave as true if the template is being applied to onboard a payer account, this will create resources only required for payer accounts.

  MinRequiredPermissions:
    Type: String
    Default: 'false'
    AllowedValues:
    - true
    - false
    Description: |
      If true, IAM policies with the min base permissions for nOps to get cost and usage data will be created. Some platform features will not be available.

Conditions:
  IsPayerAccount: !Equals [!Ref PayerAccount, 'true']
  MinRequiredPermissions: !Equals [!Ref MinRequiredPermissions, 'true']
  FullPermissions: !Equals [!Ref MinRequiredPermissions, 'false']

Resources:
  NopsGetProjectMetadataLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
        Version: 2012-10-17
      Path: /
      Policies:
        - PolicyDocument:
            Statement:
              - Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Effect: Allow
                Resource: arn:aws:logs:*:*:*
              - Action:
                  - organizations:DescribeOrganization
                  - sts:GetCallerIdentity
                Effect: Allow
                Resource: "*"
            Version: 2012-10-17
          PolicyName: root

  NopsGetProjectMetadata:
    Type: AWS::Lambda::Function
    DependsOn:
      - NopsGetProjectMetadataLambdaExecutionRole
    Properties:
      Code:
        ZipFile: !Sub |
          import cfnresponse
          import urllib3
          import json
          import boto3

          client = boto3.client('organizations')
          sts_client = boto3.client('sts')

          def lambda_handler(event, context):
            print(f'Received event:  {event}')

            stack_response = dict()
            http = urllib3.PoolManager()
            properties = event['ResourceProperties']

            if event['RequestType'] == 'Create':
              try:
                aws_account_id = properties['AWSAccountID']
                nops_auth_url = properties['NopsAuthURL']
                nops_project_url = f'{nops_auth_url}c/admin/projectaws/'
                nops_integration_metadata_url = f'{nops_auth_url}c/aws/integration/'
                api_key = properties['ApiKey']
                headers = {
                  'Accept': 'application/json',
                  'X-Nops-Api-Key': api_key
                }
                print('Getting nOps project metadata')
                response = http.request('GET', nops_project_url, headers=headers)
                project_data = json.loads(response.data)
                external_id = ''
                client_id = ''
                project_id = ''
                found_project = False

                print('Getting AWS organization metadata')
                organization_metadata = client.describe_organization()
                master_account_id = organization_metadata['Organization']['MasterAccountId']

                for project in project_data:
                  if project['account_number'] is None:
                    # Case where this is onboarding the master account for the first time
                    external_id = project['external_id']
                    client_id = project['client']
                    project_id = project['id']
                    found_project = True
                    break
                  if aws_account_id == project['account_number']:
                    # For any other account that were autodiscovered
                    external_id = project['external_id']
                    client_id = project['client']
                    project_id = project['id']
                    found_project = True
                    break
                
                if not found_project:
                  # Case that account wasn't autodiscovered, we create the project from here
                  print("Project for this account not found, creating...")
                  create_headers = {
                    'Content-Type': 'application/json',
                    'X-Nops-Api-Key': api_key
                  }
                  account_id = sts_client.get_caller_identity()["Account"]
                  body = json.dumps({
                    "name" : f"AWS Account {account_id}",
                    "account_number": account_id,
                    "master_payer_account_number": master_account_id
                  })
                  print('Creating project in nOps')
                  create_response = http.request('POST', nops_project_url, headers=create_headers, body=body)
                  project_data = json.loads(create_response.data)
                  print(project_data)
                  external_id = project_data['external_id']
                  client_id = project_data['client']
                  project_id = project_data['id']

                if aws_account_id != master_account_id and properties['IsPayerAccount'] == 'true':
                  raise Exception("Applying in child account while PayerAccount is set to true, please fix this parameter")

                print('Getting nOps project integration metadata')
                integration_headers = {
                  'Content-Type': 'application/json',
                  'X-Nops-Api-Key': api_key
                }
                integration_query_params = {
                  'system_bucket': 'na',
                  'project_id': project_id
                }
                integration_response = http.request('GET', nops_integration_metadata_url, headers=headers, fields=integration_query_params)
                integration_data = json.loads(integration_response.data)
                active_token = integration_data['active_token']

                stack_response['Message'] = 'Resource creation successful!'
                stack_response['ExternalID'] = external_id
                stack_response['ClientID'] = client_id
                stack_response['ProjectID'] = project_id
                stack_response['MasterAccountID'] = master_account_id
                stack_response['ActiveToken'] = active_token

                cfnresponse.send(event, context, 'SUCCESS', stack_response)

              except Exception as e:
                stack_response['Message'] = f'Resource creation failed: {e}'
                cfnresponse.send(event, context, 'FAILED', stack_response)

            cfnresponse.send(event, context, 'SUCCESS', stack_response)

      Runtime: python3.12
      Handler: index.lambda_handler
      Role: !GetAtt [NopsGetProjectMetadataLambdaExecutionRole, Arn]
      Timeout: 45
      Description: |
        Lambda function to get project metadata to use in resource creation.

  NopsGetMetadataLambdaTrigger:
    Type: Custom::CustomResource
    DependsOn:
      - NopsGetProjectMetadataLambdaExecutionRole
      - NopsGetProjectMetadata
    Properties:
      ServiceToken: !GetAtt [NopsGetProjectMetadata, Arn]
      NopsAuthURL: !FindInMap [attributes, Nops, AuthURL]
      AWSAccountID: !Sub ${AWS::AccountId}
      ApiKey: !Sub ${ApiKey}
      IsPayerAccount: !Sub ${PayerAccount}


  NopsIntegrationRole:
    Type: AWS::IAM::Role
    DependsOn:
      - NopsGetMetadataLambdaTrigger
    Properties:
      ManagedPolicyArns: !If [ FullPermissions, ["arn:aws:iam::aws:policy/ReadOnlyAccess"], []]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::202279780353:root
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                "sts:ExternalId": !GetAtt [NopsGetMetadataLambdaTrigger, ExternalID]
      Tags:
        - Key: version
          Value: 1.0.16
        - Key: createdat
          Value: 20230321

  NopsWAFRPolicy:
      Type: AWS::IAM::Policy
      Condition: MinRequiredPermissions
      DependsOn:
        - NopsIntegrationRole
      Properties:
        PolicyName: NopsWAFRPolicy
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Resource: "*"
              Effect: Allow
              Action:
                - "access-analyzer:GetAccessPreview"
                - "access-analyzer:GetAnalyzedResource"
                - "access-analyzer:GetAnalyzer"
                - "access-analyzer:GetArchiveRule"
                - "access-analyzer:GetFinding"
                - "access-analyzer:GetGeneratedPolicy"
                - "access-analyzer:ListAccessPreviewFindings"
                - "access-analyzer:ListAccessPreviews"
                - "access-analyzer:ListAnalyzedResources"
                - "access-analyzer:ListAnalyzers"
                - "access-analyzer:ListArchiveRules"
                - "access-analyzer:ListFindings"
                - "access-analyzer:ListPolicyGenerations"
                - "access-analyzer:ListTagsForResource"
                - "access-analyzer:ValidatePolicy"
                - "cloudtrail:DescribeTrails"
                - "cloudtrail:LookupEvents"
                - "cloudwatch:GetMetricStatistics"
                - "config:DescribeConfigurationRecorders"
                - "dynamodb:DescribeTable"
                - "iam:ListUsers"
                - "iam:GetRole"
                - "iam:GetAccountSummary"
                - "iam:GetAccountPasswordPolicy"
                - "iam:ListAttachedUserPolicies"
                - "inspector:ListAssessmentRuns"
                - "ec2:DescribeFlowLogs"
                - "ec2:DescribeRouteTables"
                - "ec2:DescribeSecurityGroups"
                - "trustedadvisor:GetOrganizationRecommendation"
                - "trustedadvisor:GetRecommendation"
                - "trustedadvisor:ListChecks"
                - "trustedadvisor:ListOrganizationRecommendationAccounts"
                - "trustedadvisor:ListOrganizationRecommendationResources"
                - "trustedadvisor:ListOrganizationRecommendations"
                - "trustedadvisor:ListRecommendationResources"
                - "trustedadvisor:ListRecommendations"
                - "wellarchitected:*"
                - "workspaces:DescribeWorkspaceDirectories" 
        Roles: [!Ref NopsIntegrationRole]

  NopsEssentialsPolicy:
    Type: AWS::IAM::Policy
    DependsOn:
      - NopsIntegrationRole
    Condition: MinRequiredPermissions
    Properties:
      PolicyName: NopsEssentialsPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Resource: "*"
            Effect: Allow
            Action:
              - "cloudwatch:ListMetrics"
              - "events:CreateEventBus"
              - "scheduler:GetSchedule"
              - "scheduler:GetScheduleGroup"
              - "scheduler:ListScheduleGroups"
              - "scheduler:ListSchedules"
              - "scheduler:ListTagsForResource"              
      Roles: [!Ref NopsIntegrationRole]

  NopsComputeCopilotPolicy:
    Type: AWS::IAM::Policy
    DependsOn:
      - NopsIntegrationRole
    Condition: MinRequiredPermissions
    Properties:
      PolicyName: NopsComputeCopilotPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Resource: "*"
            Effect: Allow
            Action:
              - "ec2:DescribeLaunchTemplateVersions"
              - "ec2:DescribeLaunchConfigurations"
              - "ec2:DescribeImages"
              - "lambda:InvokeFunction"
              - "cloudformation:ListStacks"
              - "cloudformation:DescribeStacks"              
      Roles: [!Ref NopsIntegrationRole]

  NopsIntegrationMinimumPolicy:
    Type: AWS::IAM::Policy
    DependsOn:
      - NopsIntegrationRole
    Condition: MinRequiredPermissions
    Properties:
      PolicyName: NopsIntegrationPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
            - autoscaling:DescribeAutoScalingGroups
            - autoscaling:DescribeAutoScalingInstances
            - autoscaling-plans:DescribeScalingPlans
            - autoscaling-plans:GetScalingPlanResourceForecastData
            - autoscaling-plans:DescribeScalingPlanResources
            - autoscaling:DescribeAutoScalingInstances
            - autoscaling:DescribePolicies
            - autoscaling:DescribeLaunchConfigurations
            - autoscaling:DescribeLoadBalancers
            - autoscaling:DescribeScalingActivities
            - autoscaling:DescribeAutoScalingGroups
            - autoscaling:DescribeAccountLimits
            - apigateway:GET
            - ce:DescribeCostCategoryDefinition
            - ce:DescribeNotificationSubscription
            - ce:DescribeReport
            - ce:GetAnomalies
            - ce:GetAnomalyMonitors
            - ce:GetAnomalySubscriptions
            - ce:GetApproximateUsageRecords
            - ce:GetCostAndUsage
            - ce:GetCostAndUsageWithResources
            - ce:GetCostCategories
            - ce:GetCostForecast
            - ce:GetDimensionValues
            - ce:GetPreferences
            - ce:GetReservationCoverage
            - ce:GetReservationPurchaseRecommendation
            - ce:GetReservationUtilization
            - ce:GetRightsizingRecommendation
            - ce:GetSavingsPlanPurchaseRecommendationDetails
            - ce:GetSavingsPlansCoverage
            - ce:GetSavingsPlansPurchaseRecommendation
            - ce:GetSavingsPlansUtilization
            - ce:GetSavingsPlansUtilizationDetails
            - ce:GetTags
            - ce:GetUsageForecast
            - ce:ListCostAllocationTagBackfillHistory
            - ce:ListCostAllocationTags
            - ce:ListCostCategoryDefinitions
            - ce:ListSavingsPlansPurchaseRecommendationGeneration
            - ce:ListTagsForResource
            - ce:StartSavingsPlansPurchaseRecommendationGeneration
            - ce:UpdateCostAllocationTagsStatus
            - compute-optimizer:DescribeRecommendationExportJobs
            - compute-optimizer:GetAutoScalingGroupRecommendations
            - compute-optimizer:GetEBSVolumeRecommendations
            - compute-optimizer:GetEC2InstanceRecommendations
            - compute-optimizer:GetEC2RecommendationProjectedMetrics
            - compute-optimizer:GetECSServiceRecommendationProjectedMetrics
            - compute-optimizer:GetECSServiceRecommendations
            - compute-optimizer:GetEffectiveRecommendationPreferences
            - compute-optimizer:GetEnrollmentStatus
            - compute-optimizer:GetEnrollmentStatusesForOrganization
            - compute-optimizer:GetLambdaFunctionRecommendations
            - compute-optimizer:GetLicenseRecommendations
            - compute-optimizer:GetRDSDatabaseRecommendationProjectedMetrics
            - compute-optimizer:GetRDSDatabaseRecommendations
            - compute-optimizer:GetRecommendationPreferences
            - compute-optimizer:GetRecommendationSummaries
            - config:DescribeConfigurationRecorders
            - consolidatedbilling:GetAccountBillingRole
            - cost-optimization-hub:GetPreferences
            - cost-optimization-hub:GetRecommendation
            - cost-optimization-hub:ListEnrollmentStatuses
            - cost-optimization-hub:ListRecommendations
            - cost-optimization-hub:ListRecommendationSummaries
            - cost-optimization-hub:UpdateEnrollmentStatus
            - cost-optimization-hub:UpdatePreferences
            - consolidatedbilling:ListLinkedAccounts
            - cur:GetClassicReport
            - cur:GetClassicReportPreferences
            - cur:GetUsageReport
            - cur:DescribeReportDefinitions
            - cur:PutReportDefinition
            - dynamodb:ListTables
            - ec2:DescribeImages
            - ec2:DescribeInstances
            - ec2:DescribeNatGateways
            - ec2:DescribeNetworkInterfaces
            - ec2:DescribeRegions
            - ec2:DescribeReservedInstances
            - ec2:DescribeVolumes
            - ec2:DescribeVpcs
            - ec2:DescribeAvailabilityZones
            - ec2:DescribeInstanceStatus
            - ec2:DescribeSnapshots
            - ecs:ListClusters
            - eks:ListTagsForResource
            - eks:ListClusters
            - eks:ListNodegroups
            - eks:DescribeCluster
            - eks:DescribeNodegroup
            - elasticache:DescribeCacheClusters
            - elasticache:DescribeCacheSubnetGroups
            - elasticfilesystem:DescribeFileSystems
            - elasticloadbalancing:DescribeLoadBalancers
            - es:DescribeElasticsearchDomains
            - es:ListDomainNames
            - events:ListRules
            - guardduty:ListDetectors
            - iam:ListRoles
            - iam:ListAccountAliases
            - kms:Decrypt
            - lambda:GetFunction
            - lambda:GetPolicy
            - lambda:ListFunctions
            - rds:DescribeDBClusters
            - rds:DescribeDBInstances
            - rds:DescribeDBSnapshots
            - rds:DescribeDBClusterSnapshotAttributes
            - rds:DescribeDBClusterSnapshots
            - rds:DescribeDBRecommendations
            - rds:DescribeDBSecurityGroups
            - rds:ListTagsForResource
            - redshift:DescribeClusters
            - s3:ListAllMyBuckets
            - s3:GetBucketVersioning
            - savingsplans:DescribeSavingsPlanRates
            - savingsplans:DescribeSavingsPlans
            - savingsplans:DescribeSavingsPlansOfferingRates
            - savingsplans:DescribeSavingsPlansOfferings
            - savingsplans:ListTagsForResource
            - support:DescribeCases
            - support:DescribeTrustedAdvisorCheckRefreshStatuses
            - support:DescribeTrustedAdvisorCheckResult
            - support:DescribeTrusted
            - support:DescribeTrustedAdvisorChecks
            - tag:GetResources
            - tag:GetTagValues
            - tag:DescribeReportCreation
            - tag:GetTagKeys
            - tag:GetComplianceSummary
            - organizations:ListAccounts
            - organizations:DescribeOrganization
            - organizations:ListRoots
            Effect: Allow
            Resource: "*"
      Roles: [!Ref NopsIntegrationRole]

  NopsIntegrationPolicy:
    Type: AWS::IAM::Policy
    DependsOn:
      - NopsIntegrationRole
    Condition: FullPermissions
    Properties:
      PolicyName: NopsIntegrationPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - cur:DescribeReportDefinitions
              - cur:DeleteReportDefinition
              - cur:PutReportDefinition
              - s3:ListBucket
              - support:DescribeTrustedAdvisorCheckRefreshStatuses
              - support:DescribeTrustedAdvisorCheckResult
              - support:DescribeTrustedAdvisorChecks
              - events:CreateEventBus
              - wellarchitected:*
              - ce:*
            Effect: Allow
            Resource: "*"
          - Action:
              - acm-pca:Describe*
              - acm-pca:Get*
              - acm-pca:List*
              - acm:Describe*
              - acm:Get*
              - acm:List*
              - apigateway:GET
              - appconfig:GetConfiguration*
              - appflow:DescribeConnector*
              - appflow:ListConnector*
              - appstream:DescribeDirectoryConfigs
              - appstream:DescribeUsers
              - appstream:DescribeSessions
              - appsync:Get*
              - appsync:List*
              - athena:Get*
              - athena:List*
              - backup:GetBackupVaultAccessPolicy
              - cassandra:Select
              - chime:Describe*
              - chime:Get*
              - chime:List*
              - cloud9:Describe*
              - cloud9:Get*
              - cloud9:List*
              - clouddirectory:Get*
              - clouddirectory:List*
              - cloudfront:GetCloudFrontOriginAccessIdentity 
              - cloudfront:GetFieldLevelEncryption*
              - cloudfront:GetKeyGroupConfig
              - cloudwatch:GetMetricData
              - cloudwatch:GetMetricStream
              - cloudwatch:ListMetricStreams
              - codeartifact:GetAuthorizationToken
              - codeartifact:ReadFromRepository 
              - codebuild:BatchGet*
              - codebuild:ListSourceCredentials
              - codecommit:BatchGet*
              - codecommit:Get*
              - codecommit:GitPull
              - codedeploy:BatchGet*
              - codedeploy:Get*
              - codestar:DescribeUserProfile
              - codestar:ListUserProfiles
              - cognito-identity:*
              - cognito-idp:*
              - cognito-sync:*
              - comprehend:Describe*
              - comprehend:List*
              - config:BatchGetAggregateResourceConfig
              - config:BatchGetResourceConfig
              - config:SelectAggregateResourceConfig
              - config:SelectResourceConfig
              - connect:Describe*
              - connect:Get*
              - connect:List*
              - datapipeline:DescribeObjects
              - datapipeline:EvaluateExpression
              - datapipeline:QueryObjects
              - dax:BatchGetItem
              - dax:GetItem
              - dax:Query
              - deepcomposer:Get*
              - deepcomposer:List*
              - devicefarm:GetRemoteAccessSession
              - devicefarm:ListRemoteAccessSessions
              - directconnect:Describe*
              - directconnect:List*
              - discovery:Describe*
              - discovery:Get*
              - discovery:List*
              - dms:Describe*
              - dms:List*
              - ds:Get*
              - dynamodb:GetItem
              - dynamodb:BatchGetItem
              - dynamodb:Query
              - dynamodb:Scan
              - ec2:GetConsoleScreenshot
              - ecr:BatchGetImage
              - ecr:GetAuthorizationToken
              - ecr:GetDownloadUrlForLayer
              - ecr-public:GetAuthorizationToken
              - eks:DescribeIdentityProviderConfig
              - elasticbeanstalk:DescribeConfigurationOptions
              - elasticbeanstalk:DescribeConfigurationSettings
              - es:ESHttpGet*
              - fis:GetExperimentTemplate
              - fms:GetAdminAccount
              - frauddetector:BatchGetVariable
              - frauddetector:Get*
              - gamelift:GetGameSessionLogUrl
              - gamelift:GetInstanceAccess
              - geo:ListDevicePositions
              - glue:GetSecurityConfiguration*
              - glue:SearchTables
              - glue:GetTable*
              - guardduty:GetIPSet
              - guardduty:GetMasterAccount
              - guardduty:GetMembers
              - guardduty:ListMembers
              - guardduty:ListOrganizationAdminAccounts
              - inspector2:GetConfiguration
              - imagebuilder:GetImage
              - iotroborunner:Get*
              - iotsitewise:ListAccessPolicies
              - ivs:GetPlaybackKeyPair
              - ivs:GetStreamSession
              - kafka:GetBootstrapBrokers
              - kendra:Query*
              - kinesis:Get*
              - kms:DescribeKey
              - kms:GetPublicKey
              - lex:Get*
              - lambda:GetFunctionConfiguration
              - license-manager:GetGrant
              - license-manager:GetLicense
              - license-manager:ListTokens
              - lightsail:GetBucketAccessKeys
              - lightsail:GetCertificates
              - lightsail:GetContainerImages
              - lightsail:GetKeyPair
              - lightsail:GetRelationalDatabaseLogStreams
              - logs:GetLogEvents
              - logs:StartQuery
              - machinelearning:GetMLModel
              - macie2:GetAdministratorAccount
              - macie2:GetMember
              - macie2:GetMacieSession
              - macie2:SearchResources
              - macie2:GetSensitiveDataOccurrences
              - nimble:GetStreamingSession
              - polly:SynthesizeSpeech
              - proton:GetEnvironmentTemplate
              - proton:GetServiceTemplate
              - proton:ListServiceTemplates
              - proton:ListEnvironmentTemplates
              - qldb:GetBlock
              - qldb:GetDigest
              - rds:Download*
              - rekognition:CompareFaces
              - rekognition:Detect*
              - rekognition:Search*
              - resiliencehub:DescribeAppVersionTemplate
              - resiliencehub:ListRecommendationTemplates
              - robomaker:GetWorldTemplateBody
              - s3-object-lambda:GetObject
              - sagemaker:Search
              - schemas:GetDiscoveredSchema
              - sdb:Get*
              - sdb:Select*
              - secretsmanager:*
              - securityhub:GetFindings
              - securityhub:GetMembers
              - securityhub:ListMembers
              - ses:GetTemplate
              - ses:GetEmailTemplate
              - ses:GetContact
              - ses:GetContactList
              - ses:ListTemplates
              - ses:ListEmailTemplates
              - ses:ListVerifiedEmailAddresses
              - signer:GetSigningProfile
              - signer:ListProfilePermissions
              - signer:ListSigningProfiles
              - sms-voice:DescribeKeywords
              - sms-voice:DescribeOptedOutNumbers
              - sms-voice:DescribePhoneNumbers
              - sms-voice:DescribePools
              - snowball:Describe*
              - sqs:Receive*
              - ssm-contacts:*
              - ssm:DescribeParameters*
              - ssm:GetParameter*
              - sso:Describe*
              - sso:Get*
              - sso:List*
              - storagegateway:DescribeChapCredentials
              - support:DescribeCommunications
              - timestream:ListDatabases
              - timestream:ListTables
              - transcribe:Get*
              - transcribe:List*
              - transfer:Describe*
              - transfer:List*
              - waf-regional:GetChangeToken
              - workmail:DescribeUser
              - workmail:GetMailUserDetails
              - workmail:ListUsers
            Effect: Deny
            Resource: "*"
      Roles: [!Ref NopsIntegrationRole]

  NopsSystemS3Bucket:
    Type: AWS::S3::Bucket
    Condition: IsPayerAccount
    DependsOn:
      - NopsIntegrationRole
    Properties:
      BucketName: !Sub "nops-${NopsGetMetadataLambdaTrigger.ClientID}-${NopsGetMetadataLambdaTrigger.ProjectID}-${AWS::AccountId}"
      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
  
  NopsSystemS3BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Condition: IsPayerAccount
    DependsOn:
      - NopsSystemS3Bucket
    Properties:
      Bucket: !Sub "nops-${NopsGetMetadataLambdaTrigger.ClientID}-${NopsGetMetadataLambdaTrigger.ProjectID}-${AWS::AccountId}"
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Deny
          Principal: "*"
          Action: s3:*
          Resource:
          - !GetAtt [NopsSystemS3Bucket, Arn]
          - !Sub "${NopsSystemS3Bucket.Arn}/*"
          Condition:
            Bool:
              aws:SecureTransport: 'false'
  
  NopsSystemBucketPolicy:
    Type: AWS::IAM::Policy
    Condition: IsPayerAccount
    DependsOn:
      - NopsIntegrationRole
      - NopsSystemS3Bucket
    Properties:
      PolicyName: NopsSystemBucketPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
            -  s3:ListBucket
            -  s3:GetBucketPolicy
            -  s3:GetEncryptionConfiguration
            -  s3:GetBucketVersioning
            -  s3:GetBucketPolicyStatus
            -  s3:GetBucketLocation
            -  s3:GetBucketAcl
            -  s3:GetBucketLogging
            -  s3:GetObject
            -  s3:PutBucketPolicy
            -  s3:PutObject
            -  s3:HeadBucket
            Resource:
              - !GetAtt [NopsSystemS3Bucket, Arn]
              - !Sub "${NopsSystemS3Bucket.Arn}/*"
      Roles: [!Ref NopsIntegrationRole]

  WaitForS3Handle:
    Condition: IsPayerAccount
    DependsOn: 
      - NopsSystemS3Bucket
      - NopsIntegrationRole
    Type: AWS::CloudFormation::WaitConditionHandle

  WaitForResourcesHandle:
    DependsOn:
      - NopsIntegrationRole
    Type: AWS::CloudFormation::WaitConditionHandle

  WaitForResources:
    Type: AWS::CloudFormation::WaitCondition
    Properties:
      Handle: !If [IsPayerAccount, !Ref WaitForS3Handle, !Ref WaitForResourcesHandle]
      Timeout: '15'
      Count: 0

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

  NopsCallBack:
    Type: AWS::Lambda::Function
    DependsOn:
    - WaitForResources
    - NopsCallBackLambdaExecutionRole
    Properties:
      Code:
        ZipFile: !Sub |
          import cfnresponse
          import urllib3
          import json
          import base64

          def lambda_handler(event, context):
            """Handles stack events"""

            print("Received event: ", event)

            stack_response = dict()

            if event["RequestType"] == "Create":
              try:
                properties = event["ResourceProperties"]
                nops_auth_url = properties["NopsAuthURL"]
                active_token = properties["ActiveToken"]
                nops_integration_complete_url = f'{nops_auth_url}c/aws/integration/?active_token={active_token}'
                event["ResourceProperties"]["NopsAuthURL"] = nops_integration_complete_url
                
                notify_nops(event)
                stack_response["Message"] = "Resource creation successful!"
                cfnresponse.send(event, context, "SUCCESS", stack_response)
              except Exception as e:
                print(e)
                stack_response["Message"] = f"Resource creation failed: {e}"
                cfnresponse.send(event, context, "FAIL", stack_response)

            cfnresponse.send(event, context, "SUCCESS", stack_response)

          def notify_nops(event):
            print(f'Notification event: {event}')
            properties = event['ResourceProperties']
            nops_auth_url = properties['NopsAuthURL']
            api_key = ''
            account_number = properties["AWSAccountID"]
            http = urllib3.PoolManager()
            encoded_data = json.dumps(event).encode('utf-8')
            headers = {
              'Content-Type': 'application/json',
              'X-Nops-Api-Key': api_key,
              'X-Aws-Account-Number': account_number,
            }
            print("Finalizing nOps integration")
            response = http.request('POST', nops_auth_url, body=encoded_data, headers=headers)
            print("Notify response: ", response.data)

      Runtime: python3.12
      Handler: index.lambda_handler
      Role: !GetAtt [NopsCallBackLambdaExecutionRole, Arn]
      Timeout: 45
      Description: |
        Lambda function to update nOps infra with role creation output.

  NopsLambdaTrigger:
    Type: Custom::CustomResource
    DependsOn:
      - NopsIntegrationRole
      - NopsCallBack
      - WaitForResources
    Properties:
      RoleArn: !GetAtt [NopsIntegrationRole, Arn]
      ServiceToken: !GetAtt [NopsCallBack, Arn]
      ServiceBucket: !If [IsPayerAccount,!Sub "nops-${NopsGetMetadataLambdaTrigger.ClientID}-${NopsGetMetadataLambdaTrigger.ProjectID}-${AWS::AccountId}", "na"]
      NopsAuthURL: !FindInMap [attributes, Nops, AuthURL]
      AWSAccountID: !Sub ${AWS::AccountId}
      ExternalID: !GetAtt [NopsGetMetadataLambdaTrigger, ExternalID]
      ApiKey: !Sub ${ApiKey}
      ActiveToken: !GetAtt [NopsGetMetadataLambdaTrigger, ActiveToken]

Outputs:
  RoleArn:
    Description: The ARN of the IAM role
    Value: !GetAtt [NopsIntegrationRole, Arn]

  Message:
    Description: The message from the lambda trigger.
    Value: !GetAtt [NopsLambdaTrigger, Message]

  LambdaLookupStackFunctionARN:
    Description: The arn of the lambda function.
    Value: !GetAtt [NopsCallBack, Arn]
  