ケーススタディで改めて学ぶ、S3をセキュアに運用するために気をつけるべきこと

takumi ogawa
Eureka Engineering
Published in
15 min readDec 18, 2021

この記事は Eureka Advent Calendar 2021 の 18日目の記事です。

VRで遊ぶ私とTeam VRの人たち

はじめに

こんにちは、エウレカ SREチーム の@ogadyです。

最近はお絵描きに目覚めてしまい、同僚のHead of VR Team(自称)の二次創作だったりをしていました。(ちなみにVRチームについてはこちら↓)

メタバース内からメタバースへ向けたアプリを開発

入社1年でVR勢を巻き込んでVRチームを構成した話

お絵描きってのは難しいもんで、自分で完璧に描き切ったと思っても翌日眺めると違和感だったり、もっと改善したいポイントなどが見つかって、修正して、を繰り返していくことでどんどん理想の絵に近づいていくわけです。

皆さんはここまでの話を読んでこれってエンジニアリングと同じだなと感じますよね🙄

ビギナーの私は最初から完成を目指さずに、サイクルを回しながら徐々に完成に近づけていくしかないのです、、、

余談はさておき、ここから本題です。

S3を”安全に”使えていますか?

Amazon S3ってとても便利ですよね。クラウドインフラ環境としてAWSを採用しているのであれば、ほとんどの場合S3は使用していると思います。

バケット作成すればあとはそこに置きたいファイルなどをPutObjectしていくだけ!シンプルに使う分にはとてもわかりやすいです。

ただし、便利な反面、本番で動いているS3には考慮すべき事項が色々あります。

基本的に、本番環境で扱う実データの置き場所として使う場合は、組織のセキュリティ要件に従ってアクセス権限を適切に絞る必要があります。

本記事では、S3を使用する際に考慮するべき設定をケーススタディ形式で紹介していきます。

なお、本記事では主に予防的セキュリティの代表的なプラクティスを紹介します。セキュリティモニタリングについては基本的に触れません。

ケーススタディ

本記事では以下のような、ユーザーがアップロードしたデータを管理画面上でなんらかの処理を行うようなユースケースでどのようにS3を守るかを考えてみます。

前提

  • WebAPIやAdminAPIのエンドポイントは適切に認証/認可が行われている
  • AWSのIAMロールがセキュアに運用されている
  • 各APIの実行ホストは適切にネットワークアクセス制限、IAM設定が行われている

要件

ユーザーがS3にアップロードしたデータは、

  • 社内の業務に必要なユーザー(管理者ユーザーとシステム管理者)のみがアクセス可能である必要がある
  • 適切に暗号化されている必要がある
  • 監査証跡や、インシデント発生時の追跡可能なログが必要である
アーキテクチャ図(before)

アーキテクチャ図を見てもらうと、S3のアクセス制限がなく誰からでも見えるようになってしまっています。また、監査ログや暗号化の設定もないため要件を満たせていない状態です。本番環境でここまで公開状態になっていることはまずないはずですが、今回はケーススタディとしてあえてこのような設定にしてみました。

API側の認証/認可の仕組みがあるとは言え、これではセキュアにS3を運用できてるとは言えません。

ではこのようなユースケースの場合どのようなセキュリティ対策がs3にとられているべきでしょうか?

(一部Terraformのサンプルコードなどを載せますが、考え方はマネジメントコンソールでもその他IaCツールでも共通です。)

1. 適切なアクセス制御

1. 1. S3バケットを非公開にする

まずはS3バケットを非公開設定にします。

S3の機能でパブリックアクセスブロック設定を有効にしましょう。

設定は4種類ありますが、すべての設定を有効にすることで、全アクセスポイント/バケット/オブジェクトへのパブリックアクセスを確実にブロックすることができます。

Terraform抜粋↓

resource "aws_s3_bucket_public_access_block" "sample" {
bucket = aws_s3_bucket.sample.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
depends_on = [
aws_s3_bucket.sample
]
}

