mirror of
https://github.com/Sea-Haven-Industries/sh-openswe-traces.git
synced 2026-09-30 09:13:16 +00:00
* chore: tag the access-log bucket Trivial resource change to exercise the CI deploy pipeline end-to-end (OIDC deploy role + dedicated sh-openswe-traces-cfn-exec-role). * chore: gitignore .env (CI conventions check)
244 lines
9.3 KiB
YAML
244 lines
9.3 KiB
YAML
AWSTemplateFormatVersion: "2010-09-09"
|
|
Transform: AWS::Serverless-2016-10-31
|
|
Description: >
|
|
sh-openswe-traces — LangSmith Bulk Export destination. Storage-only:
|
|
KMS-encrypted S3 bucket, a least-privilege IAM writer for LangSmith's
|
|
export job, and a Secrets Manager holder for that writer's access key.
|
|
No compute — the export schedule is configured on the LangSmith side.
|
|
|
|
Parameters:
|
|
ExportPrefix:
|
|
Type: String
|
|
Default: langsmith/
|
|
Description: S3 key prefix LangSmith writes exports under (also the lifecycle scope).
|
|
|
|
Resources:
|
|
TracesKey:
|
|
Type: AWS::KMS::Key
|
|
Properties:
|
|
Description: SSE-KMS CMK for sh-openswe-traces (LangSmith export archive).
|
|
EnableKeyRotation: true
|
|
KeyPolicy:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
# Root-enable so IAM identity policies (below) govern access.
|
|
- Sid: EnableIAMPolicies
|
|
Effect: Allow
|
|
Principal:
|
|
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
|
|
Action: "kms:*"
|
|
Resource: "*"
|
|
|
|
TracesKeyAlias:
|
|
Type: AWS::KMS::Alias
|
|
Properties:
|
|
AliasName: alias/sh-openswe-traces
|
|
TargetKeyId: !Ref TracesKey
|
|
|
|
TracesBucket:
|
|
Type: AWS::S3::Bucket
|
|
DeletionPolicy: Retain
|
|
UpdateReplacePolicy: Retain
|
|
# Logging target policy must exist before S3 will accept LoggingConfiguration.
|
|
DependsOn: TracesLogBucketPolicy
|
|
Properties:
|
|
BucketName: sh-openswe-traces
|
|
PublicAccessBlockConfiguration:
|
|
BlockPublicAcls: true
|
|
BlockPublicPolicy: true
|
|
IgnorePublicAcls: true
|
|
RestrictPublicBuckets: true
|
|
BucketEncryption:
|
|
ServerSideEncryptionConfiguration:
|
|
- ServerSideEncryptionByDefault:
|
|
SSEAlgorithm: aws:kms
|
|
KMSMasterKeyID: !Ref TracesKey
|
|
BucketKeyEnabled: true
|
|
OwnershipControls:
|
|
Rules:
|
|
- ObjectOwnership: BucketOwnerEnforced
|
|
# Tamper recovery: the writer key is write-only (no DeleteObject / no
|
|
# DeleteObjectVersion), so a malicious or buggy overwrite creates a noncurrent
|
|
# version the prior bytes are recoverable from. Exports write new partitioned
|
|
# keys, so noncurrent versions are rare — expire them after 90 days.
|
|
VersioningConfiguration:
|
|
Status: Enabled
|
|
LifecycleConfiguration:
|
|
Rules:
|
|
- Id: archive-exports-to-deep-archive
|
|
Status: Enabled
|
|
Prefix: !Ref ExportPrefix
|
|
Transitions:
|
|
- StorageClass: DEEP_ARCHIVE
|
|
TransitionInDays: 90
|
|
- Id: expire-noncurrent-versions
|
|
Status: Enabled
|
|
NoncurrentVersionExpiration:
|
|
NoncurrentDays: 90
|
|
- Id: abort-incomplete-multipart
|
|
Status: Enabled
|
|
AbortIncompleteMultipartUpload:
|
|
DaysAfterInitiation: 7
|
|
LoggingConfiguration:
|
|
DestinationBucketName: !Ref TracesLogBucket
|
|
LogFilePrefix: s3-access/
|
|
|
|
TracesBucketPolicy:
|
|
Type: AWS::S3::BucketPolicy
|
|
Properties:
|
|
Bucket: !Ref TracesBucket
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: DenyInsecureTransport
|
|
Effect: Deny
|
|
Principal: "*"
|
|
Action: "s3:*"
|
|
Resource:
|
|
- !GetAtt TracesBucket.Arn
|
|
- !Sub "${TracesBucket.Arn}/*"
|
|
Condition:
|
|
Bool:
|
|
"aws:SecureTransport": "false"
|
|
# No SSE-header enforcement Deny. LangSmith's exporter does not send an
|
|
# "aws:kms" SSE header, so a "must be aws:kms" Deny blocks its writes — and
|
|
# StringNotEqualsIfExists on a Deny also blocks header-less puts (absent key
|
|
# evaluates true). Encryption is instead guaranteed by the bucket DEFAULT
|
|
# (SSE-KMS with our CMK, applied to every header-less put) plus S3's baseline
|
|
# (no object is ever stored unencrypted). If LangSmith explicitly requests
|
|
# AES256, that object lands as SSE-S3 rather than CMK — verify post-write
|
|
# (head-object) and decide CMK-vs-SSE-S3 if so.
|
|
|
|
# Server access logging target for TracesBucket — read attribution for the
|
|
# secret-bearing archive (the org trail logs no S3 data events). SSE-S3 only:
|
|
# S3 log delivery cannot write to an SSE-KMS bucket.
|
|
TracesLogBucket:
|
|
Type: AWS::S3::Bucket
|
|
DeletionPolicy: Retain
|
|
UpdateReplacePolicy: Retain
|
|
Properties:
|
|
BucketName: sh-openswe-traces-logs
|
|
PublicAccessBlockConfiguration:
|
|
BlockPublicAcls: true
|
|
BlockPublicPolicy: true
|
|
IgnorePublicAcls: true
|
|
RestrictPublicBuckets: true
|
|
BucketEncryption:
|
|
ServerSideEncryptionConfiguration:
|
|
- ServerSideEncryptionByDefault:
|
|
SSEAlgorithm: AES256
|
|
OwnershipControls:
|
|
Rules:
|
|
- ObjectOwnership: BucketOwnerEnforced
|
|
LifecycleConfiguration:
|
|
Rules:
|
|
- Id: expire-access-logs
|
|
Status: Enabled
|
|
ExpirationInDays: 365
|
|
Tags:
|
|
- Key: project
|
|
Value: sh-openswe-traces
|
|
- Key: role
|
|
Value: s3-access-logs
|
|
|
|
TracesLogBucketPolicy:
|
|
Type: AWS::S3::BucketPolicy
|
|
Properties:
|
|
Bucket: !Ref TracesLogBucket
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: S3ServerAccessLogsWrite
|
|
Effect: Allow
|
|
Principal:
|
|
Service: logging.s3.amazonaws.com
|
|
Action: "s3:PutObject"
|
|
Resource: !Sub "${TracesLogBucket.Arn}/s3-access/*"
|
|
Condition:
|
|
ArnLike:
|
|
# literal (not !GetAtt) to avoid a cycle with TracesBucket's DependsOn
|
|
"aws:SourceArn": "arn:aws:s3:::sh-openswe-traces"
|
|
StringEquals:
|
|
"aws:SourceAccount": !Ref "AWS::AccountId"
|
|
- Sid: DenyInsecureTransport
|
|
Effect: Deny
|
|
Principal: "*"
|
|
Action: "s3:*"
|
|
Resource:
|
|
- !GetAtt TracesLogBucket.Arn
|
|
- !Sub "${TracesLogBucket.Arn}/*"
|
|
Condition:
|
|
Bool:
|
|
"aws:SecureTransport": "false"
|
|
|
|
# No explicit UserName: an IAM name would require CAPABILITY_NAMED_IAM, but the
|
|
# standard cd-sam.yaml reusable workflow deploys with CAPABILITY_IAM only. The
|
|
# principal is referenced by ARN (in the secret + LangSmith config), not by name;
|
|
# the tag carries the human-facing identifier.
|
|
LangSmithExportUser:
|
|
Type: AWS::IAM::User
|
|
Properties:
|
|
Tags:
|
|
- Key: Name
|
|
Value: sh-openswe-langsmith-export
|
|
- Key: purpose
|
|
Value: langsmith-bulk-export-writer
|
|
Policies:
|
|
- PolicyName: langsmith-export-put
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
# Bucket-wide (not prefix-scoped): LangSmith's destination-creation
|
|
# validation writes a test object whose key is NOT guaranteed to be
|
|
# under ExportPrefix (docs reference a /tmp path), and its documented
|
|
# policy scopes PutObject to the whole bucket. This bucket is
|
|
# single-purpose, so bucket-wide write is still tightly bounded.
|
|
# Deliberately NO s3:GetObject / s3:DeleteObject (both optional per
|
|
# LangSmith): omitting them keeps the writer write-only (no exfil,
|
|
# no delete). Trade-off: LangSmith skips post-write size verification
|
|
# and leaves its small test object behind (harmless).
|
|
- Sid: PutExportObjects
|
|
Effect: Allow
|
|
Action:
|
|
- "s3:PutObject"
|
|
- "s3:AbortMultipartUpload"
|
|
Resource: !Sub "${TracesBucket.Arn}/*"
|
|
- Sid: EncryptWithBucketKey
|
|
Effect: Allow
|
|
Action:
|
|
- "kms:GenerateDataKey"
|
|
- "kms:Encrypt"
|
|
# Required by S3 at CompleteMultipartUpload for SSE-KMS. Safe: the
|
|
# writer has no s3:GetObject, so there is no object to decrypt/exfil.
|
|
- "kms:Decrypt"
|
|
Resource: !GetAtt TracesKey.Arn
|
|
# Usable only through S3 — blocks a leaked key from calling kms:Decrypt
|
|
# directly against arbitrary ciphertext under this CMK.
|
|
Condition:
|
|
StringEquals:
|
|
"kms:ViaService": !Sub "s3.${AWS::Region}.amazonaws.com"
|
|
|
|
# Holder only — the real access key is minted post-deploy and written in
|
|
# with `aws secretsmanager put-secret-value` (see README). Never in the template.
|
|
ExportKeySecret:
|
|
Type: AWS::SecretsManager::Secret
|
|
Properties:
|
|
Name: sh-openswe/langsmith-export-s3
|
|
Description: >
|
|
Access key for the sh-openswe-langsmith-export IAM user, consumed by
|
|
LangSmith Bulk Export. Populated out-of-band post-deploy; rotate quarterly.
|
|
# Encrypted with the aws/secretsmanager managed key — deliberately NOT the
|
|
# trace CMK, so the credential and the data it protects never share a key the
|
|
# writer principal holds any KMS grant on.
|
|
|
|
Outputs:
|
|
BucketName:
|
|
Value: !Ref TracesBucket
|
|
BucketArn:
|
|
Value: !GetAtt TracesBucket.Arn
|
|
KmsKeyArn:
|
|
Value: !GetAtt TracesKey.Arn
|
|
ExportUserName:
|
|
Value: !Ref LangSmithExportUser
|
|
ExportKeySecretName:
|
|
Value: sh-openswe/langsmith-export-s3
|