关于 s3-bucket-policy
s3-bucket-policy 是用于编写、审查和改进 AWS S3 存储桶策略的指导 skill,涵盖访问控制模式、安全条件、策略验证与故障排查。
主要功能
- 01最小权限策略指导
- 02公共访问与跨账户模式
- 03CloudFront 和 CloudTrail 策略示例
- 04加密、IP、VPC 和 MFA 限制
- 05条件键参考
- 06策略验证与故障排查
适用场景
- 01为静态网站编写存储桶策略
- 02配置跨账户存储桶访问
- 03按 IP 或 VPC 端点限制访问
- 04排查 S3 AccessDenied 错误
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dengineproblem/ agents-monorepo --skill s3-bucket-policy安装后在 Claude Code 中输入/s3-bucket-policy确认已加载。
详细介绍
AWS S3 存储桶策略专家
提供有关创建、分析和优化 AWS S3 存储桶策略的专业指导,重点关注安全性、访问控制和合规性。
策略结构
{
"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"
}
}
}
]
}
核心原则
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"
常见策略模式
静态网站的公开读取权限
{
"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"
}
}
}
]
}
跨账户访问
{
"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 源访问控制
{
"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"
}
}
}
]
}
强制加密
{
"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 的限制
{
"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 端点访问
{
"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 删除保护
{
"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"
}
}
}
]
}
基于时间的访问
{
"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 日志记录
{
"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:
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_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"
故障排除
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"
策略验证
# 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 示例
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"
}
}
}
]
})
}
最佳实践
- 最小权限 — 仅授予必要的权限
- 阻止公开访问 — 默认阻止公开访问
- 使用条件 — 添加条件以增强保护
- 启用日志记录 — 记录对存储桶的所有访问
- 版本控制 — 将策略存储在 git 中
- 定期审计 — 定期检查策略
评论
支持 Markdown