About s3-bucket-policy
s3-bucket-policy is a guidance skill for authoring, reviewing, and improving AWS S3 bucket policies. It covers access-control patterns, security conditions, policy validation, and troubleshooting.
Key features
- 01Least-privilege policy guidance
- 02Public and cross-account access patterns
- 03CloudFront and CloudTrail policy examples
- 04Encryption, IP, VPC, and MFA restrictions
- 05Condition key reference
- 06Policy validation and troubleshooting
Use cases
- 01Draft a bucket policy for a static website
- 02Configure cross-account bucket access
- 03Restrict access by IP or VPC endpoint
- 04Investigate S3 AccessDenied errors
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ dengineproblem/ agents-monorepo --skill s3-bucket-policyAfter installing, type/s3-bucket-policyin Claude Code to confirm it loaded.
Details
From the project READMEAWS S3 Bucket Policy Expert
Expert guidance on creating, analyzing, and optimizing AWS S3 bucket policies with focus on security, access control, and compliance.
Policy Structure
{
"Version": "2012-10-17",
"Id": "PolicyIdentifier",
"Statement": [
{
"Sid": "StatementIdentifier",
"Effect": "Allow | Deny",
"Principal": {
"AWS": "arn:aws:iam::account-id:root"
},
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
],
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
Core Principles
security_principles:
least_privilege:
description: "Grant only minimum necessary permissions"
practice: "Start with deny all, add specific allows"
explicit_deny:
description: "Deny always overrides Allow"
practice: "Use Deny for security guardrails"
defense_in_depth:
description: "Multiple layers of security"
practice: "Combine bucket policy + IAM + ACL + encryption"
avoid_wildcards:
bad: '"Principal": "*"'
better: '"Principal": {"AWS": "arn:aws:iam::123456789012:root"}'
common_mistakes:
- "Using Principal: * without conditions"
- "Missing resource ARN for objects (/*)"
- "Forgetting to block public access"
- "Not enabling versioning before policies"
Common Policy Patterns
Public Read for Static Website
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-website-bucket/*",
"Condition": {
"StringEquals": {
"s3:ExistingObjectTag/public": "true"
}
}
}
]
}
Cross-Account Access
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::987654321098:root"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::shared-bucket",
"arn:aws:s3:::shared-bucket/*"
],
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
CloudFront Origin Access Control
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-cdn-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE"
}
}
}
]
}
Enforce Encryption
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::secure-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyIncorrectKMSKey",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::secure-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
}
]
}
IP-Based Restrictions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowFromCorporateNetwork",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::internal-bucket/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"192.0.2.0/24",
"203.0.113.0/24"
]
}
}
},
{
"Sid": "DenyFromOtherIPs",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::internal-bucket",
"arn:aws:s3:::internal-bucket/*"
],
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"192.0.2.0/24",
"203.0.113.0/24"
]
}
}
}
]
}
VPC Endpoint Access Only
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonVPCAccess",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::private-bucket",
"arn:aws:s3:::private-bucket/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-1234567890abcdef0"
}
}
}
]
}
MFA Delete Protection
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireMFAForDelete",
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion"
],
"Resource": "arn:aws:s3:::critical-bucket/*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
Time-Based Access
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BusinessHoursOnly",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::business-bucket",
"arn:aws:s3:::business-bucket/*"
],
"Condition": {
"DateGreaterThan": {
"aws:CurrentTime": "2024-01-01T18:00:00Z"
},
"DateLessThan": {
"aws:CurrentTime": "2024-01-02T09:00:00Z"
}
}
}
]
}
CloudTrail Logging
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::cloudtrail-logs-bucket",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/mytrail"
}
}
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::cloudtrail-logs-bucket/AWSLogs/123456789012/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"AWS:SourceArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/mytrail"
}
}
}
]
}
Condition Keys Reference
condition_keys:
global:
aws:SourceIp: "IP address or CIDR"
aws:SourceVpc: "VPC ID"
aws:SourceVpce: "VPC endpoint ID"
aws:PrincipalOrgID: "AWS Organization ID"
aws:CurrentTime: "ISO 8601 datetime"
aws:MultiFactorAuthPresent: "true/false"
aws:SecureTransport: "true/false"
s3_specific:
s3:x-amz-acl: "ACL to apply"
s3:x-amz-server-side-encryption: "AES256 or aws:kms"
s3:x-amz-server-side-encryption-aws-kms-key-id: "KMS key ARN"
s3:ExistingObjectTag/<key>: "Object tag value"
s3:RequestObjectTagKeys: "Tags being set"
s3:prefix: "Object key prefix"
s3:max-keys: "Max keys in ListBucket"
s3:object-lock-mode: "GOVERNANCE or COMPLIANCE"
operators:
StringEquals: "Exact match"
StringNotEquals: "Not equal"
StringLike: "Wildcard match (*)"
IpAddress: "IP in CIDR"
NotIpAddress: "IP not in CIDR"
DateGreaterThan: "After date"
DateLessThan: "Before date"
Bool: "Boolean check"
Null: "Key exists/not exists"
Security Best Practices
security_checklist:
block_public_access:
setting: "Block all public access"
how: |
aws s3api put-public-access-block \
--bucket my-bucket \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
enable_versioning:
purpose: "Protect against accidental deletion"
how: |
aws s3api put-bucket-versioning \
--bucket my-bucket \
--versioning-configuration Status=Enabled
enable_logging:
purpose: "Audit access"
how: |
aws s3api put-bucket-logging \
--bucket my-bucket \
--bucket-logging-status '{"LoggingEnabled":{"TargetBucket":"log-bucket","TargetPrefix":"s3-access/"}}'
default_encryption:
purpose: "Encrypt at rest"
how: |
aws s3api put-bucket-encryption \
--bucket my-bucket \
--server-side-encryption-configuration \
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms","KMSMasterKeyID":"alias/s3-key"}}]}'
lifecycle_policy:
purpose: "Manage object lifecycle"
example: "Transition to Glacier after 90 days, delete after 365"
Troubleshooting
common_issues:
access_denied:
symptoms: "403 AccessDenied error"
checks:
- "Verify IAM user/role permissions"
- "Check bucket policy allows action"
- "Verify resource ARN is correct"
- "Check for explicit Deny statements"
- "Verify bucket block public access settings"
debug: |
# Check effective policy
aws s3api get-bucket-policy --bucket my-bucket
# Test access
aws s3api head-object --bucket my-bucket --key test.txt
policy_too_large:
limit: "20 KB maximum"
solutions:
- "Use IAM policies instead"
- "Consolidate statements"
- "Use conditions instead of listing principals"
- "Reference IAM roles instead of users"
invalid_principal:
symptoms: "MalformedPolicy error"
common_causes:
- "Account ID doesn't exist"
- "Role/user doesn't exist"
- "Typo in ARN format"
format: "arn:aws:iam::ACCOUNT-ID:root/role/user"
condition_not_working:
checks:
- "Verify condition key spelling"
- "Check operator type matches value type"
- "Ensure condition applies to correct action"
Policy Validation
# Validate policy syntax
aws iam simulate-custom-policy \
--policy-input-list file://policy.json \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::my-bucket/test.txt
# Test policy with IAM Policy Simulator
# Console: https://policysim.aws.amazon.com/
# Check for public access
aws s3api get-bucket-policy-status --bucket my-bucket
# List bucket policies
aws s3api get-bucket-policy --bucket my-bucket --output text
Terraform Example
resource "aws_s3_bucket" "example" {
bucket = "my-secure-bucket"
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_policy" "example" {
bucket = aws_s3_bucket.example.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "EnforceSSL"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.example.arn,
"${aws_s3_bucket.example.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}
Лучшие практики
- Least privilege — минимальные необходимые права
- Block public access — блокируй публичный доступ по умолчанию
- Use conditions — добавляй условия для дополнительной защиты
- Enable logging — логируй все обращения к bucket
- Version control — храни политики в git
- Regular audits — проверяй политики регулярно
Comments
Markdown supported