AWS实战 - 利用IAM对S3做访问控制


介绍

要对S3的访问权限做控制,既可以使用基于身份的策略(IAM用户策略),也可以使用基于资源的策略(ACL和存储桶策略)。

访问一个存储桶的权限控制流程如图所示: 存储桶流程

访问存储桶中的对象的权限控制流程如图所示: 存储桶对象流程

当Amazon S3收到对象操作请求时,它会将基于资源的所有相关权限(对象访问控制列表 (ACL)、存储桶策略、存储桶 ACL)和IAM用户策略转换为将在运行时进行评估的策略集。然后它会通过一系列步骤评估生成的策略集。在每个步骤中,它会在三个特定上下文 (用户上下文、存储桶上下文和对象上下文) 中评估一个策略子集。

简单来说就是先判断用户权限,然后判断存储桶权限,最后判断对象权限。

目前我们都是通过同一个AWS账户下的多个IAM用户去访问S3的,因此可以只使用IAM用户策略去做访问权限控制,这也基本能满足绝大部分常规的权限控制需求,如果无法满足的情况再考虑使用存储桶策略和ACL。


实战演练

准备工作

  • 使用AWS根账号(我的账户名是Harp)登录AWS管理控制台,选择IAM服务进入,创建一个名为Administrators的组,并向其附加AdministratorAccess权限;
  • 创建一个IAM用户(Harp-Admin),并将其添加到Administrators组;
  • 下载Authy软件,手机版或PC版均可,并安装,用于为用户添加MFA验证;
  • 在IAM控制台中点击用户,点击Harp-Admin,点击安全凭证;
  • 已分配 MFA 设备目前显示未分配,点击管理,添加虚拟 MFA 设备,继续;
  • 此时弹出如下的对话框,如果使用的Authy是手机版本的,那么点击显示QR码并扫描,如果是PC版则点击显示私有密钥,并在PC版的Authy中输入该密钥; 虚拟MFA设备
  • 之后将Authy中连续两个30s的MFA CODE输入以下文本框中,并点击分配MFA;

MFA TOKEN

  • 此时Harp-Admin已经开启了MFA验证,使用该用户登录控制台时,需要在验证密码之后,再验证MFA(即输入Authy中的实时CODE);
  • 在IAM控制台中点击账户设置,将密码策略修改如下,可根据需要调整;

密码策略

创建测试用户和存储桶

  • 根据AWS最佳实践,本步骤的操作都是使用Harp-Admin来处理的;
  • 依次创建以下4个用户,暂不赋予任何权限;

用户

  • 如下图,创建Bucket一列对应的存储桶,Object不为*的需要创建对应的文件夹,权限一列是我们要实现的访问控制权限;

存储桶及权限

配置IAM用户策略

