Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions cmd/schema-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,32 +462,46 @@ func (sg *SchemaGenerator) createDirectories(resources []ResourceDefinition) err
return nil
}

// guessProviderFromResourceType attempts to determine the provider from the resource type string
// guessProviderFromResourceType attempts to determine the provider from the resource type string.
// Matches against hyphen-delimited tokens so that, for example, "ecr" does not false-match
// "gcp-secrets-manager" (the "ecr" substring in "secrets" mis-routed the entry into the AWS
// index.json with provider:"aws"). Tokens are exact matches on the split parts.
func (sg *SchemaGenerator) guessProviderFromResourceType(resourceType string) string {
tokens := strings.Split(resourceType, "-")
has := func(candidates ...string) bool {
for _, tok := range tokens {
for _, c := range candidates {
if tok == c {
return true
}
}
}
return false
}
switch {
case strings.Contains(resourceType, "aws") || strings.Contains(resourceType, "s3") || strings.Contains(resourceType, "ecr") || strings.Contains(resourceType, "rds") || strings.Contains(resourceType, "ecs") || strings.Contains(resourceType, "lambda"):
case has("aws", "s3", "ecr", "rds", "ecs", "lambda", "fargate"):
return "aws"
case strings.Contains(resourceType, "gcp") || strings.Contains(resourceType, "gcloud") || strings.Contains(resourceType, "gke") || strings.Contains(resourceType, "cloudrun") || strings.Contains(resourceType, "artifact") || strings.Contains(resourceType, "pubsub"):
case has("gcp", "gcloud", "gke", "cloudrun", "artifact", "pubsub"):
return "gcp"
case strings.Contains(resourceType, "k8s") || strings.Contains(resourceType, "kubernetes") || strings.Contains(resourceType, "caddy"):
case has("k8s", "kubernetes", "caddy"):
return "kubernetes"
case strings.Contains(resourceType, "mongodb") || strings.Contains(resourceType, "atlas"):
case has("mongodb", "atlas"):
return "mongodb"
case strings.Contains(resourceType, "cloudflare"):
case has("cloudflare"):
return "cloudflare"
case strings.Contains(resourceType, "fs") || strings.Contains(resourceType, "passphrase"):
case has("fs", "passphrase"):
return "fs"
case strings.Contains(resourceType, "compose"):
case has("compose"):
return "compose"
case strings.Contains(resourceType, "docker"):
case has("docker"):
return "docker"
case strings.Contains(resourceType, "github"):
case has("github"):
return "github"
case strings.Contains(resourceType, "discord"):
case has("discord"):
return "discord"
case strings.Contains(resourceType, "slack"):
case has("slack"):
return "slack"
case strings.Contains(resourceType, "telegram"):
case has("telegram"):
return "telegram"
default:
return "unknown"
Expand Down
59 changes: 59 additions & 0 deletions docs/docs/reference/supported-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,65 @@ When this resource is used in a client stack via the `uses` section, Simple Cont

📖 **For complete details on environment variables and template placeholders, see:** [Template Placeholders Advanced - AWS RDS MySQL](../concepts/template-placeholders-advanced.md#rds-mysql)

#### **CloudTrail Security Alerts** (`aws-cloudtrail-security-alerts`)

Creates CloudWatch metric filters and alarms for security-relevant CloudTrail events, aligned with the AWS Security Hub/CIS CloudWatch controls (CloudWatch.1 through CloudWatch.14).

**Golang Struct Reference:** `pkg/clouds/aws/cloudtrail_security_alerts.go:CloudTrailSecurityAlertsConfig`

```yaml
# server.yaml - Parent Stack
resources:
resources:
production:
resources:
cloudtrail-security:
type: aws-cloudtrail-security-alerts
config:
# AWS account configuration (inherited from AccountConfig)
credentials: "${auth:aws-us}"
account: "${auth:aws-us.projectId}"

# CloudTrail log group (required)
logGroupName: "aws-cloudtrail-logs-s3-buckets"
logGroupRegion: "us-west-2" # Optional: if different from default region

# Notification channels — any combination, or none
email:
addresses:
- security@company.com
slack:
webhookUrl: "${secret:security-slack-webhook}"
# discord:
# webhookUrl: "${secret:security-discord-webhook}"
# telegram:
# chatID: "-1001234567890"
# token: "${secret:security-telegram-token}"

# Alert selectors (all default to false)
alerts:
rootAccountUsage: true # CIS CloudWatch.1
unauthorizedApiCalls: true # CIS CloudWatch.2 (threshold: 5)
consoleLoginWithoutMfa: true # CIS CloudWatch.3
iamPolicyChanges: true # CIS CloudWatch.4
cloudTrailTampering: true # CIS CloudWatch.5
failedConsoleLogins: true # CIS CloudWatch.6
kmsKeyDeletion: true # CIS CloudWatch.7
s3BucketPolicyChanges: true # CIS CloudWatch.8
configChanges: true # CIS CloudWatch.9
securityGroupChanges: true # CIS CloudWatch.10
naclChanges: true # CIS CloudWatch.11
networkGatewayChanges: true # CIS CloudWatch.12
routeTableChanges: true # CIS CloudWatch.13
vpcChanges: true # CIS CloudWatch.14
```

**Compliance:** SOC 2 (CC6/CC7), ISO 27001:2022 (A.5/A.8), NIST 800-53 (AU-6, AC-2, SI-4)

**Scope:** CloudTrail log groups are account-wide. Declare this resource in **exactly one environment block per AWS account** — declaring it in multiple environments (e.g. `staging` and `prod`) that target the same account produces duplicate metric filters that all match the same events, leading to duplicate notifications. Multiple accounts (e.g. EU vs US) get independent alert sets, which is supported.

**Note:** Does not require `uses` in client stacks — the resource monitors the CloudTrail log group directly.

### **Authentication** (`AuthType` → `auth` section in `secrets.yaml`)

#### **AWS Token Authentication** (`aws-token`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# CloudTrail Security Alerts Implementation Summary

## Implementation Complete

New resource type `aws-cloudtrail-security-alerts` that creates CloudWatch metric filters and alarms for security-relevant CloudTrail events. Covers the full AWS Security Hub/CIS CloudWatch control set (CloudWatch.1-14).

## What Was Implemented

### New Resource Type

Users can define security alerts in their `server.yaml`:

```yaml
resources:
resources:
prod:
resources:
cloudtrail-security:
type: aws-cloudtrail-security-alerts
config:
logGroupName: aws-cloudtrail-logs-s3-buckets
logGroupRegion: us-west-2
email:
addresses:
- security@company.com
alerts:
rootAccountUsage: true
unauthorizedApiCalls: true
consoleLoginWithoutMfa: true
iamPolicyChanges: true
cloudTrailTampering: true
failedConsoleLogins: true
kmsKeyDeletion: true
s3BucketPolicyChanges: true
configChanges: true
securityGroupChanges: true
naclChanges: true
networkGatewayChanges: true
routeTableChanges: true
vpcChanges: true
```

### 14 CIS-Aligned Alerts

| CIS Control | Alert | Default Threshold |
|-------------|-------|-------------------|
| CloudWatch.1 | Root account usage | >= 1 / 5min |
| CloudWatch.2 | Unauthorized API calls | >= 5 / 5min |
| CloudWatch.3 | Console login without MFA (success only) | >= 1 / 5min |
| CloudWatch.4 | IAM policy changes | >= 1 / 5min |
| CloudWatch.5 | CloudTrail config changes | >= 1 / 5min |
| CloudWatch.6 | Failed console logins | >= 1 / 5min |
| CloudWatch.7 | KMS key deletion/disable | >= 1 / 5min |
| CloudWatch.8 | S3 bucket policy changes | >= 1 / 5min |
| CloudWatch.9 | AWS Config changes | >= 1 / 5min |
| CloudWatch.10 | Security group changes | >= 1 / 5min |
| CloudWatch.11 | NACL changes | >= 1 / 5min |
| CloudWatch.12 | Network gateway changes | >= 1 / 5min |
| CloudWatch.13 | Route table changes | >= 1 / 5min |
| CloudWatch.14 | VPC changes | >= 1 / 5min |

### Code Changes Summary

| File | Change | Lines |
|------|--------|-------|
| `pkg/clouds/aws/cloudtrail_security_alerts.go` | Config struct, selectors, reader | +41 |
| `pkg/clouds/aws/cloudtrail_security_alerts_test.go` | Config parsing tests | +151 |
| `pkg/clouds/aws/init.go` | Register config reader | +3 |
| `pkg/clouds/pulumi/aws/cloudtrail_security_alerts.go` | Pulumi provisioner (metric filters + alarms) | +310 |
| `pkg/clouds/pulumi/aws/cloudtrail_security_alerts_test.go` | Alert selection + definition tests | +120 |
| `pkg/clouds/pulumi/aws/init.go` | Register provisioner | +2 |

**Total:** 6 files, ~627 lines

## Architecture

Each enabled alert creates:
1. A CloudWatch LogMetricFilter on the specified CloudTrail log group
2. A CloudWatch MetricAlarm (Sum >= threshold in 5 min period)
3. Optional SNS topic + email subscriptions for notifications

### Cross-Region Support

CloudTrail log groups may be in a different region than the main SC deployment. When `logGroupRegion` is specified, a region-specific AWS provider is created with credentials from `AccountConfig`.

### Resource Naming

Resource names are derived from the descriptor name (not hardcoded), supporting multiple instances per stack.

### Notification Channels

- Email: via SNS topic + SNS email subscriptions
- Slack / Discord / Telegram: via SC helpers Lambda (same image used by ECS ALB alerts).
Each enabled alert gets its own Lambda (deterministic per-alert env vars for
AlertName/AlertDescription), which pulls the webhook URL from Secrets Manager
and formats the alarm payload for the target channel. Channels can be combined
with email for dual delivery.

The helpers image is pushed into an ECR repo namespaced by the SC resource
descriptor name (`<resPrefix>-security-helpers`), so the CloudTrail security
alerts resource can coexist with compute-stack ALB alerts that already use
`sc-cloud-helpers` without URN or ECR-repo collisions. When `logGroupRegion`
is set, the helpers image is pushed into that region so the Lambda can pull
from same-region ECR.

IAM role names (`<resPrefix>-<alert>-execution-role-<pulumi-suffix>`) are
capped via `util.TrimStringMiddle(..., 38, "-")` to stay under AWS's 64-char
IAM role name limit for the longer CIS alert names.

## Compliance Coverage

- SOC 2: CC6.1, CC6.5, CC6.6, CC7.1, CC7.2, CC7.3
- ISO 27001:2022: A.5.16, A.5.18, A.8.2, A.8.5, A.8.15, A.8.16, A.8.20, A.8.22, A.8.24, A.8.32
- NIST 800-53: AU-6, AC-2, AC-6, SI-4
- AWS CIS Benchmark: CloudWatch.1 through CloudWatch.14

## Testing Results

All tests passing (config parsing, alert selection, deterministic ordering, definition validation, unique names).

## Review History

- Self-review: 3 rounds
- OpenAI Codex code review: fixed credential pass-through, naming collisions, MFA filter scope
- OpenAI Codex compliance gap analysis: expanded 7 -> 14 alerts, corrected filter patterns to match CIS reference
- Post-Slack-wiring review: fixed `createSNSTopicForAlerts` tags-arg signature drift;
fixed IAM role name overflow for CIS alerts with longer names (TrimStringMiddle cap);
fixed cross-region ECR so the helpers Lambda pulls from same-region ECR
Loading
Loading