From a5fa0b16a3973b60ca2cdb8d872d2f5b832480b6 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:41:12 -0400 Subject: [PATCH] feat(iam): add hcptf roles and boundary for procurement-ingest (PLAT-86) (#85) * feat(iam): add hcptf roles and boundary for procurement-ingest * fix(iam): tighten procurement-ingest apply and plan scopes Replace kms:* and secret-value writes on shell statements; split IAM collection APIs onto Resource "*". --- .../deploy-substrate.template.yaml | 124 +++++- .../terraform-substrate.template.yaml | 419 ++++++++++++++++++ 2 files changed, 539 insertions(+), 4 deletions(-) diff --git a/lib/deploy-substrate/deploy-substrate.template.yaml b/lib/deploy-substrate/deploy-substrate.template.yaml index 8a13b89..7558b6d 100644 --- a/lib/deploy-substrate/deploy-substrate.template.yaml +++ b/lib/deploy-substrate/deploy-substrate.template.yaml @@ -541,12 +541,128 @@ Resources: - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/front-sla-alerts/index/*" - !Ref AWS::NoValue + # ── procurement-ingest (PLAT-86) — prod-only exact secrets + data plane ─ + # Derived from live CDK stacks in seahaven-prod 2026-08-06. Exact secret + # ARNs only (no secret:procurement-* / workorder-ingest/* wildcards). + - !If + - IsProdAccount + - Sid: ProcurementIngestSecrets + Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: + - arn:aws:secretsmanager:us-east-1:011934824531:secret:procurement-ingest/web-ui-auth-token-ApAMmr + - arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: ProcurementIngestDynamoDB + Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + - dynamodb:Query + - dynamodb:Scan + - dynamodb:BatchGetItem + - dynamodb:BatchWriteItem + - dynamodb:DescribeTable + - dynamodb:ConditionCheckItem + - dynamodb:GetRecords + - dynamodb:GetShardIterator + - dynamodb:DescribeStream + # ListStreams is a collection API (Resource "*"); ARN-scoping + # it is a silent no-op. Runtime stream consumers use the + # stream ARN via DescribeStream/GetRecords above. + Resource: + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrders" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrders/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrderComments" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrderComments/*" + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: ProcurementIngestS3 + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + - s3:PutObject* + - s3:DeleteObject* + - s3:AbortMultipartUpload + Resource: + - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}" + - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}/*" + - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}" + - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}/*" + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: ProcurementIngestSqs + Effect: Allow + Action: + - sqs:SendMessage + - sqs:ReceiveMessage + - sqs:DeleteMessage + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:ChangeMessageVisibility + Resource: + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:po-ingest-*" + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:WorkorderIngestStack-*" + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:workorder-shoc-emitter-*" + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: ProcurementIngestKms + Effect: Allow + Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:GenerateDataKey* + - kms:ReEncrypt* + Resource: + - arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12 + - arn:aws:kms:us-east-1:011934824531:key/d10fd1f0-a61a-4405-8568-85e9fd11ba18 + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: ProcurementIngestBedrock + Effect: Allow + Action: + - bedrock:InvokeModel + - bedrock:InvokeModelWithResponseStream + Resource: + - !Sub "arn:aws:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.anthropic.claude-haiku-4-5-20251001-v1:0" + - arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 + - arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 + - arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: ProcurementIngestSns + Effect: Allow + Action: + - sns:Publish + Resource: + - !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:site-alerts" + - !Ref AWS::NoValue + # ── FURTHER PER-WORKLOAD DATA-PLANE STATEMENTS ────────────────────── # Floor above + afi-backup-monitor (PLAT-56) + front-integrations - # (PLAT-72). Additional stacks add their own statements here, derived - # from THEIR OWN template, in their own PR, deployed to - # UPDATE_COMPLETE before first workload deploy. WIDENING PATH in the - # header still governs. + # (PLAT-72) + procurement-ingest (PLAT-86). Additional stacks add + # their own statements here, derived from THEIR OWN template, in + # their own PR, deployed to UPDATE_COMPLETE before first workload + # deploy. WIDENING PATH in the header still governs. # # WHY the floor stayed empty of data-plane (decided 2026-07-30, Adam): # The security win of INFRA-186 comes from DELETION, not enumeration. diff --git a/lib/terraform-substrate/terraform-substrate.template.yaml b/lib/terraform-substrate/terraform-substrate.template.yaml index acbbf81..59ed71e 100644 --- a/lib/terraform-substrate/terraform-substrate.template.yaml +++ b/lib/terraform-substrate/terraform-substrate.template.yaml @@ -941,3 +941,422 @@ Resources: Condition: StringEquals: "secretsmanager:Name": sh-openswe/langsmith-export-s3 + + # --------------------------------------------------------------------------- + # procurement-ingest (PLAT-86) — plan + apply roles for workspace + # procurement-ingest-prod. Import-in-place of three former CDK stacks + # (po-ingest, WorkorderIngestStack, procurement-api). Copy shape from + # front-integrations; extend for S3 email buckets, SQS, SES receipt rules, + # API Gateway, KMS (SHOC + DynamoDB CMK manage), Secrets Manager shell/ + # rotation, DynamoDB streams, and prefix-scoped Lambda/alarms/log groups. + # --------------------------------------------------------------------------- + HcptfProcurementIngestPlanRole: + Type: AWS::IAM::Role + Condition: IsProdAccount + Properties: + RoleName: hcptf-procurement-ingest-plan + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + "app.terraform.io:aud": aws.workload.identity + "app.terraform.io:sub": organization:seahaven:project:seahaven-prod:workspace:procurement-ingest-prod:run_phase:plan + ManagedPolicyArns: + - arn:aws:iam::aws:policy/job-function/ViewOnlyAccess + Policies: + - PolicyName: procurement-ingest-plan-refresh + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: RefreshIamRoles + Effect: Allow + Action: + - iam:GetRole + - iam:GetRolePolicy + - iam:ListRolePolicies + - iam:ListAttachedRolePolicies + Resource: + - !Sub "arn:aws:iam::${AWS::AccountId}:role/tf-managed/po-*" + - !Sub "arn:aws:iam::${AWS::AccountId}:role/tf-managed/workorder-*" + - !Sub "arn:aws:iam::${AWS::AccountId}:role/tf-managed/procurement-api" + - Sid: RefreshManagedPolicies + Effect: Allow + Action: + - iam:GetPolicy + - iam:GetPolicyVersion + Resource: "*" + - Sid: RefreshLambda + Effect: Allow + Action: + - lambda:Get* + Resource: + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:po-*" + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:workorder-*" + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:procurement-api" + # Collection/list APIs authorize only against Resource "*". + - Sid: RefreshLambdaList + Effect: Allow + Action: + - lambda:ListFunctions + - lambda:ListEventSourceMappings + - lambda:GetAccountSettings + Resource: "*" + - Sid: RefreshArtifactsBucket + Effect: Allow + Action: + - s3:Get* + - s3:ListBucket + Resource: + - !Sub "arn:aws:s3:::procurement-ingest-artifacts-${AWS::AccountId}" + - !Sub "arn:aws:s3:::procurement-ingest-artifacts-${AWS::AccountId}/*" + - Sid: RefreshEmailBuckets + Effect: Allow + Action: + - s3:Get* + - s3:ListBucket + - s3:GetBucketNotification + - s3:GetBucketPolicy + - s3:GetEncryptionConfiguration + - s3:GetBucketTagging + - s3:GetBucketVersioning + - s3:GetBucketPublicAccessBlock + Resource: + - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}" + - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}/*" + - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}" + - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}/*" + - Sid: RefreshDynamoDB + Effect: Allow + Action: + - dynamodb:DescribeTable + - dynamodb:DescribeTimeToLive + - dynamodb:DescribeContinuousBackups + - dynamodb:DescribeStream + - dynamodb:ListTagsOfResource + Resource: + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrders" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrders/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrderComments" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrderComments/*" + - Sid: RefreshDynamoDBList + Effect: Allow + Action: + - dynamodb:ListStreams + - dynamodb:ListTables + Resource: "*" + - Sid: RefreshSqs + Effect: Allow + Action: + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:ListQueueTags + Resource: + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:po-ingest-*" + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:WorkorderIngestStack-*" + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:workorder-shoc-emitter-*" + - Sid: RefreshCloudWatchAlarms + Effect: Allow + Action: + - cloudwatch:DescribeAlarms + - cloudwatch:ListTagsForResource + Resource: + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:po-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:workorder-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:procurement-api-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:purchase-orders-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:verified-sites-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:pending-site-review-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:WorkOrders-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:WorkOrderComments-*" + - Sid: RefreshApiGateway + Effect: Allow + Action: + - apigateway:GET + Resource: + - !Sub "arn:aws:apigateway:us-east-1::/restapis/mvul1efda2" + - !Sub "arn:aws:apigateway:us-east-1::/restapis/mvul1efda2/*" + - arn:aws:apigateway:us-east-1::/domainnames/procurement-api.seahaven.com + - arn:aws:apigateway:us-east-1::/domainnames/procurement-api.seahaven.com/* + - Sid: RefreshKms + Effect: Allow + Action: + - kms:DescribeKey + - kms:GetKeyPolicy + - kms:GetKeyRotationStatus + - kms:ListResourceTags + - kms:ListAliases + Resource: + - !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:key/*" + - !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:alias/workorder-ingest-shoc-webhook-kms" + - Sid: RefreshSecrets + Effect: Allow + Action: + - secretsmanager:DescribeSecret + - secretsmanager:GetResourcePolicy + - secretsmanager:ListSecretVersionIds + Resource: + - !Sub "arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:workorder-ingest/shoc-webhook-hmac-*" + - !Sub "arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:procurement-ingest/web-ui-auth-token-*" + - Sid: RefreshSes + Effect: Allow + Action: + - ses:DescribeReceiptRule + - ses:DescribeReceiptRuleSet + Resource: "*" + - Sid: RefreshLogs + Effect: Allow + Action: + - logs:DescribeLogGroups + - logs:DescribeMetricFilters + - logs:ListTagsForResource + Resource: "*" + + HcptfProcurementIngestApplyRole: + Type: AWS::IAM::Role + Condition: IsProdAccount + Properties: + RoleName: hcptf-procurement-ingest + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + "app.terraform.io:aud": aws.workload.identity + "app.terraform.io:sub": organization:seahaven:project:seahaven-prod:workspace:procurement-ingest-prod:run_phase:apply + ManagedPolicyArns: + - !Ref HcptfIamManagementPolicy + Policies: + - PolicyName: procurement-ingest-services + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: LambdaAll + Effect: Allow + Action: + - lambda:* + Resource: + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:po-*" + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:workorder-*" + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:procurement-api" + # Event source mapping ARNs are UUID-keyed; AWS authorises Create against + # FunctionArn. Get/Update/Delete also take the mapping ARN, so Resource + # stays "*" but FunctionArn is constrained to this stack's functions. + - Sid: LambdaEventSourceMappings + Effect: Allow + Action: + - lambda:CreateEventSourceMapping + - lambda:DeleteEventSourceMapping + - lambda:UpdateEventSourceMapping + - lambda:GetEventSourceMapping + - lambda:ListTags + - lambda:TagResource + - lambda:UntagResource + Resource: "*" + Condition: + "ForAnyValue:StringLike": + "lambda:FunctionArn": + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:po-*" + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:workorder-*" + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:procurement-api" + - Sid: LambdaList + Effect: Allow + Action: + - lambda:ListFunctions + - lambda:ListEventSourceMappings + - lambda:GetAccountSettings + Resource: "*" + - Sid: CloudWatchLogs + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:DeleteLogGroup + - logs:PutRetentionPolicy + - logs:DeleteRetentionPolicy + - logs:TagResource + - logs:UntagResource + - logs:ListTagsForResource + - logs:PutMetricFilter + - logs:DeleteMetricFilter + - logs:DescribeMetricFilters + Resource: + - !Sub "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/lambda/po-*" + - !Sub "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/lambda/workorder-*" + - !Sub "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/lambda/procurement-api*" + - Sid: CloudWatchLogsDescribe + Effect: Allow + Action: + - logs:DescribeLogGroups + Resource: "*" + - Sid: ArtifactsBucket + Effect: Allow + Action: + - s3:* + Resource: + - !Sub "arn:aws:s3:::procurement-ingest-artifacts-${AWS::AccountId}" + - !Sub "arn:aws:s3:::procurement-ingest-artifacts-${AWS::AccountId}/*" + - Sid: EmailBuckets + Effect: Allow + Action: + - s3:* + Resource: + - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}" + - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}/*" + - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}" + - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}/*" + - Sid: DynamoDBTables + Effect: Allow + Action: + - dynamodb:* + Resource: + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrders" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrders/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrderComments" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/WorkOrderComments/*" + - Sid: DynamoDBList + Effect: Allow + Action: + - dynamodb:ListTables + - dynamodb:ListStreams + Resource: "*" + - Sid: SqsQueues + Effect: Allow + Action: + - sqs:* + Resource: + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:po-ingest-*" + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:WorkorderIngestStack-*" + - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:workorder-shoc-emitter-*" + - Sid: CloudWatchAlarms + Effect: Allow + Action: + - cloudwatch:* + Resource: + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:po-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:workorder-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:procurement-api-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:purchase-orders-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:verified-sites-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:pending-site-review-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:WorkOrders-*" + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:WorkOrderComments-*" + - Sid: SiteAlertsSns + Effect: Allow + Action: + - sns:Publish + - sns:GetTopicAttributes + - sns:ListTagsForResource + Resource: + - !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:site-alerts" + - Sid: ApiGateway + Effect: Allow + Action: + - apigateway:* + Resource: + - !Sub "arn:aws:apigateway:us-east-1::/restapis/mvul1efda2" + - !Sub "arn:aws:apigateway:us-east-1::/restapis/mvul1efda2/*" + - arn:aws:apigateway:us-east-1::/domainnames/procurement-api.seahaven.com + - arn:aws:apigateway:us-east-1::/domainnames/procurement-api.seahaven.com/* + - Sid: SesReceiptRules + Effect: Allow + Action: + - ses:CreateReceiptRule + - ses:UpdateReceiptRule + - ses:DeleteReceiptRule + - ses:DescribeReceiptRule + - ses:SetReceiptRulePosition + Resource: + - !Sub "arn:aws:ses:us-east-1:${AWS::AccountId}:receipt-rule-set/INBOUND_MAIL:receipt-rule/ExistingRuleSetPoEmailRuleAC8E9C87-qwGDj9lBoL1G" + - !Sub "arn:aws:ses:us-east-1:${AWS::AccountId}:receipt-rule-set/INBOUND_MAIL:receipt-rule/ExistingRuleSetWorkorderEmailRuleEA29F845-PKtaDBvIg61a" + - Sid: SesDescribeRuleSet + Effect: Allow + Action: + - ses:DescribeReceiptRuleSet + Resource: "*" + # Key management only on the live SHOC CMK — no kms:* (excludes + # unconstrained key-policy/destructive ops on other keys and + # avoids data-plane Encrypt/Decrypt on the apply role). + - Sid: ShocKms + Effect: Allow + Action: + - kms:DescribeKey + - kms:GetKeyPolicy + - kms:GetKeyRotationStatus + - kms:ListResourceTags + - kms:PutKeyPolicy + - kms:EnableKeyRotation + - kms:DisableKeyRotation + - kms:ScheduleKeyDeletion + - kms:CancelKeyDeletion + - kms:TagResource + - kms:UntagResource + - kms:EnableKey + - kms:DisableKey + Resource: + - !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:key/d10fd1f0-a61a-4405-8568-85e9fd11ba18" + - Sid: KmsList + Effect: Allow + Action: + - kms:ListAliases + - kms:ListKeys + Resource: "*" + - Sid: ShocKmsAlias + Effect: Allow + Action: + - kms:CreateAlias + - kms:DeleteAlias + - kms:UpdateAlias + Resource: + - !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:alias/workorder-ingest-shoc-webhook-kms" + - !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:key/d10fd1f0-a61a-4405-8568-85e9fd11ba18" + # Shell lifecycle only — no CreateSecret / UpdateSecret so apply + # never writes SecretString into HCP state or run logs. Create is + # isolated in ShocSecretCreate with an exact Name pin. + - Sid: ShocSecretShell + Effect: Allow + Action: + - secretsmanager:DeleteSecret + - secretsmanager:DescribeSecret + - secretsmanager:GetResourcePolicy + - secretsmanager:PutResourcePolicy + - secretsmanager:DeleteResourcePolicy + - secretsmanager:TagResource + - secretsmanager:UntagResource + - secretsmanager:RotateSecret + - secretsmanager:CancelRotateSecret + - secretsmanager:UpdateSecretVersionStage + Resource: + - !Sub "arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:workorder-ingest/shoc-webhook-hmac-*" + - Sid: ShocSecretCreate + Effect: Allow + Action: + - secretsmanager:CreateSecret + Resource: "*" + Condition: + StringEquals: + "secretsmanager:Name": workorder-ingest/shoc-webhook-hmac + - Sid: WebUiSecretDescribe + Effect: Allow + Action: + - secretsmanager:DescribeSecret + - secretsmanager:GetResourcePolicy + Resource: + - !Sub "arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:procurement-ingest/web-ui-auth-token-*"