s3_common_policy
  • 在IAM控制台中点击策略,创建策略,JSON,输入如下json,命名为s3_common_policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::derek-public*"
            ]
        },
        {
            "Sid": "AllowRootLevelListingOfTheBucket",
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::derek-bucket3"
            ],
            "Condition": {
                "StringEquals": {
                    "s3:prefix": [
                        ""
                    ],
                    "s3:delimiter": [
                        "/"
                    ]
                }
            }
        },
        {
            "Sid": "AllowListBucketOfASpecificUserPrefix",
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::derek-bucket3"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "public/*"
                    ]
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::derek-public1/*",
                "arn:aws:s3:::derek-bucket3/public/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::derek-public2/*"
        }
    ]
}

解释一下各条statements:
statement1: 对所有的S3资源赋予s3:ListAllMyBuckets和s3:GetBucketLocation,这两个权限是给控制台使用的,通过控制台访问S3时,需要这两个权限来正常列出所有存储桶,如果通过CLI访问则不需要;
statement2: 赋予所有derek-public开头的存储桶列出存储桶对象的权限(s3:ListBucket);
statement3: 允许列出derek-bucket3第一层路径下的对象,这个写法可以在S3的官方文档中找到;
statement4: 允许列出derek-bucket3/public下的所有对象;
statement5: 允许对derek-public1和derek-bucket3/public中的对象读写、复制操作;
statement6: 允许对derek-public2中的对象读取操作;

  • 创建一个用户组s3_common_group,并将所有用户添加到其中,将s3_common_policy附加到这个组;
  • 此时id为1/4/8的权限已经实现,4个用户可以正常读取derek-public1derek-public2derek-bucket3/public,其中derek-public2只有可读权限,如果用户尝试写入,例如创建一个文件夹,会显示“无法使用名称XXX创建文件夹”;

创建错误

  • 如果用户尝试进入无权限的bucket或者文件夹,例如derek-bucket1derek-bucket3/user1,会显示Access Denied

Access Denied

user1_policy
  • 在IAM控制台中点击策略,创建策略,JSON,输入如下json,命名为user1_policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket1",
                "arn:aws:s3:::derek-bucket2"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket1/*",
                "arn:aws:s3:::derek-bucket3/user1/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::derek-bucket2"
        },
        {
            "Sid": "AllowListBucketOfASpecificUserPrefix",
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::derek-bucket3"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "user1/*"
                    ]
                }
            }
        }
    ]
}

解释一下各条statements:
statement1: 允许列出derek-bucket1和derek-bucket2中的所有对象;
statement2: 允许对derek-bucket1和derek-bucket3/user1中的对象读写、复制操作;
statement3: 允许对derek-bucket2中的对象读取操作;
statement4: 允许列出derek-bucket3/user1下的所有对象;

  • 因为该策略只有user1使用,我们可以不创建组,直接将其附加到user1上;
  • 点击用户,user1,添加权限,直接附加现有策略,选择user1_policy,审核,添加权限;
  • 此时id为3/4/6中涉及user1的权限已经配置完成;
user2_policy
  • 在IAM控制台中点击策略,创建策略,JSON,输入如下json,命名为user2_policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket2"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket2/*",
                "arn:aws:s3:::derek-bucket3/user2/*"
            ]
        },
        {
            "Sid": "AllowListBucketOfASpecificUserPrefix",
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::derek-bucket3"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "user2/*"
                    ]
                }
            }
        }
    ]
}

解释一下各条statements:
statement1: 允许列出derek-bucket2中的所有对象;
statement2: 允许对derek-bucket2derek-bucket3/user2中的对象读写、复制操作;
statement3: 允许列出derek-bucket3/user2下的所有对象;

  • 将该策略附加给user2,此时id为4/7中涉及user2的权限已经配置完成;
user3_policy
  • 在IAM控制台中点击策略,创建策略,JSON,输入如下json,命名为user3_policy;
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket3"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::derek-bucket3"
        }
    ]
}
  • 将该策略附加给user3,此时id为5的权限已经配置完成;
user4_policy
  • 根据之前的步骤,使用Harp-Admin为user4创建虚拟MFA;
  • 在IAM控制台中点击策略,创建策略,JSON,输入如下json,命名为user4_policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket4"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket4/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket4/mfa/*"
            ],
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": false
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::derek-bucket4/mfa/*"
            ]
        }
    ]
}

解释一下各条statements:
statement1: 允许列出derek-bucket4中的所有对象;
statement2: 允许对derek-bucket4中的对象读写、复制操作;
statement3: 未通过MFA验证时禁止写derek-bucket4/mfa下的所有对象
statement4: 允许读取derek-bucket4/mfa下的所有对象;

  • 其中涉及MFA验证的权限控制可以参考带MFA条件的示例策略
  • 将该策略附加给user4,此时id为9的权限已经配置完成;
  • 如果user4通过控制台登录,必须要使用MFA验证,登录之后符合statement3的条件,因此可以在mfa文件夹下写入内容;
  • 如果user4通过CLI直接向mfa写入,则会被拒绝,如图:

MFA Denied

临时凭证

  • 修改AWS CLI的配置文件credentials,如下图红框,添加一个mfa-user,将上一步的结果复制到这里;

MFA USER

  • 利用mfa-user复制文件到mfa文件夹,提示复制成功;

MFA Passed and Upload Succeed


一些坑

在使用user1时,在derek-bucket1中建立文件夹没有问题,但是上传文件却失败,提示OptionsRequestDenied,经谷歌查询猜测可能和浏览器的AdBlock相关插件有关,于是换火狐浏览器、或者使用CLI上传,都没有问题。