This document covers security best practices for the Kro Serverless Application Stack.
- Security Overview
- IAM Configuration
- Cognito Authentication
- API Gateway Security
- S3 Bucket Security
- Data Encryption
- Security Checklist
The Kro Serverless Application Stack implements defense-in-depth security:
┌─────────────────────────────────────────────────────────────────┐
│ Security Layers │
├─────────────────────────────────────────────────────────────────┤
│ Layer 1: Network Security │
│ ├── CloudFront with HTTPS only │
│ ├── API Gateway with TLS 1.2+ │
│ └── VPC endpoints (optional) │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: Authentication │
│ ├── Cognito User Pool │
│ ├── JWT token validation │
│ └── OAuth 2.0 authorization code flow │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: Authorization │
│ ├── IAM roles with least privilege │
│ ├── Cognito groups for RBAC │
│ └── Resource-based policies │
├─────────────────────────────────────────────────────────────────┤
│ Layer 4: Data Protection │
│ ├── DynamoDB encryption at rest │
│ ├── S3 server-side encryption │
│ └── TLS in transit │
└─────────────────────────────────────────────────────────────────┘
All IAM roles follow least privilege principles:
# Minimal permissions for Lambda function
PolicyDocument:
Version: "2012-10-17"
Statement:
# DynamoDB access - specific table only
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
Resource:
- "arn:aws:dynamodb:*:*:table/${TABLE_NAME}"
- "arn:aws:dynamodb:*:*:table/${TABLE_NAME}/index/*"
# CloudWatch Logs - function-specific
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- "arn:aws:logs:*:*:log-group:/aws/lambda/${FUNCTION_NAME}:*"# Limited to specific bucket and prefix
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:ListBucket
Resource:
- "arn:aws:s3:::${BUCKET_NAME}"
- "arn:aws:s3:::${BUCKET_NAME}/${PREFIX}/*"Kubernetes ServiceAccounts are linked to IAM roles:
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-uploader-sa
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::ACCOUNT:role/s3-uploader-role"Trust Policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT:oidc-provider/OIDC_PROVIDER"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"OIDC_PROVIDER:sub": "system:serviceaccount:NAMESPACE:SA_NAME"
}
}
}]
}# Secure User Pool settings
spec:
forProvider:
# Password policy
policies:
passwordPolicy:
minimumLength: 12
requireLowercase: true
requireUppercase: true
requireNumbers: true
requireSymbols: true
temporaryPasswordValidityDays: 7
# MFA configuration
mfaConfiguration: OPTIONAL
# Account recovery
accountRecoverySetting:
recoveryMechanisms:
- name: verified_email
priority: 1
# User verification
autoVerifiedAttributes:
- email# Secure App Client settings
spec:
forProvider:
# Generate client secret
generateSecret: true
# OAuth flows
allowedOAuthFlows:
- code # Authorization code flow (most secure)
# Prevent implicit flow in production
allowedOAuthFlowsUserPoolClient: true
# Token validity
idTokenValidity: 60 # 1 hour
accessTokenValidity: 60 # 1 hour
refreshTokenValidity: 30 # 30 days
tokenValidityUnits:
idToken: minutes
accessToken: minutes
refreshToken: days
# Callback URLs (use HTTPS only in production)
callbackURLs:
- "https://*.example.com/callback"
logoutURLs:
- "https://*.example.com"# Define groups with precedence
groups:
- name: platform-admins
precedence: 1
description: Full platform administration
- name: developers
precedence: 2
description: Application developers
- name: read-only
precedence: 3
description: Read-only access# API Gateway JWT Authorizer
apiVersion: apigatewayv2.services.k8s.aws/v1alpha1
kind: Authorizer
spec:
apiID: ${API_ID}
authorizerType: JWT
identitySource:
- "$request.header.Authorization"
jwtConfiguration:
issuer: "https://cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}"
audience:
- "${CLIENT_ID}"# Protected routes require JWT
apiVersion: apigatewayv2.services.k8s.aws/v1alpha1
kind: Route
spec:
apiID: ${API_ID}
routeKey: "GET /items"
authorizationType: JWT
authorizerID: ${AUTHORIZER_ID}# Restrictive CORS policy
corsConfiguration:
allowOrigins:
- "https://*.example.com"
allowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowHeaders:
- Authorization
- Content-Type
maxAge: 300CloudFront accesses S3 through OAC, not public access:
# OAC configuration
apiVersion: cloudfront.services.k8s.aws/v1alpha1
kind: OriginAccessControl
spec:
originAccessControlConfig:
name: "hkc-sample-oac"
signingBehavior: always
signingProtocol: sigv4
originAccessControlOriginType: s3{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT:distribution/DIST_ID"
}
}
}]
}# Block all public access
publicAccessBlockConfiguration:
blockPublicAcls: true
blockPublicPolicy: true
ignorePublicAcls: true
restrictPublicBuckets: true# Server-side encryption enabled by default
spec:
sseSpecification:
enabled: true
sseType: KMS # Use AWS managed key or CMK# Server-side encryption
spec:
serverSideEncryptionConfiguration:
rules:
- applyServerSideEncryptionByDefault:
sseAlgorithm: AES256
# Or use KMS:
# sseAlgorithm: aws:kms
# kmsMasterKeyID: "arn:aws:kms:..."- CloudFront: TLS 1.2+ enforced
- API Gateway: TLS 1.2+ enforced
- Lambda to DynamoDB: TLS by default
- Lambda to S3: TLS by default
- ACM certificates issued and validated
- Route 53 hosted zone configured
- OIDC provider configured for EKS
- IAM roles created with least privilege
- Cognito User Pool configured with strong password policy
- Environment variables secured (not in Git)
- Secrets stored in Kubernetes Secrets or AWS Secrets Manager
- HTTPS enforced for all endpoints
- JWT authorizer configured for API Gateway
- S3 bucket policies restrict access to CloudFront only
- CloudWatch Logs enabled for Lambda
- API Gateway access logging enabled
- CloudFront access logging enabled (optional)
- Cognito user activity monitored
- IAM Access Analyzer reviewed
- Regular security audits
- Dependency updates
- Certificate renewal monitoring
- Access key rotation (if applicable)
- User access reviews
# Add to .gitignore
scripts/env.sh
*.env
*.secret# Store secrets
aws secretsmanager create-secret \
--name "hkc-sample/cognito-client-secret" \
--secret-string "YOUR_SECRET"# Monitor API calls
aws cloudtrail create-trail \
--name hkc-sample-trail \
--s3-bucket-name your-cloudtrail-bucket# Web Application Firewall for CloudFront
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
spec:
scope: CLOUDFRONT
defaultAction:
allow: {}
rules:
- name: AWSManagedRulesCommonRuleSet
priority: 1
overrideAction:
none: {}
statement:
managedRuleGroupStatement:
vendorName: AWS
name: AWSManagedRulesCommonRuleSet- Review IAM policies quarterly
- Audit Cognito user access monthly
- Check for unused resources
- Update dependencies regularly
- Enable CloudTrail logging
- Implement access controls
- Document security procedures
- Implement data retention policies
- Enable encryption at rest
- Document data processing
- Use KMS for encryption
- Enable audit logging
- Implement access controls