1.2. S3へのアクセス権限を設定する

次にS3のアクセス権限を絞ります。

S3のアクセスコントロールはS3バケットポリシーやACLとアクセスするIAMポリシーの組み合わせで考える必要があります。

該当のS3バケットにアクセスするためにはアクセスするユーザー、ロール側のIAM許可が必要になります。その上でS3のバケットポリシー側に明示的に拒否設定を入れていない場合にのみ可能です。

詳細はクラスメソッドさんの記事がわかりやすいのでこちらを確認していただけると理解が捗ると思います。

1.2.1. IAM側でアクセスできるバケットを絞る

S3にアクセスするAWSユーザー、ロールのIAM policyにのみアクセス権限を付与することで、それ以外のアクセスを制限します。

IAM policy抜粋↓

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::sample",
"arn:aws:s3:::sample/*"
],
"Action": [
"s3:GetObjectVersion",
"s3:GetObject",
"s3:GetBucketLocation",
"s3:PutObject",
"s3:DeleteObjectVersion",
"s3:DeleteObject",
"s3:PutObjectAcl",
"s3:ListBucket"
]
}
]
}

その他のIAMロールにはこのS3に対してアクセス許可がないことを確認しましょう。ワイルドカードなどで全てのS3への許可設定をしているIAMロールなどがいた場合は適切に権限をNarrowする必要があります。

そもそものIAMセキュア化については弊社SREチームのDatchさんが素晴らしい記事を書いてくれたのでぜひそちらもご覧ください。

1.2.2. バケットポリシーを設定する

アクセス権限のコントロールをバケットポリシーで設定します。ACLを使用する方法もありますが、ACLはバケット/オブジェクトごとにAWSアカウント単位でしか権限管理できない+オブジェクトACLの管理は煩雑なので、素直にバケットポリシーで管理しましょう。基本的にはバケットポリシーのみでS3を守り切ることは可能です。

先日のre:Invent2021でACL無効化の設定を明示的に行えるようになったため、こちらを設定するのも良いと思います。

S3のバケットポリシーで転送時に暗号化されている(TLSを使用する)ことを必須に設定しましょう。このポリシーを設定することで、ネットワークのトラフィックを盗聴・操作することを防止できます。

