AWSTemplateFormatVersion: '2010-09-09'
Description: 'Transilience Managed Compliance - Full Onboarding with SSM Write & Cost Explorer'

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Transilience Configuration"
        Parameters:
          - CustomerEmail
          - CustomerName
    ParameterLabels:
      CustomerEmail:
        default: "Your Email Address"
      CustomerName:
        default: "Your Company Name"

Parameters:
  CustomerEmail:
    Type: String
    Description: Email address for notifications and account identification
    AllowedPattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
    ConstraintDescription: Must be a valid email address
  
  CustomerName:
    Type: String
    Description: Your company or organization name
    MinLength: 2
    MaxLength: 100

  TransilienceBackendUrl:
    Type: String
    Default: "https://transilience-customer-prod--transilience-onboarding-onbo-d5f413.modal.run/onboarding/create"
    Description: Transilience backend API endpoint (do not modify)

  TransilienceBackendApiKey:
    Type: String
    Default: "mcs-onboarding-2026-05-13-static-key"
    Description: Transilience backend API key (do not modify)
    NoEcho: true

Resources:
  #============================================================================
  # OIDC Identity Provider for Modal.com
  # DeletionPolicy: Retain so the provider survives stack deletion.
  #============================================================================
  ModalOIDCProvider:
    Type: AWS::IAM::OIDCProvider
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Url: https://oidc.modal.com
      ClientIdList:
        - oidc.modal.com
      ThumbprintList:
        - "9e99a48a9960b14926bb7f3b02e22da2b0ab7280"
      Tags:
        - Key: Purpose
          Value: Transilience-Managed-Compliance
        - Key: CreatedBy
          Value: Transilience-CloudFormation

  #============================================================================
  # IAM Role for Transilience Access
  #============================================================================
  TransilienceComplianceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'TrComplianceRole-${AWS::StackName}'
      Description: Role for Transilience Managed Compliance service to access AWS resources
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowModalOIDCAssumeRole
            Effect: Allow
            Principal:
              Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.modal.com"
            Action: sts:AssumeRoleWithWebIdentity
            Condition:
              StringEquals:
                "oidc.modal.com:aud": "oidc.modal.com"
              StringLike:
                "oidc.modal.com:sub":
                  - "modal:workspace_id:ac-v1CZ9TWcqA7282i3kcFYa9:*"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/SecurityAudit
        - !Ref ECRAndS3ReadPolicy
        - !Ref LogsAndVPCReadPolicy
        - !Ref SecurityServicesReadPolicy
        - !Ref SSMReadPolicy
        - !Ref CostExplorerPolicy
      Tags:
        - Key: Purpose
          Value: Transilience-Managed-Compliance
        - Key: CreatedBy
          Value: Transilience-CloudFormation
        - Key: CustomerEmail
          Value: !Ref CustomerEmail

  #============================================================================
  # Managed Policy 1: ECR + S3 General Read
  #============================================================================
  ECRAndS3ReadPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: Transilience-ECR-S3-Read
      Description: ECR and S3 general read access for Transilience compliance
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ECRReadAccess
            Effect: Allow
            Action:
              - ecr:GetAuthorizationToken
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:BatchGetImage
              - ecr:DescribeRepositories
              - ecr:DescribeImages
              - ecr:DescribeImageScanFindings
              - ecr:ListImages
              - ecr:ListTagsForResource
              - ecr:GetRepositoryPolicy
              - ecr:GetLifecyclePolicy
              - ecr:GetLifecyclePolicyPreview
            Resource: "*"
          - Sid: S3BucketConfigRead
            Effect: Allow
            Action:
              - s3:GetBucketLocation
              - s3:GetBucketPolicy
              - s3:GetBucketPolicyStatus
              - s3:GetBucketAcl
              - s3:GetBucketVersioning
              - s3:GetBucketTagging
              - s3:GetBucketLogging
              - s3:GetBucketPublicAccessBlock
              - s3:GetAccountPublicAccessBlock
              - s3:GetEncryptionConfiguration
              - s3:GetBucketWebsite
              - s3:GetBucketCORS
              - s3:GetLifecycleConfiguration
              - s3:GetReplicationConfiguration
              - s3:GetBucketObjectLockConfiguration
              - s3:ListBucket
              - s3:ListBucketVersions
              - s3:ListAllMyBuckets
            Resource: "*"

  #============================================================================
  # Managed Policy 2: CloudWatch Logs + VPC & Network Read
  #============================================================================
  LogsAndVPCReadPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: Transilience-Logs-VPC-Read
      Description: CloudWatch Logs and VPC/Network read access for Transilience compliance
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: CloudWatchLogsRead
            Effect: Allow
            Action:
              - logs:DescribeLogGroups
              - logs:DescribeLogStreams
              - logs:GetLogEvents
              - logs:FilterLogEvents
              - logs:GetLogRecord
              - logs:GetQueryResults
              - logs:DescribeQueries
              - logs:DescribeMetricFilters
              - logs:DescribeSubscriptionFilters
              - logs:ListTagsLogGroup
              - logs:GetLogGroupFields
              - logs:DescribeExportTasks
              - logs:DescribeDestinations
              - logs:DescribeResourcePolicies
            Resource: "*"
          - Sid: VPCFlowLogsAndNetworkRead
            Effect: Allow
            Action:
              - ec2:DescribeFlowLogs
              - ec2:DescribeVpcs
              - ec2:DescribeSubnets
              - ec2:DescribeNetworkInterfaces
              - ec2:DescribeSecurityGroups
              - ec2:DescribeSecurityGroupRules
              - ec2:DescribeNetworkAcls
              - ec2:DescribeRouteTables
              - ec2:DescribeVpcEndpoints
              - ec2:DescribeVpcPeeringConnections
              - ec2:DescribeNatGateways
              - ec2:DescribeInternetGateways
              - ec2:DescribeEgressOnlyInternetGateways
              - ec2:DescribeTransitGateways
              - ec2:DescribeTransitGatewayAttachments
              - ec2:DescribeInstances
              - ec2:DescribeInstanceStatus
              - ec2:DescribeTags
            Resource: "*"

  #============================================================================
  # Managed Policy 3: Security Services Read (Inspector, SecHub, GuardDuty,
  #                    Macie, Access Analyzer, Detective)
  #============================================================================
  SecurityServicesReadPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: Transilience-Security-Services-Read
      Description: Security services read access for Transilience compliance
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: InspectorRead
            Effect: Allow
            Action:
              - inspector2:ListFindings
              - inspector2:GetFindingsReportStatus
              - inspector2:ListCoverage
              - inspector2:ListCoverageStatistics
              - inspector2:ListAccountPermissions
              - inspector2:BatchGetAccountStatus
              - inspector2:BatchGetFreeTrialInfo
              - inspector2:DescribeOrganizationConfiguration
              - inspector2:GetConfiguration
              - inspector2:GetDelegatedAdminAccount
              - inspector2:GetEc2DeepInspectionConfiguration
              - inspector2:GetEncryptionKey
              - inspector2:GetMember
              - inspector2:GetSbomExport
              - inspector2:ListDelegatedAdminAccounts
              - inspector2:ListFilters
              - inspector2:ListFindingAggregations
              - inspector2:ListMembers
              - inspector2:ListTagsForResource
              - inspector2:ListUsageTotals
              - inspector2:SearchVulnerabilities
            Resource: "*"
          - Sid: SecurityHubRead
            Effect: Allow
            Action:
              - securityhub:GetFindings
              - securityhub:GetInsights
              - securityhub:GetInsightResults
              - securityhub:GetEnabledStandards
              - securityhub:GetSecurityControlDefinition
              - securityhub:DescribeHub
              - securityhub:DescribeStandards
              - securityhub:DescribeStandardsControls
              - securityhub:DescribeProducts
              - securityhub:DescribeActionTargets
              - securityhub:ListEnabledProductsForImport
              - securityhub:ListInvitations
              - securityhub:ListMembers
              - securityhub:ListTagsForResource
              - securityhub:ListFindingAggregators
              - securityhub:ListSecurityControlDefinitions
              - securityhub:ListStandardsControlAssociations
              - securityhub:BatchGetSecurityControls
              - securityhub:BatchGetStandardsControlAssociations
              - securityhub:GetConfigurationPolicy
              - securityhub:GetConfigurationPolicyAssociation
              - securityhub:ListAutomationRules
              - securityhub:ListConfigurationPolicies
              - securityhub:ListConfigurationPolicyAssociations
            Resource: "*"
          - Sid: GuardDutyRead
            Effect: Allow
            Action:
              - guardduty:GetFindings
              - guardduty:ListFindings
              - guardduty:ListDetectors
              - guardduty:GetDetector
              - guardduty:GetFindingsStatistics
              - guardduty:ListMembers
              - guardduty:GetMasterAccount
              - guardduty:GetAdministratorAccount
            Resource: "*"
          - Sid: MacieRead
            Effect: Allow
            Action:
              - macie2:GetFindings
              - macie2:ListFindings
              - macie2:DescribeBuckets
              - macie2:GetFindingStatistics
              - macie2:GetMacieSession
            Resource: "*"
          - Sid: AccessAnalyzerAndDetectiveRead
            Effect: Allow
            Action:
              - access-analyzer:List*
              - access-analyzer:Get*
              - detective:List*
              - detective:Get*
              - account:GetAlternateContact
            Resource: "*"


  #============================================================================
  # Managed Policy 5: SSM Read-Only
  #============================================================================
  SSMReadPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: Transilience-SSM-Read
      Description: SSM read-only access for Transilience compliance
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: SSMInstanceRead
            Effect: Allow
            Action:
              - ssm:DescribeInstanceInformation
              - ssm:DescribeInstanceProperties
              - ssm:GetConnectionStatus
            Resource: "*"
          - Sid: SSMInventoryRead
            Effect: Allow
            Action:
              - ssm:GetInventory
              - ssm:GetInventorySchema
              - ssm:ListInventoryEntries
              - ssm:DescribeInventoryDeletions
            Resource: "*"
          - Sid: SSMPatchRead
            Effect: Allow
            Action:
              - ssm:DescribeInstancePatches
              - ssm:DescribeInstancePatchStates
              - ssm:DescribeInstancePatchStatesForPatchGroup
              - ssm:DescribePatchBaselines
              - ssm:DescribePatchGroups
              - ssm:DescribePatchGroupState
              - ssm:DescribePatchProperties
              - ssm:DescribeAvailablePatches
              - ssm:GetPatchBaseline
              - ssm:GetPatchBaselineForPatchGroup
              - ssm:GetDeployablePatchSnapshotForInstance
            Resource: "*"
          - Sid: SSMCommandRead
            Effect: Allow
            Action:
              - ssm:ListCommands
              - ssm:ListCommandInvocations
              - ssm:GetCommandInvocation
            Resource: "*"
          - Sid: SSMSessionRead
            Effect: Allow
            Action:
              - ssm:DescribeSessions
            Resource: "*"
          - Sid: SSMDocumentRead
            Effect: Allow
            Action:
              - ssm:ListDocuments
              - ssm:ListDocumentVersions
              - ssm:DescribeDocument
              - ssm:DescribeDocumentPermission
              - ssm:GetDocument
            Resource: "*"
          - Sid: SSMParameterRead
            Effect: Allow
            Action:
              - ssm:GetParameter
              - ssm:GetParameters
              - ssm:GetParametersByPath
              - ssm:GetParameterHistory
              - ssm:DescribeParameters
            Resource: "*"
          - Sid: SSMAssociationRead
            Effect: Allow
            Action:
              - ssm:ListAssociations
              - ssm:ListAssociationVersions
              - ssm:DescribeAssociation
              - ssm:DescribeAssociationExecutions
              - ssm:DescribeAssociationExecutionTargets
              - ssm:DescribeEffectiveInstanceAssociations
            Resource: "*"
          - Sid: SSMAutomationRead
            Effect: Allow
            Action:
              - ssm:DescribeAutomationExecutions
              - ssm:GetAutomationExecution
              - ssm:DescribeAutomationStepExecutions
            Resource: "*"
          - Sid: SSMMaintenanceWindowRead
            Effect: Allow
            Action:
              - ssm:DescribeMaintenanceWindows
              - ssm:GetMaintenanceWindow
              - ssm:DescribeMaintenanceWindowTargets
              - ssm:DescribeMaintenanceWindowTasks
              - ssm:GetMaintenanceWindowTask
              - ssm:DescribeMaintenanceWindowExecutions
              - ssm:DescribeMaintenanceWindowExecutionTasks
              - ssm:GetMaintenanceWindowExecution
              - ssm:GetMaintenanceWindowExecutionTask
              - ssm:DescribeMaintenanceWindowSchedule
            Resource: "*"
          - Sid: SSMComplianceRead
            Effect: Allow
            Action:
              - ssm:ListResourceComplianceSummaries
              - ssm:ListComplianceItems
              - ssm:ListComplianceSummaries
            Resource: "*"

  #============================================================================
  # Managed Policy 7: Cost Explorer, Budgets & Billing
  #============================================================================
  CostExplorerPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: Transilience-Cost-Explorer
      Description: Cost Explorer, Budgets, and Billing read access for Transilience
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: CostExplorerRead
            Effect: Allow
            Action:
              - ce:GetCostAndUsage
              - ce:GetCostAndUsageWithResources
              - ce:GetCostForecast
              - ce:GetDimensionValues
              - ce:GetTags
              - ce:GetReservationCoverage
              - ce:GetReservationPurchaseRecommendation
              - ce:GetReservationUtilization
              - ce:GetRightsizingRecommendation
              - ce:GetSavingsPlansCoverage
              - ce:GetSavingsPlansPurchaseRecommendation
              - ce:GetSavingsPlansUtilization
              - ce:GetSavingsPlansUtilizationDetails
              - ce:GetAnomalies
              - ce:GetAnomalyMonitors
              - ce:GetAnomalySubscriptions
              - ce:ListCostAllocationTags
              - ce:ListCostCategoryDefinitions
              - ce:DescribeCostCategoryDefinition
            Resource: "*"
          - Sid: CostUsageReportRead
            Effect: Allow
            Action:
              - cur:DescribeReportDefinitions
              - cur:GetClassicReport
              - cur:GetUsageReport
            Resource: "*"
          - Sid: BudgetsRead
            Effect: Allow
            Action:
              - budgets:ViewBudget
              - budgets:DescribeBudgetAction
              - budgets:DescribeBudgetActionsForAccount
              - budgets:DescribeBudgetActionsForBudget
              - budgets:DescribeBudgetPerformanceHistory
            Resource: "*"
          - Sid: BillingRead
            Effect: Allow
            Action:
              - billing:GetBillingData
              - billing:GetBillingDetails
              - billing:GetBillingNotifications
              - billing:GetBillingPreferences
              - billing:GetContractInformation
              - billing:GetCredits
              - billing:GetIAMAccessPreference
              - billing:GetSellerOfRecord
              - billing:ListBillingViews
            Resource: "*"
  RegistrationLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'TrRegLambdaRole-${AWS::StackName}'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Tags:
        - Key: Purpose
          Value: Transilience-Registration

  #============================================================================
  # Lambda Function to Register with Transilience Backend
  #============================================================================
  RegistrationFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub 'TrRegistration-${AWS::StackName}'
      Description: Registers AWS account with Transilience backend on stack creation
      Runtime: python3.12
      Handler: index.handler
      Role: !GetAtt RegistrationLambdaRole.Arn
      Timeout: 30
      MemorySize: 128
      Environment:
        Variables:
          BACKEND_URL: !Ref TransilienceBackendUrl
          BACKEND_API_KEY: !Ref TransilienceBackendApiKey
      Code:
        ZipFile: |
          import json
          import urllib.request
          import urllib.error
          import os
          import cfnresponse

          def handler(event, context):
              """
              CloudFormation Custom Resource handler.
              Sends account registration data to Transilience backend.
              """
              print(f"Received event: {json.dumps(event)}")
              
              request_type = event.get('RequestType', '')
              
              if request_type == 'Delete':
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                      'Message': 'Delete acknowledged - no action taken'
                  })
                  return
              
              try:
                  properties = event.get('ResourceProperties', {})
                  account_id = properties.get('AccountId')
                  role_arn = properties.get('RoleArn')
                  role_name = properties.get('RoleName')
                  customer_email = properties.get('CustomerEmail')
                  customer_name = properties.get('CustomerName')
                  customer_name = ''.join((customer_name or '').strip().split())
                  stack_id = event.get('StackId', '')
                  region = properties.get('Region')
                  payload = {
                      'account_id': account_id,
                      'role_arn': role_arn,
                      'role_name': role_name,
                      'customer_email': customer_email,
                      'customer_name': customer_name,
                      'stack_id': stack_id,
                      'region': region,
                      'request_type': request_type,
                      'provider': 'aws',
                      'source': 'cloudformation-onboarding'
                  }
                  
                  print(f"Sending payload to backend: {json.dumps(payload)}")
                  
                  backend_url = os.environ.get('BACKEND_URL')
                  backend_api_key = os.environ.get('BACKEND_API_KEY')

                  req = urllib.request.Request(
                      backend_url,
                      data=json.dumps(payload).encode('utf-8'),
                      headers={
                          'Content-Type': 'application/json',
                          'User-Agent': 'Transilience-CloudFormation-Onboarding/1.0',
                          'X-API-Key': backend_api_key
                      },
                      method='POST'
                  )
                  
                  with urllib.request.urlopen(req, timeout=25) as response:
                      response_body = response.read().decode('utf-8')
                      print(f"Backend response: {response_body}")
                      
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                          'Message': 'Successfully registered with Transilience',
                          'AccountId': account_id,
                          'RoleArn': role_arn
                      })
                      
              except urllib.error.HTTPError as e:
                  error_body = e.read().decode('utf-8') if e.fp else str(e)
                  print(f"HTTP Error: {e.code} - {error_body}")
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                      'Message': f'Role created but registration failed: {error_body}',
                      'Warning': 'Please contact support@transilience.ai'
                  })
                  
              except Exception as e:
                  print(f"Error: {str(e)}")
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                      'Message': f'Role created but registration encountered an error: {str(e)}',
                      'Warning': 'Please contact support@transilience.ai'
                  })
      Tags:
        - Key: Purpose
          Value: Transilience-Registration

  #============================================================================
  # Custom Resource to Trigger Registration
  #============================================================================
  AccountRegistration:
    Type: Custom::TransilienceRegistration
    DependsOn:
      - TransilienceComplianceRole
    Properties:
      ServiceToken: !GetAtt RegistrationFunction.Arn
      AccountId: !Ref AWS::AccountId
      RoleArn: !GetAtt TransilienceComplianceRole.Arn
      RoleName: !Ref TransilienceComplianceRole
      CustomerEmail: !Ref CustomerEmail
      CustomerName: !Ref CustomerName
      Region: !Ref AWS::Region
      Timestamp: !Sub "${AWS::StackName}-${AWS::Region}"

