좋은 방법은 “정책 장부 YAML을 기준으로 검증하고, MinIO에는 검증 통과 후만 반영”하는 구조입니다.
즉, CI가 직접 MinIO policy JSON만 검사하는 게 아니라 아래 4가지를 함께 검증해야 합니다.
1. 정책 장부 자체가 올바른가?
2. 장부에서 생성된 IAM/ILM/Replication 설정이 안전한가?
3. 기존 운영 정책과 충돌하지 않는가?
4. 적용 후 실제 MinIO 상태가 Git 장부와 일치하는가?
MinIO AIStor는 PBAC 기반으로 IAM 호환 policy를 사용하고, mc admin policy 계열 명령으로 policy 생성/조회/연결을 관리합니다. 따라서 CI 기준은 “IAM JSON 문법”뿐 아니라 AIStor bucket profile, prefix, ILM, replication, versioning, object lock 간의 조합 검증까지 포함해야 합니다. (MinIO AIStor Documentation)
가장 현실적인 구조는 아래입니다.
Git PR
↓
[1] YAML/Schema 검증
↓
[2] Policy Render 검증
↓
[3] OPA/Rego 기반 의미 검증
↓
[4] MinIO live-state diff / plan
↓
[5] Dev 또는 Stage smoke test
↓
[6] 승인 후 Prod apply
↓
[7] Post-check + Drift check
핵심은 정책 요청서/장부가 source of truth이고, IAM JSON은 결과물이라는 점입니다.
aistor-policy-registry/
catalogs/
bucket-profiles.yaml
access-profiles.yaml
ilm-profiles.yaml
replication-profiles.yaml
dangerous-actions.yaml
registries/
prod/
lakehouse-prod.yaml
sandbox-prod.yaml
audit-prod.yaml
dev/
lakehouse-dev.yaml
rendered/
prod/
policies/
p-prod-lakehouse-finance-raw-ro.json
p-prod-lakehouse-finance-raw-rw-no-delete.json
ilm/
lakehouse-prod-ilm.yaml
replication/
lakehouse-prod-replication.yaml
tests/
schema/
bucket-registry.schema.json
rego/
access.rego
ilm.rego
replication.rego
versioning.rego
objectlock.rego
naming.rego
smoke/
s3-access-tests.yaml
tools/
render.py
validate_schema.py
plan.sh
apply.sh
drift_check.sh
먼저 정책 장부의 구조를 강제해야 합니다. 예를 들어 모든 prefix entry에 아래 필드가 반드시 있어야 합니다.
bucket: lakehouse-prod
bucketProfile: lakehouse-table
ownerTeam: finance-analytics
dataOwner: finance-data-owner
env: prod
ticket: REQ-2026-00123
prefixes:
- prefix: domain=finance/zone=raw/
workload: spark-iceberg
dataClass: internal
access:
- principalType: oidc-group
principal: kc-aistor-prod-finance-raw-rw
profile: rw-no-delete
ilm:
profile: none
replication:
profile: none
versioning:
required: false
objectLock:
required: false
CI에서 아래를 검사합니다.
| 검증 항목 | 실패 조건 |
|---|---|
| 필수 필드 | bucket, ownerTeam, dataOwner, ticket 누락 |
| prefix 형식 | /로 끝나지 않음 |
| env 값 | dev/stage/prod 외 값 |
| profile 값 | 카탈로그에 없는 access/ILM/replication profile |
| principal 이름 | Keycloak/LDAP group naming rule 위반 |
| 임시 권한 | expiryDate 없음 |
| prod 변경 | 승인자/티켓 없음 |
특히 prefix는 반드시 /로 끝나게 강제하는 것이 좋습니다. 첨부 사례에서도 lsf/가 아니라 lsf처럼 지정하면 lsf_stg 같은 유사 prefix까지 포함될 수 있다고 되어 있어, CI에서 바로 차단해야 합니다.
장부 YAML에서 실제 MinIO IAM policy JSON, ILM command, replication command를 생성합니다.
예:
python tools/render.py --env prod
그 다음 CI에서 아래를 확인합니다.
# JSON 문법 확인
find rendered/prod/policies -name "*.json" -print0 \
| xargs -0 -I{} jq empty {}
# render 결과가 deterministic 한지 확인
python tools/render.py --env prod --check
render.py --check는 다시 렌더링했을 때 Git에 있는 결과물과 차이가 나면 실패하게 만듭니다.
장부 YAML 변경
↓
rendered JSON 변경
↓
둘 중 하나만 바뀌면 CI 실패
이렇게 해야 사람이 JSON만 직접 수정하거나, 장부와 실제 policy가 따로 노는 것을 막을 수 있습니다.
여기서가 가장 중요합니다.
단순 문법 검사가 아니라 운영 규칙을 코드로 박아야 합니다.
도구는 conftest + OPA/Rego 조합이 가장 적합합니다.
conftest test registries/prod/*.yaml --policy tests/rego
package aistor.policy
deny[msg] {
prefix := input.prefixes[_].prefix
not endswith(prefix, "/")
msg := sprintf("prefix must end with '/': %s", [prefix])
}
rw-no-delete profile에 Delete 권한 금지package aistor.policy
deny[msg] {
p := input.prefixes[_]
a := p.access[_]
a.profile == "rw-no-delete"
a.allowDelete == true
msg := sprintf("rw-no-delete profile cannot allow delete: bucket=%s prefix=%s", [input.bucket, p.prefix])
}
렌더링된 IAM JSON도 따로 검사해야 합니다.
package aistor.rendered_policy
deny[msg] {
stmt := input.Statement[_]
stmt.Effect == "Allow"
action := stmt.Action[_]
action == "s3:DeleteObject"
contains(input.__policy_name, "rw-no-delete")
msg := sprintf("rw-no-delete policy contains s3:DeleteObject: %s", [input.__policy_name])
}
deny[msg] {
stmt := input.Statement[_]
stmt.Effect == "Allow"
action := stmt.Action[_]
action == "s3:DeleteObjectVersion"
contains(input.__policy_name, "rw-no-delete")
msg := sprintf("rw-no-delete policy contains s3:DeleteObjectVersion: %s", [input.__policy_name])
}
고객사 D 사례처럼 versioning bucket에서 DeleteObjectVersion을 막는 정책은 실수 방지에 유용하지만, 이 권한 차단은 WORM Object Lock과 동일한 것은 아니므로 별도 profile로 관리하는 것이 좋습니다.
package aistor.rendered_policy
deny[msg] {
stmt := input.Statement[_]
stmt.Effect == "Allow"
stmt.Resource[_] == "arn:aws:s3:::*"
msg := sprintf("global bucket wildcard is forbidden: %s", [input.__policy_name])
}
deny[msg] {
stmt := input.Statement[_]
stmt.Effect == "Allow"
stmt.Action[_] == "s3:*"
msg := sprintf("s3:* is forbidden in normal access policy: %s", [input.__policy_name])
}
MinIO AIStor는 AWS IAM 문법과 구조에 호환되는 policy를 사용하므로, wildcard와 resource 범위 실수는 실제 권한 과다 허용으로 이어질 수 있습니다. (MinIO AIStor Documentation)
prefix 접근 policy에서 가장 흔한 실수는 object ARN만 제한하고 ListBucket 조건을 제대로 걸지 않는 것입니다.
좋은 구조는 아래와 같습니다.
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::lakehouse-prod"],
"Condition": {
"StringLike": {
"s3:prefix": ["domain=finance/zone=raw/*"]
}
}
}
검증 규칙은 아래처럼 둡니다.
package aistor.rendered_policy
deny[msg] {
stmt := input.Statement[_]
stmt.Effect == "Allow"
stmt.Action[_] == "s3:ListBucket"
not stmt.Condition.StringLike["s3:prefix"]
msg := sprintf("ListBucket must have s3:prefix condition: %s", [input.__policy_name])
}
MinIO AIStor ILM은 object expiration, noncurrent version expiration, delete marker 정리, transition rule 등을 지원합니다. 따라서 CI는 단순히 “ILM rule 있음/없음”이 아니라 bucket profile과 workload별 허용 조합을 검증해야 합니다. (MinIO AIStor Documentation)
package aistor.ilm
deny[msg] {
input.versioning.enabled == true
not input.ilm.noncurrentCleanup.enabled
msg := sprintf("versioning enabled bucket requires noncurrent cleanup rule: %s", [input.bucket])
}
첨부 2-tier 가이드에서도 versioning이 켜진 경우 noncurrent version 정리 rule을 함께 계획해야 한다고 되어 있고, 고객사 A 사례에서는 Iceberg/Dremio 계열 workload에서 delete marker와 non-current version이 대량 누적된 문제가 있었습니다.
package aistor.ilm
deny[msg] {
p := input.prefixes[_]
p.workload == "spark-iceberg"
startswith(p.ilm.profile, "expire-")
not p.ilm.approvedByDataOwner
msg := sprintf("age-based expiration on Iceberg path requires explicit approval: %s/%s", [input.bucket, p.prefix])
}
Iceberg table path는 application의 snapshot/vacuum lifecycle과 맞물리기 때문에, object age만 보고 삭제하는 rule을 쉽게 걸면 위험합니다. 고객사 A 사례에서도 실제 삭제 주체가 ILM인지 application인지 추적해야 했고, delete marker 정리가 주요 이슈였습니다.
package aistor.ilm
deny[msg] {
default_days := input.ilm.defaultTransitionDays
p := input.prefixes[_]
p.ilm.transitionDays > default_days
msg := sprintf("prefix transitionDays must be shorter than bucket default: prefix=%s prefixDays=%d defaultDays=%d", [p.prefix, p.ilm.transitionDays, default_days])
}
첨부 2-tier 가이드에 따르면 bucket default transition rule과 prefix transition rule이 같이 있을 때, prefix rule이 default보다 길면 default rule이 먼저 실행되어 의도와 다르게 동작할 수 있습니다.
여기는 반드시 강하게 막아야 합니다.
package aistor.replication
deny[msg] {
input.replication.enabled == true
input.replication.drDirectReadRequired == true
input.ilm.transition.enabled == true
msg := sprintf("DR direct-read bucket cannot use ILM transition due to stub-only replication risk: %s", [input.bucket])
}
2-tier 가이드의 핵심 주의사항은, Hot에서 Warm으로 transition된 object는 Hot에 실제 bytes가 없고 stub metadata만 남으며, 이 상태에서 replication하면 target에도 stub만 복제될 수 있다는 점입니다. 그러면 replication target이 Warm tier에 접근하지 못할 경우 직접 read가 실패할 수 있습니다.
edge-sync-before-expiry 필수package aistor.replication
deny[msg] {
input.replication.enabled == true
input.ilm.expiry.enabled == true
input.replication.edgeSyncBeforeExpiry != true
msg := sprintf("replication + ILM expiry requires edgeSyncBeforeExpiry: %s", [input.bucket])
}
MinIO AIStor의 mc replicate add에는 --edge-sync-before-expiry 옵션이 있고, 이 옵션은 ILM expiration이 replication 완료 전에 object를 삭제하지 않도록 막는 용도입니다. 공식 문서도 이 옵션이 설정되지 않으면 기본적으로 비활성이라고 설명합니다. (MinIO AIStor Documentation)
package aistor.replication
warn[msg] {
input.replication.enabled == true
input.ilm.expiry.enabled == true
input.replication.targetLifecycleAligned != true
msg := sprintf("replication target should have aligned lifecycle rules because lifecycle expiration deletes are not replicated: %s", [input.bucket])
}
MinIO AIStor 문서에 따르면 server-side replication은 lifecycle expiration으로 삭제된 object를 복제하지 않고, 명시적 client-driven delete만 복제합니다. 따라서 active-active 또는 DR 구성에서는 source/target lifecycle 정책 정렬 여부를 CI에서 확인해야 합니다. (MinIO AIStor Documentation)
Object Lock은 실수하면 되돌리기 어렵기 때문에 CI에서 별도 강제 규칙을 둬야 합니다.
package aistor.objectlock
deny[msg] {
input.objectLock.enabled == true
input.bucketProfile in ["lakehouse-table", "sandbox", "tmp", "staging"]
msg := sprintf("object lock is forbidden for high-churn bucket profile: %s", [input.bucket])
}
deny[msg] {
input.objectLock.enabled == true
input.replication.enabled == true
input.replication.targetObjectLockEnabled != true
msg := sprintf("object lock replication requires target bucket object lock enabled: %s", [input.bucket])
}
warn[msg] {
input.objectLock.enabled == true
input.ilm.expiry.enabled == true
input.objectLock.retentionDays > input.ilm.expireDays
msg := sprintf("object lock retention is longer than ILM expiry; expiry will be delayed: %s", [input.bucket])
}
첨부 고객사 A 사례에서는 Object Lock이 의도치 않게 활성화된 bucket에서 ILM rule 적용 제약과 delete marker 정리 문제가 발생했고, 결국 Batch Expiry를 우회 수단으로 사용했습니다. 이 유형의 실수는 CI에서 사전에 막아야 합니다.
정적 검증이 끝나면 실제 MinIO 상태와 Git 장부를 비교합니다.
이 단계는 Terraform의 plan 같은 역할입니다.
예:
# 실제 policy 목록
mc admin policy ls aistor-prod > actual/policies.txt
# 특정 policy JSON
mc admin policy info aistor-prod p-prod-lakehouse-finance-raw-ro \
| jq -S . > actual/policies/p-prod-lakehouse-finance-raw-ro.json
# policy entity 확인
mc admin policy entities aistor-prod p-prod-lakehouse-finance-raw-ro \
> actual/entities/p-prod-lakehouse-finance-raw-ro.txt
# ILM rule 확인
mc ilm rule ls aistor-prod/lakehouse-prod > actual/ilm/lakehouse-prod.txt
# Replication 확인
mc replicate ls aistor-prod/lakehouse-prod > actual/replication/lakehouse-prod.txt
# Versioning 확인
mc version info aistor-prod/lakehouse-prod > actual/versioning/lakehouse-prod.txt
mc admin policy info, mc admin policy ls, mc admin policy entities는 각각 policy JSON 조회, policy 목록 조회, policy에 연결된 user/group 조회에 사용됩니다. 단, mc admin policy entities는 MinIO-managed user/group 기준이고, AD/LDAP는 별도 LDAP policy entity 명령을 써야 합니다. (MinIO AIStor Documentation)
이후 CI에서 diff를 생성합니다.
diff -ru rendered/prod/policies actual/policies
결과를 아래처럼 분류합니다.
| Diff 유형 | 처리 |
|---|---|
| Git에는 있는데 MinIO에 없음 | 신규 생성 예정 |
| MinIO에는 있는데 Git에 없음 | 비인가 정책 가능성, apply 중단 |
| JSON 내용 불일치 | 변경 승인 필요 |
| binding 불일치 | group/policy mapping 검토 |
| ILM rule 불일치 | 삭제/transition 위험으로 수동 승인 |
| replication rule 불일치 | DR 영향으로 수동 승인 |
정책이 정적으로 맞아도 실제 접근이 원하는 대로 되는지 확인해야 합니다.
운영 prod 전체를 테스트하기보다 dev/stage bucket 또는 canary prefix에서 대표 profile별 테스트를 돌리는 방식이 좋습니다.
예:
ro profile
- target prefix list 가능
- target prefix get 가능
- put 불가
- delete 불가
- 다른 prefix list/get 불가
rw-no-delete profile
- target prefix list 가능
- put 가능
- multipart upload 가능
- delete 불가
- delete version 불가
ingest-only profile
- put 가능
- get/list 제한
- delete 불가
bucket-admin profile
- 승인된 bucket만 관리 가능
- 다른 bucket 접근 불가
테스트 예시는 아래와 같습니다.
# 허용되어야 함
AWS_ACCESS_KEY_ID="$AK" AWS_SECRET_ACCESS_KEY="$SK" \
aws s3api put-object \
--endpoint-url "$ENDPOINT" \
--bucket lakehouse-dev \
--key "domain=finance/zone=raw/ci-test.txt" \
--body ./ci-test.txt
# 거부되어야 함
set +e
AWS_ACCESS_KEY_ID="$AK" AWS_SECRET_ACCESS_KEY="$SK" \
aws s3api delete-object \
--endpoint-url "$ENDPOINT" \
--bucket lakehouse-dev \
--key "domain=finance/zone=raw/ci-test.txt"
if [ $? -eq 0 ]; then
echo "ERROR: delete should have been denied"
exit 1
fi
set -e
OIDC/LDAP를 쓴다면 CI에서 모든 실제 사용자 group을 흉내 내기는 어렵습니다. 대신 대표 group 또는 테스트용 claim/service account를 만들어 smoke test를 수행하고, 실제 OIDC/LDAP mapping은 별도 diff 검증으로 관리하는 것이 현실적입니다. MinIO AIStor에서 MinIO-managed user/group policy attach와 OIDC/LDAP policy 연결 방식은 다르므로, CI도 principal type별로 분리해야 합니다. (MinIO AIStor Documentation)
사용자 환경에서는 Jenkins를 이미 쓰고 있으므로, 아래 방식이 잘 맞습니다.
pipeline {
agent { label 'linux-ci' }
options {
timestamps()
disableConcurrentBuilds()
}
environment {
ENV_NAME = 'prod'
}
stages {
stage('YAML & Schema Lint') {
steps {
sh '''
yamllint catalogs registries
python tools/validate_schema.py --env ${ENV_NAME}
'''
}
}
stage('Render Policies') {
steps {
sh '''
python tools/render.py --env ${ENV_NAME}
python tools/render.py --env ${ENV_NAME} --check
find rendered/${ENV_NAME}/policies -name "*.json" -print0 | xargs -0 -I{} jq empty {}
'''
}
}
stage('Policy-as-Code Validation') {
steps {
sh '''
conftest test registries/${ENV_NAME} --policy tests/rego
conftest test rendered/${ENV_NAME}/policies --policy tests/rego
'''
}
}
stage('Plan Against MinIO') {
steps {
withCredentials([
string(credentialsId: 'aistor-prod-access-key', variable: 'MINIO_ACCESS_KEY'),
string(credentialsId: 'aistor-prod-secret-key', variable: 'MINIO_SECRET_KEY')
]) {
sh '''
mc alias set aistor-prod ${MINIO_ENDPOINT} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
bash tools/plan.sh ${ENV_NAME} > plan.txt
'''
}
archiveArtifacts artifacts: 'plan.txt', fingerprint: true
}
}
stage('Smoke Test on Dev') {
when {
changeRequest()
}
steps {
withCredentials([
string(credentialsId: 'aistor-dev-access-key', variable: 'MINIO_ACCESS_KEY'),
string(credentialsId: 'aistor-dev-secret-key', variable: 'MINIO_SECRET_KEY')
]) {
sh '''
mc alias set aistor-dev ${MINIO_DEV_ENDPOINT} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
bash tests/smoke/run.sh dev
'''
}
}
}
stage('Manual Approval') {
when {
branch 'main'
}
steps {
input message: 'Apply AIStor policy changes to prod?'
}
}
stage('Apply') {
when {
branch 'main'
}
steps {
withCredentials([
string(credentialsId: 'aistor-prod-access-key', variable: 'MINIO_ACCESS_KEY'),
string(credentialsId: 'aistor-prod-secret-key', variable: 'MINIO_SECRET_KEY')
]) {
sh '''
mc alias set aistor-prod ${MINIO_ENDPOINT} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
bash tools/apply.sh ${ENV_NAME}
'''
}
}
}
stage('Post Check') {
when {
branch 'main'
}
steps {
sh '''
bash tools/drift_check.sh ${ENV_NAME}
'''
}
}
}
}
mc admin policy create는 같은 이름의 policy가 이미 있으면 overwrite할 수 있으므로, apply 전에 반드시 plan/diff와 수동 승인 단계를 두는 것이 좋습니다. (MinIO AIStor Documentation)
아래는 실패 시 apply를 막는 규칙으로 두는 것이 좋습니다.
| 영역 | Blocker 조건 |
|---|---|
| Prefix | /로 끝나지 않는 prefix |
| Access | s3:*, arn:aws:s3:::* 사용 |
| Access | rw-no-delete인데 DeleteObject 포함 |
| Access | DeleteObjectVersion 일반 profile에 포함 |
| Access | ListBucket에 s3:prefix condition 없음 |
| Public | anonymous policy가 예외 승인 없이 존재 |
| Versioning | versioning enabled인데 noncurrent cleanup 없음 |
| Iceberg | Iceberg active path에 단순 age-based expire |
| Object Lock | lakehouse/tmp/sandbox bucket에 object lock 요청 |
| Replication | DR direct-read bucket에 ILM transition 존재 |
| Replication | ILM expiry와 replication이 있는데 edge-sync-before-expiry 없음 |
| Warm Tier | warm-tier-target bucket에 app access policy 존재 |
| ILM | prefix transition days가 bucket default보다 김 |
| Naming | policy/group 이름 규칙 위반 |
| Ownership | ownerTeam/dataOwner/ticket 누락 |
| Temporary Access | expiryDate 없음 |
아래는 무조건 실패보다는 경고 + 수동 승인 대상으로 두면 됩니다.
| 영역 | Warning 조건 |
|---|---|
rw-delete 요청 | 삭제 권한은 가능하지만 data owner 승인 필요 |
| replication target lifecycle 미정렬 | source expiry가 target에 전파된다고 착각할 위험 |
| versioning enabled bucket에서 high-churn workload | storage/scanner/list/healing 부담 가능 |
| Object Lock retention > ILM expiry | 실제 삭제가 retention 만료 후로 지연 |
| replication bandwidth 제한 | backlog 증가 가능 |
| 대량 prefix ILM 신규 적용 | scanner/transition queue 증가 가능 |
| Batch Expiry 사용 | 일회성 대량 삭제 위험 |
적용은 한 번에 다 하지 말고 순서를 고정하는 것이 좋습니다.
1. bucket 존재 확인
2. versioning/object lock 상태 확인
3. IAM policy create/update
4. principal binding
5. ILM rule 적용
6. replication rule 적용
7. smoke test
8. snapshot 저장
예:
# 1. policy 반영
mc admin policy create aistor-prod \
p-prod-lakehouse-finance-raw-rw-no-delete \
rendered/prod/policies/p-prod-lakehouse-finance-raw-rw-no-delete.json
# 2. MinIO-managed group이면 attach
mc admin policy attach aistor-prod \
p-prod-lakehouse-finance-raw-rw-no-delete \
--group finance-raw-rw
# 3. ILM rule 확인
mc ilm rule ls aistor-prod/lakehouse-prod
# 4. replication rule 확인
mc replicate ls aistor-prod/lakehouse-prod
단, OIDC/LDAP principal은 MinIO-managed group attach와 다르게 처리해야 합니다. mc admin policy attach는 MinIO-managed user/group용이고, AD/LDAP는 mc idp ldap policy attach 계열을 사용해야 합니다. (MinIO AIStor Documentation)
CI는 PR 시점뿐 아니라 매일 또는 매주 정기적으로 돌아야 합니다.
bash tools/drift_check.sh prod
Drift check는 아래를 비교합니다.
Git desired state
vs
MinIO actual state
확인 대상:
mc admin policy ls
mc admin policy info
mc admin policy entities
mc ilm rule ls
mc replicate ls
mc replicate status
mc version info
mc retention info --default
mc anonymous list
Drift가 나오면 아래처럼 분류합니다.
| Drift | 의미 | 조치 |
|---|---|---|
| MinIO에만 있는 policy | 수동 변경 가능성 | 회수 또는 Git 등록 |
| Git에만 있는 policy | apply 누락 | 재적용 |
| policy JSON 다름 | 비인가 변경 가능성 | 승인 이력 확인 |
| ILM rule 다름 | 삭제/transition 위험 | 즉시 검토 |
| replication 다름 | DR 영향 가능 | 즉시 검토 |
| anonymous policy 존재 | public exposure 가능 | 보안 승인 확인 |
처음부터 모든 걸 자동화하려고 하면 부담이 큽니다. 아래 순서로 시작하는 것이 좋습니다.
1단계
- bucket/prefix registry YAML 작성
- access profile 5개만 정의
ro, rw-no-delete, rw-delete, ingest-only, bucket-admin
- schema validation + prefix slash + wildcard 금지
2단계
- IAM JSON 자동 render
- OPA/Rego로 delete, wildcard, ListBucket condition 검증
- Jenkins PR 검증 추가
3단계
- ILM/versioning/replication/object lock 조합 검증 추가
- live-state plan/diff 추가
- dev smoke test 추가
4단계
- prod apply 승인 workflow
- nightly drift check
- 운영 dashboard/report 생성
가장 먼저 만들 규칙은 이 6개입니다.
1. prefix는 반드시 /로 끝나야 함
2. s3:* 및 arn:aws:s3:::* 금지
3. rw-no-delete에는 DeleteObject/DeleteObjectVersion 금지
4. versioning enabled면 noncurrent cleanup 필수
5. DR direct-read bucket에는 ILM transition 금지
6. replication + ILM expiry면 edge-sync-before-expiry 필수
이 6개만 먼저 넣어도 주먹구구식 정책 적용으로 인한 큰 사고 가능성을 상당히 줄일 수 있습니다.