IAM policy抜粋↓

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EncryptionInTransit",
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::sample/*",
"arn:aws:s3:::sample"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
]
}

1.3. S3 Presigned URLを使用する

今回のケースでは、管理者ユーザーの人がクライアント端末のブラウザからURLで直接S3のオブジェクトにアクセスしています。保護対象のデータがあるS3にネットワークの穴を開けるのは避けたいので、Presigned URL(署名付きURL)を使用することで、セキュアにアクセスすることができるようになります。

AWS SDKを使用してPresigned URLを発行することができます。

リスクを軽減するために署名の有効期限は最小限に設定しましょう。

2. データ自体の保護

2.1. S3のServer Side Encryption(SSE)を有効にする

S3のServer Side Encryption(以降SSE)はオブジェクトをAWSのデータセンター内に保存する前に暗号化する設定です。

暗号化に使用するキーはSSE-S3、SSE-KMS、SSE-Cの3種類の中から指定できます。

それぞれ検討してみます。

Amazon S3 が管理するキーによる暗号化 (SSE-S3)

Amazon S3 が管理するキーを使用します。追加料金がかからないため、安価かつ簡単に導入できます。

Terraform抜粋↓

resource "aws_s3_bucket" "sample" {
-------略-------
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}

AWS KMSに保存されているキーによる暗号化 (SSE-KMS)

KMSで管理しているキーを使用して暗号化します。特に、カスタマーマネージドキーを使用することで、KMSのpolicyでKMS自体のアクセス権限を設定することにより、より柔軟なアクセスコントロールや、KMSの監査証跡も残すことができます。アクセスする場合に署名バージョン 4による署名が必須となるので、そういった用件がある場合にもセキュアに管理可能です。追加料金がかかります。

Terraform抜粋↓

resource "aws_s3_bucket" "sample" {
-------略-------
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.sample_key.arn
sse_algorithm = "aws:kms"
}
}
}
}

また、KMSのインラインPolicyに、S3からのキー参照権限、アクセスアナライザーの許可設定や、S3経由でのアプリケーションロールからのアクセス許可設定をおこないます。

長くなるため記載していませんが、システム管理者IAMロールからのアクセス許可設定も必要です。

IAM policy抜粋↓

{
"Sid": "for S3 get key info",
"Effect": "Allow",
"Principal": {
"Service": "s3.amazonaws.com"
},
"Action": [
"kms:List*",
"kms:Get*",
"kms:Describe*"
],
"Resource": "*"
},
{
"Sid": "forAccessAnalyizer",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/aws-service-role/access-analyzer.amazonaws.com/AWSServiceRoleForAccessAnalyzer"
},
"Action": [
"kms:List*",
"kms:GetKeyPolicy",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Allow to encrypt/decrypt via S3",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:role/app-role",
"arn:aws:iam::123456789012:role/admin-app-role"
]
},
"Action": [
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:GenerateDataKey",
"kms:Encrypt",
"kms:DescribeKey",
"kms:Decrypt",
"kms:CreateGrant"
],
"Resource": "*",
"Condition": {
"StringLike": {
"kms:ViaService": "s3.*.amazonaws.com"
}
}
}

自前で管理しているキーによる暗号化 (SSE-C)

自前で管理しているキーを使用して暗号化します。マネージドでないのでキーの管理コストがかかってきますが、透過的復号がされないので、クレデンシャル漏洩のリスクに対して効果があります。Presigned URLがエンドユーザーのクライアントから使用できないという欠点があります。

今回は監査証跡の要件と、上記で述べた署名バージョン4を使用したPresigned URLを必須にしたいのでSSE-KMSを利用します。

2.2. バージョニングの有効化

オブジェクトのバージョニングを有効にすることで、悪意のある or 意図しないオブジェクト操作から復旧することが可能になります。

Terraform抜粋↓

resource "aws_s3_bucket" "sample" {
-------略-------
versioning {
enabled = true
}
}

3. データアクセスの追跡性を担保

3.1. Amazon S3 サーバーアクセスログを有効にする

サーバーアクセスログには、バケットに対するリクエストの詳細が記録されます。サーバーアクセスログは、セキュリティやアクセス監査の証跡として利用したり、インシデント発生時のデータアクセスを追跡する用途として使用できます。

Terraform抜粋↓

resource "aws_s3_bucket" "sample" {
-------略-------
logging {
target_bucket = "sample-access-log"
target_prefix = ""
}
}

ログの出力先バケットのセキュリティ対策も忘れないようにしましょう。

完成したアーキテクチャ

最終的にこのような形になりました。

アーキテクチャ図(after)

これによりS3に格納されたデータのセキュリティを向上させることができました。

S3は非常に便利で強力なコンポーネントですが、セキュリティを担保するために必要なプラクティスがいくつもあります。

AWSの公式ドキュメントにこの記事で説明しきれなかった予防的セキュリティプラクティスや、モニタリングセキュリティのプラクティスも紹介されているので、ぜひこちらも確認して見てください。

終わりに

今回紹介したものはあくまでもAWSの代表的なプラクティスであり、これだけで全てのリスクが防げているわけではありません。

前提として、こういったデータを扱う場合は、組織としてサービスで扱う情報について、データの機密区分および区分別の暗号化ポリシー、許容されるアクセス権限ポリシーなどが定められている必要があり、現場のレベルで「これくらい守っていればOKでしょ」という判断で実装するべきではありません。本記事のケーススタディでは、よくあるセキュリティ要件を想定してそれに従って実装していきました。

今回の例はあくまでもサンプルのケーススタディなので、それぞれの組織、サービスに沿ったセキュリティ対策を模索するのが大切です。

この記事がS3をセキュアに運用したい誰かの参考になれば幸いです。

--

--