#============================================================================
# Outputs
#============================================================================
Outputs:
  AccountId:
    Description: AWS Account ID that was onboarded
    Value: !Ref AWS::AccountId
    Export:
      Name: !Sub "${AWS::StackName}-AccountId"

  RoleArn:
    Description: ARN of the Transilience Compliance Role
    Value: !GetAtt TransilienceComplianceRole.Arn
    Export:
      Name: !Sub "${AWS::StackName}-RoleArn"

  RoleName:
    Description: Name of the Transilience Compliance Role
    Value: !Ref TransilienceComplianceRole
    Export:
      Name: !Sub "${AWS::StackName}-RoleName"

  OIDCProviderArn:
    Description: ARN of the Modal OIDC Provider
    Value: !GetAtt ModalOIDCProvider.Arn
    Export:
      Name: !Sub "${AWS::StackName}-OIDCProviderArn"

  OnboardingStatus:
    Description: Status of the onboarding registration
    Value: !GetAtt AccountRegistration.Message

  PermissionsSummary:
    Description: Summary of permissions granted
    Value: "SecurityAudit, ECR, S3, CloudWatch Logs, VPC & Network, Inspector v2, Security Hub, GuardDuty, Macie, Access Analyzer, Detective, SSM Read, Cost Explorer, Budgets, Billing"
