Flatt Security Blog

株式会社Flatt Securityの公式ブログです。プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

株式会社Flatt Securityの公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

重複したIAM、拒否と許可どっちが優先?アクセス制御の特性をAWS・Google Cloud・Azure・Firebaseそれぞれについて理解する

はじめに

こんにちは、セキュリティエンジニアの@okazu_dm です。

突然ですが、皆さんは以下のクイズに自信を持って回答できるでしょうか。これはSRE NEXT 2023で弊社が出題したクイズなのですが、それぞれのポリシーに存在するAllow, Denyのうちどれが優先されるかがポイントになります。

クイズの答えはここをクリック クイズの正解は......「④なし」でした。AWSのIAMポリシーは拒否(Deny)優先です。しかし、それは他のクラウドサービスでも果たして同じでしょうか?

今回の記事では、各種クラウドサービスにおけるアクセス制御について焦点を当てます。具体的には「それぞれのアクセス制御は拒否優先なのかどうか」を調査しました。

リソースに対するアクセス制御は、クラウドサービス上でシステムを構築する際のセキュリティの根幹となる要素です。特に近年のクラウドサービスは、IAM(Identity and Access Management)サービスでのアクセス制御を採用しているものが多く、同じような仕組みに見えて混乱される方も多いかと思います。この記事ではそれらの微妙な差異を解説しているため、確認していただければと思います。

調査対象のクラウドサービス

この記事では、以下のクラウドサービス上のプロダクトにおけるアクセス制御の検証を実施し、ポリシーの優先順位の調査を行いました。

  • AWS: S3
  • Google Cloud: Google Cloud Storage
  • Firebase: Firestore
  • Azure: Blob Storage

それぞれのクラウドサービス上で、最低限「許可する対象および条件を記述する」形でのアクセス制御が可能なプロダクトを選択しました。

まとめ

以降は検証した内容をそれぞれ書いているので、先に結果だけをまとめました。

今回、対象としたサービスだと操作対象の指定方法や条件の設定などはAWSが一番細かい粒度で許可と拒否それぞれが設定可能でした。そのため柔軟さという意味ではAWSが一番高いと言えます。

一方でFirebase(Firestore)のセキュリティルールでは複数のルールのうち、一個でも該当する許可ルールが存在すれば許可と判定されるため、慎重な扱いが必要です。 これについては私見ですが、Firestoreはシンプルなデータ構造の管理に向いていると考えれば、セキュリティルールも単純な評価ロジックであることは一定の理が見いだせます。

また、Google CloudとAzureは拒否の設定はそれぞれ限定的な形で提供されており、これらのサービスも、機能の不足ではなく、あえて柔軟な拒否設定をできないようにすることで複雑なポリシーを作りにくいような設計にしたのではないかと考えています。

検証シナリオ

以下のシナリオで検証を進めました。

  • 原則としてすべてのオブジェクトにはアクセスできる(全体許可)。
  • ただし、pathA/ 以下の(または pathA/ からオブジェクト名が始まる)オブジェクトにはアクセスできない(一部禁止) 。

この状態で、全体に対するアクセス許可ルールが優先されるのか、あるいは一部のオブジェクトに対するアクセス禁止ルールが優先されるのか、という点を以下のように確認していきます。

  • pathA/foo.txt にアクセスした場合、拒否されるか。
  • pathB/bar.txt にアクセスした場合、許可されるか。

以降では、個々のサービスについての検証内容とその結果について書いていきます。 (アカウントIDなど、一部の情報を **** などの文字列に置き換えています。)

AWS: Amazon S3

Amazon S3はAWS上で提供される、オブジェクトストレージサービスです。バケット上のオブジェクトに対するアクセス制御ルールを定義するための方法を以下に示します。(以下では省略していますが、S3 Access Pointsという機能も存在します)

  • IAMポリシー:
    • IAMポリシーでアクセス制御を行う方法です。
    • 本記事はIAMポリシーの評価ロジックの比較を目的としているため、こちらの方法について確認します。
  • ACL(アクセスコントロールリスト):
    • オブジェクトごとにアクセス許可の設定が可能な仕組みです。
    • 以下の公式ドキュメントにあるように、現在は多くのユースケースでは選択されないものとなっています。
  • S3 Access Grants
    • 2023年に発表された、大規模なアクセス管理を行いたい場合の選択肢です。
    • IAM Identity Centerと連動したアクセス制御が可能になります

また、IAMポリシーベースでのアクセス制御には、大きく分けて以下の二通りの仕組みがあります。

  • ロールやユーザ、グループ等に対してポリシーを付与する方法
  • S3バケットに対して直接許可のルールを記述する方法 (バケットポリシー)

これらのうち、今回は前者の方法を用いて、IAMユーザに対して直接ポリシーを付与します。

設定

まず、AWSのポリシーについての簡単な解説をします。

  • IAMポリシーは複数の許可/拒否の設定文(Statement)から構成される。
  • Statementには「Effect(Allow/Denyのどちらか)」と「どのリソースに対して効果を発揮するか」、また「どの操作(Action)を対象とするか」を記述する。
  • 更に、任意でStatementが有効になる条件を設定可能(タグや現在時刻など様々な条件が存在する)。

これらを踏まえた上で、以下のようなIAMポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GrantAllAccess",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::okazu-bucket-policy-test-20241005",
                "arn:aws:s3:::okazu-bucket-policy-test-20241005/*"
            ]
        },
        {
            "Sid": "DenyPathAAccess",
            "Effect": "Deny",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::okazu-bucket-policy-test-20241005/pathA/*"
            ]
        }
    ]
}

このIAMポリシーについて解説します。

  • このポリシーには2つのステートメントがある。
  • 1つ目のステートメントでは okazu-bucket-policy-test-20241005 バケットに対するすべてのアクセスを許可する。
  • 2つ目のステートメントでは ​​okazu-bucket-policy-test-20241005 バケット内の、 pathA/ から始まる名前のオブジェクトに対するすべてのアクセスを拒否する。

AWSのIAMポリシーに関しては、以下のドキュメントにあるように、明示的な拒否のルールが最も優先されます。

そのため、上記のポリシーでは、pathA/ 以下のオブジェクトに対するアクセス拒否のルールが、バケット全体に対するアクセス許可のルールよりも優先されることが期待されます。

次に、userAというIAMユーザを作成し、このポリシーを付与します。

これにより、userAは当該バケットへの全面的なアクセスが許可されるが、pathA/ 以下へのアクセスのみ禁止される、という状態を実現しました。 また、S3には以下のような準備をしておきました。

  • okazu-bucket-policy-test-20241005 というバケットを作成。
    • (当初はバケットポリシーの挙動の確認のためのバケットでしたが、そのまま今回の検証にも流用したため、実態と乖離した名前になっております)
  • pathA/foo.txt, pathB/bar.txt をバケット以下に作成。

また、AWS CLIの準備として、userAのアクセスキーを発行してCLIで使うように設定しました。

確認

では、以下の2️通りの操作で期待通りの挙動をするかを確認します。

  • pathA/foo.txt を取得しようとした場合、拒否されるか
  • pathB/bar.txt を取得しようとした場合、許可されるか

まず以下について確認します。

  • pathA/foo.txt を取得しようとした場合、拒否されるか

これは、以下のコマンドで確認しました。

aws s3api get-object  --bucket okazu-bucket-policy-test-20241005 --key pathA/foo.txt foo.txt

その結果、以下のようにエラーが返ってきており、期待通り foo.txt を取得することはできませんでした。このため、禁止のルールは適切に適用されていることがわかります。

An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:iam::********:user/userA is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::okazu-bucket-policy-test-20241005/pathA/foo.txt" with an explicit deny in an identity-based policy
  • pathB/bar.txt を取得しようとした場合、許可されるか

これは、以下のコマンドで確認しました。

aws s3api get-object  --bucket okazu-bucket-policy-test-20241005 --key pathB/bar.txt ./bar

その結果、以下のようにレスポンスが帰ってきました。また、 bar.txt の中身は意図通りでした。

{
    "AcceptRanges": "bytes",
    "LastModified": "2024-10-25T15:21:00+00:00",
    "ContentLength": 4,
    "ETag": "\"c157a79031e1c40f85931829bc5fc552\"",
    "ContentType": "text/plain",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

以上から、「AWSのIAMポリシーにおいては拒否のルールの方が許可のルールより強い」ということがわかります。

補足: 特定pathのみオブジェクト一覧取得を禁止することは可能か?

今回はあくまで pathA/ 以下のオブジェクトをアクセスできないようにする、という目的のため簡単に「pathA/foo.txt が取得できるか」だけを確認しました。その結果、期待通りpathA/ 以下のすべてのオブジェクトに対する操作を禁止できていることが分かりました。

しかし、直感に反して「pathA/ 以下のオブジェクト一覧」は取得可能です。

これは、以下のような事情から発生しています。

  • AWSでオブジェクト一覧を取得する際には、s3:ListBucket の許可が必要
  • s3:ListBucket はバケットを対象に取る
  • 今回はバケットそのものに対する操作は全面的に許可している

以上より、オブジェクト一覧の取得は許可されていることがわかります。

これについては、例えば以下の記事などでは「s3:LIstBucket を呼び出すときに特定のprefixを渡せないようにするConditionを書く」という方法が指定されていました。

例えば、今回のケースに合わせて書くと以下のようなIAMポリシーを設定するという方法です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllAllow01",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:ListAllMyBuckets"
            ],
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Sid": "ListDeny01",
            "Action": "s3:ListBucket",
            "Effect": "Deny",
            "Resource": "arn:aws:s3:::okazu-bucket-policy-test-20241005",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "pathA/*"
                }
            }
        },
        {
            "Sid": "GetDeny01",
            "Action": "s3:GetObject",
            "Effect": "Deny",
            "Resource": "arn:aws:s3:::okazu-bucket-policy-test-20241005/pathA/*"
        }
    ]
}

実際に試したところ、確かにprefixを指定した場合、以下のように禁止されました。

aws s3api --output=text list-objects-v2  --bucket okazu-bucket-policy-test-20241005 --prefix "pathA/"

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:iam::329599622035:user/userA is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::okazu-bucket-policy-test-20241005" with an explicit deny in an identity-based policy

ここで、以下の list-objects-v2 のリファレンスを見たところ、「バケット内のオブジェクト一覧(最大1000個)が返ってくる」とあります。オブジェクトストレージのpathは単なるオブジェクトのキーの一部であることを考えると、prefixを指定しない場合すべてのオブジェクトが一覧表示の対象になると思われます。

このことを確認するために、以下のように--prefix "pathA/"のオプションを外したところ、バケット内のオブジェクトすべてが返されました。(表示の調整用途として、オブジェクトの名前とサイズだけを表示するためにqueryオプションを追加しています)

aws --output=text --profile userC s3api list-objects-v2  --bucket okazu-bucket-policy-test-20241005 --query='Contents[*].[Key, Size]'

pathA/  0
pathA/foo.txt   3
pathB/  0
pathB/bar.txt   4

以上から、特定のpath以下のオブジェクト一覧取得を禁止する方法を見つけることはできませんでした。 その他、S3に関するアクセス許可については、以下のドキュメントなどでより詳細に解説されています。

Google Cloud: Google Cloud Storage

Google Cloud Storage(GCS)は、Google Cloud において提供されているオブジェクトストレージサービスです。GCSのアクセス制御には、以下のような方法があります。

  • IAMベースでのアクセス制御
    • 大まかにはAWSと同じく、Google Cloud上のプリンシパル(Googleアカウントやサービスアカウント、Googleグループなど)に許可設定を付与する仕組みです。
  • ACL
    • こちらもS3のACLと似ており、オブジェクトレベルのアクセス制御を実現したい際など特殊なユースケースにおいて必要とされます。
    • 公式ドキュメントにおいても、基本的にはACLよりもIAMによる制御を推奨しています。
  • マネージドフォルダ
    • こちらも厳密にはIAMによる制御の一種ですが、フォルダレベルでのアクセス制御を実現するための機構です。

設定

Google CloudにおけるIAMの権限設定は許可と拒否が完全に分離されており、条件設定の粒度や設定内容も異なるためやや複雑です。

  • 基本は許可のみ
    • 許可する権限のセットをロールという形でまとめている。
    • 条件はプリンシパルにロールを付与する際に設定できる。
  • 通常の権限許可設定とは別に、拒否ポリシー という仕組みが存在する。
    • 許可と比較すると粒度は粗い。
      • 具体的には、許可の場合は操作対象のリソース名を対象に取る条件を設定できるが、拒否の場合はタグしか対象に取れない。
    • 拒否ポリシーはルールごとに、「拒否するプリンシパル」「拒否する権限」「拒否する条件」をセットとして管理する(ロールを作って付与、という許可の方式とは違い直接拒否のルールを付与する)
    • 試す場合には組織レベルで”拒否管理者”のロールが必要な点に注意。

IAMに関する基本的な概念は以下の公式ドキュメントにもまとまっているため、ぜひご一読ください。

以上を踏まえて、今回はまずサービスアカウント(Googleアカウントに紐づかないAPIを外部から利用する用途のためなどのアカウント)に対して、storage.objects.get の権限のみを(カスタムロールを作成して)付与しました。 また、カスタムロールの付与の際には、以下のような条件でpathA以下のオブジェクト以外のみアクセスできるようにしています。

!resource.name.startsWith("projects/_/buckets/okazu-blog-bucket-202410/objects/pathA/")

(備考: ! 演算子を使ってnot条件を設定したい場合は、キャプチャのように直接エディタで書く必要があります)

次に、GCSでは以下の準備をしました。

  • /okazu-blog-bucket-202410 というバケットを作成
  • pathA/foo.txt, pathB/bar.txt をバケット以下に作成

また上記のサービスアカウントを手元のgcloud CLIから使用するように準備しました。

確認

まずはAWSと同じく以下の2点を確認します。

  • pathA/foo.txtを取得しようとした場合、拒否されるか
  • pathB/bar.txtを取得しようとした場合、許可されるか

では、順番に確認します。

  • pathA/foo.txtを取得しようとした場合、拒否されるか
gcloud storage cp gs://okazu-blog-bucket-202410/pathA/foo.txt ./

  Completed files 0 | 0B                                                                                                                                                                             
ERROR: (gcloud.storage.cp) HTTPError 403: test-684@*****.iam.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).

期待通り、拒否されています。 次に、以下を確認します。

  • pathB/bar.txtを取得しようとした場合、許可されるか
gcloud storage cp gs://okazu-blog-bucket-202410/pathB/bar.txt ./
Copying gs://okazu-blog-bucket-202410/pathB/bar.txt to file://./bar.txt
  Completed files 1/1 | 4.0B/4.0B    

実際にダウンロードされたファイルを確認したところ、正しくダウンロードされており、こちらも意図通りでした。

補足: 許可を追加で割り当ててしまう落とし穴に注意

以上のやり方は、あくまで「pathA/ 以下のオブジェクトに対して許可を出していない」だけにすぎません。 これは「pathA/ 以下のオブジェクトに対する許可」を改めて付与した場合、それによって、pathA/ 以下にアクセス可能になるということを意味します。

例えば、条件を指定せずに別で storage.objects.get を含む権限を許可してしまうと、意図せずして pathA/ 以下にアクセス可能になってしまう事故が考えられるため、注意が必要です。

追加の確認: 拒否ポリシーは許可に優先されるか

先ほど粒度が粗いとした拒否ポリシーについて、許可と拒否、どちらが優先されるかを最後に確認してGoogle Cloudに関する検証は終わりとします。

以上のように拒否ポリシーを設定した後に、先ほどは許可されていた pathB/bar.txt へのアクセスを試みます。

 gcloud storage cp  gs://okazu-blog-bucket-202410/pathB/bar.txt ./
  Completed files 0 | 0B                                                                                                                                                                                                              
ERROR: (gcloud.storage.cp) HTTPError 403: [email protected] does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource due to an IAM deny policy.

以上のように、明示的に拒否ポリシーで拒否されたエラーメッセージが出るため、許可よりも拒否が強いことがわかります。

補足: 拒否ポリシーのユースケースについて

前述したように拒否ポリシーの条件設定は、タグをベースとしたもののみで、リソース名などのレベルでは設定ができません。 この点から、今回実現しようとしていた「GCSバケット内の特定のpath以下へのアクセスを禁止」という用途には使えないことがわかります。

では、Google Cloudの拒否ポリシーはどのようなユースケースが適しているのでしょうか。 この点については、以下の公式ドキュメントの一文が感覚的にはわかりやすいように思います。

Google Cloud リソースへのアクセスにガードレールを設定できます

抽象的な表現ですが、ガードレールという表現からは事故を防ぐ安全装置のようなニュアンスを読み取れます。 この表現と、実際の拒否ポリシーの挙動から、「プロジェクト全体に対して設定しておき、プロジェクトの管理者の設定ミスを防ぐ」ような粗い粒度の使い方が適しているように思われます。

Firebase: Firestore

Cloud Firestore(文中では単にFirestoreとします)はmBaaSプラットフォームであるFirebase上で提供されるNoSQLデータベースです。Firebaseが2014年にGoogleに買収されたため、現在はGoogle Cloudと連携する形で提供されています。

ちなみに、FirebaseにはオブジェクトストレージであるCloud Storage for Firebaseがありますが、プロダクトとしてはFirestoreの方が有名であると考え、Firestoreを採用しました。 Firestore上のデータに対するアクセス制御には、FirestoreとCloud Storage for Firebaseの中で使えるアクセス制御機構である、セキュリティルールを利用します。

これはCEL1で記述するアクセス制御のルールで、Firebaseが提供するIDaaSであるFirebase Authenticationと連携したルールも実現可能です。

設定

実際の設定内容に触れる前に、まずはFirebaseのセキュリティルールについて解説します。 以下は、セキュリティルールに関する公式ドキュメントです。

Firebaseのセキュリティルールの例を以下に示します。

   // rule1
   match /{document=**} {
        allow read, write  
    }

    // rule2
    match /pathA/{document=**} {
        allow read, write: if false;
    }

これは、セキュリティルールの一部の例で、この部分に含まれる2️つのルールはそれぞれ以下のような意味になります。

  1. データベース以下のすべてのデータに対する読み書きを許可する
  2. /pathA 以下のすべてのデータに対する読み書きを許可しない(「禁止」ではない点に注意)

pathA/ 以下にアクセスしようとした際、1番目は許可する、2番目は許可しない、となっておりAWSやGoogle Cloudのことを思い返すと2番目の方が優先されて禁止されるようにも思えますが、実際に /pathA/ 以下にアクセスすると以下のように許可されてしまいます。

これは、前述したドキュメントの中にもあるようにFirestoreのセキュリティルールではアクセス権を付与するルールが1つでも一致すれば、リクエストは許可されるという評価ロジックになっているためです。 そのため、許可するルールと許可しないルールが存在した場合、許可が優先されます(明示的な禁止が存在しない)。

このことから、データベースの規模が大きくなり、ルールが複雑化すればするほど思わぬアクセスを許可してしまう事故のリスクは(他のサービスに比べて)高くなると言えます。

実際に、セキュリティルールの記述に起因してアクセス制御の不備などの脆弱性につながる事例を、弊社はセキュリティ診断サービスの提供の中で多く確認しています。そうしたFirebase利用時に頻出するリスクについてまとめた記事も公開しておりますので、あわせてご覧ください。

さて、ここまでに確認した許可優先の原則を踏まえて、今回の意図に沿うように書き直したセキュリティルールが以下です。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

   match /{col}/{document=**} {
      allow read, write: if col != "pathA";  
    }

  }
}

修正後のルールでは、pathA/ 以下以外の全てに対してのアクセスを許可する、というルールになっています。(ちなみに前述した、GCSの検証の際も同じ趣旨の条件設定をしています)

検証

Firestoreでは、Web UI上でルールのテストができるため、それを使って以下を確認します。

  • pathA/foo を取得しようとした場合、拒否されるか
  • pathB/bar を取得しようとした場合、許可されるか

まず、以下から確認します。

  • pathA/foo を取得しようとした場合、拒否されるか

こちらは以下のように意図通り拒否されました。

では、次に許可設定が意図通り働いているかを、以下で確認します。

  • pathB/bar を取得しようとした場合、許可されるか

許可されました。

以上より、Firestoreでは明示的な拒否はできないため複雑な権限設計の難易度は高いものの、パスのマッチングと文字列比較を用いてパスベースでのアクセス制御は可能であることを示しました。

Azure: Blob Storage

Microoft AzureのオブジェクトストレージであるBlob Storageについて検証します。 Blob Storageのサービスは以下のようなモデルとなっています(関係ある部分だけを抜粋)。

  • ストレージアカウント: サービスを管理する1単位の概念で、想定されるワークロードなどによって異なる種類のアカウントが作成可能。
  • コンテナ: オブジェクトを配置する空間。ストレージアカウント内に複数作成可能。
  • BLOBオブジェクト: 実際に読み書きするファイル。

(公式ドキュメントより転載)

以下のドキュメント・画像が示すように、Blob Storageに対するアクセス許可の最小単位はコンテナです。

(Blob Storageに対するアクセス許可では、BLOBオブジェクト名を対象には取れない様子)

設定

AzureもGoogle Cloudと同じく、IAMの設定は許可のみで、ロールを割り当てる際に条件を設定できる点も同じです。また、拒否割り当て2という機能で限定的に拒否設定を実現できます(拒否割り当てについては後述します)。

まず、以下の準備をします。

  • ”path-a”, “path-b” という2つのコンテナを用意
  • ”foo.txt”, “bar.txt”というオブジェクトをそれぞれのコンテナに配置

また、Azure CLIからアクセスする際に使用するユーザを作成し、そのユーザでログインしておきます。

Azureでは、様々な単位(サブスクリプション、リソースグループ、ストレージアカウント、コンテナなど)でIAMの設定を作成することができますが、今回はストレージアカウント内で以下のような条件で「ストレージ BLOB データ閲覧者」のロールを検証用のユーザに付与します。

(
 (
  !(ActionMatches{'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read'})
 )
 OR 
 (
  @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringNotEquals 'path-a'
 )
)

検証

では、以下の2️通りの操作をAzure CLIで実行して、期待通りの挙動をするかを確認します。

  • path-aのfoo.txtへのアクセスが拒否されるか
  • path-bのbar.txtへのアクセスが許可されるか

まず、

  • path-aのfoo.txtへのアクセスが拒否されるか

ですが、こちらは以下の通り拒否されます。

az storage blob download  --auth-mode login --blob-url https://okazuiamtest.blob.core.windows.net/path-a/foo.txt 

You do not have the required permissions needed to perform this operation.
Depending on your operation, you may need to be assigned one of the following roles:
    "Storage Blob Data Owner"
    "Storage Blob Data Contributor"
    "Storage Blob Data Reader"
    "Storage Queue Data Contributor"
    "Storage Queue Data Reader"
    "Storage Table Data Contributor"
    "Storage Table Data Reader"

If you want to use the old authentication method and allow querying for the right account key, please use the "--auth-mode" parameter and "key" value.

また、

  • path-bのbar.txtへのアクセスが許可されるか

についても、意図通り許可されます。

az storage blob download  --auth-mode login --blob-url https://okazuiamtest.blob.core.windows.net/path-b/bar.txt 
Finished[#############################################################]  100.0000%
bar

補足: 拒否割り当てについて

前述した通り、Azureには拒否割り当てという仕組みがあり、これを使うことで限定的な範囲で特定の操作の拒否を実現できます。

ただし、「削除の拒否」「書き込みと削除の拒否」のみが実現可能となっており、今回実現したかった「読み取りを含めた完全なアクセスの拒否」は実現できません。 以下のドキュメントで拒否割り当ての概要の紹介がされています。

この中に、以下のような記述が見られます。

独自の拒否割り当てを直接作成することはできません。 ただし、デプロイ スタックの作成中に拒否設定を指定できます。これにより、そのデプロイ スタックのリソースが所有する拒否の割り当てが作成されます。

つまり、前提としてデプロイスタックという仕組みの管理下にリソースを置かないと拒否割り当ては使えないということです。デプロイスタックは後述しますが、Azureのリソースを記述するための独自のテンプレート言語を用いたIaCに近い仕組みです。

また、実際の作成方法について書かれているドキュメント内には、例として以下のように書かれています。

たとえば、テンプレートをリソース グループ スコープにデプロイするサブスクリプション スコープでデプロイ スタックを作成し、拒否設定モード DenyDelete を使用すると、指定したリソース グループに管理対象リソースを簡単にプロビジョニングでき、それらのリソースへの削除の試行をブロックできます

このデプロイスタックの仕組みは現在はプレビュー中であり、CLIからしか作成できません。 CLIからは以下のように作成することが可能です。

az stack group create \
  --name '<deployment-stack-name>' \
  --resource-group '<resource-group-name>' \
  --template-file '<bicep-file-name>' \
  --action-on-unmanage 'detachAll' \
  --deny-settings-mode 'denyDelete' \
  --deny-settings-excluded-actions 'Microsoft.Compute/virtualMachines/write Microsoft.StorageAccounts/delete' \
  --deny-settings-excluded-principals '<object-id> <object-id>'

このコマンドの中で注目すべき点は二点あり、一つは --deny-settings-mode ‘denyDelete’という箇所で、これは前述したように削除を禁止する設定です。 もう一点は、--template-file というオプションで、デプロイスタックに渡すbicepのテンプレートファイルを指定します。

試しに、以下のようなbicepファイルをtest.bicepして保存してみます。

resource storage 'Microsoft.Storage/storageAccounts@2023-04-01' existing = {
  name: 'okazuiamtest'
}

resource service 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = {
  name: 'default'
  parent: storage
}

resource symbolicname 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = {
  name: 'cantdeletecontainer'
  parent: service
  properties: {
    defaultEncryptionScope: '$account-encryption-key'
    denyEncryptionScopeOverride: false
    immutableStorageWithVersioning: {
      enabled: false
    }
  }
}

次に、以下のコマンドで、拒否割り当てを適用しつつBlob Storageのコンテナを作成できます。

az stack group create --name 'deny-stack' \
    --resource-group '202410-blog' \
    --template-file 'test.bicep' \
    --action-on-unmanage 'detachAll' \
    --deny-settings-mode 'denyDelete' 

そして、試しにWebコンソールからコンテナを削除しようとしたところ、以下のように管理者であっても拒否されました。

このため、誤操作による削除を防ぐなどの目的では一定の効果が見込めるように思われます。 ただし、現状ではデプロイスタック自体がプレビュー状態かつ、拒否可能な権限の粒度も非常に粗いため、有効に使えるケースはまだ限定的と考えられます。

以上より、Azureは基本的には許可しかないため、IAMの条件で操作対象外にしたいリソースを含まないような記述が必要であり、Google Cloudと同じく誤って別のロールを付与する際に、許可範囲を広げないように注意する必要があります。

おわりに

今回の記事では、各種クラウドサービスのアクセス制御について、その設定や「それぞれのアクセス制御は拒否優先なのかどうか」を調査しました。 IAMベースでのアクセス制御という点ではFirestore以外のサービスは共通していますが、それぞれ制御の粒度やアクセス拒否の実現レベルに大きな差がある点は特徴的でした。

今回はIAMサービスについてのみ注目しましたが、実際にクラウドを活用したシステムやアプリケーションをセキュアに運用するためには多層防御的な緩和策や攻撃を検知する仕組みなどが必要です。また、それぞれのクラウドサービスごとに対応したセキュリティ関連のサービスやベストプラクティスがあるため、IAMを完璧にしたから安心、とはならない点にご注意ください。

Flatt Securityではクラウドレイヤーからアプリケーションレイヤーまで包括的にリスクをチェック可能なセキュリティ診断(脆弱性診断サービス)を提供しています。クラウド診断(CSPM)機能とWebアプリケーション・Web API診断機能を併せ持つSaaS「Shisho Cloud(シショウ クラウド)」を利用すれば月額5万円からの診断実施が可能です。また、弊社エンジニアによる手動診断によって、認証・認可やビジネスロジックの脆弱性などの観点も含む高度な検査も提供することが可能です。

各サービスに関してぜひお気軽にお問い合わせください。

また、Flatt Security はセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式X のフォローをぜひお願いします!

では、ここまでお読みいただきありがとうございました。

Flatt Securityは外部パートナーと連携して技術記事を発信しています

本稿はFlatt Securityの外部パートナーが執筆し、Flatt Securityが監修を行った記事です。通常、企業の技術ブログは自社の技術力やカルチャーの発信のため、雇用関係にある社内メンバーの執筆によって発信されることがほとんどだと思います。

一方、Flatt Securityの技術ブログでは、本稿のようにセキュリティの知見をお持ちの外部の方に依頼しているケースがあります。種々の脆弱性情報や情報発信に知見を持つFlatt Securityの技術ブログ編集部が執筆者の方と連携することで、テーマや構成の検討・レビューをサポートしています。

これは、Flatt Securityの技術ブログの目的が自社のアピールにとどまらず、セキュリティに関する有益な情報をより多く社会に還元し、セキュリティ企業とセキュリティサービス利用者の間の情報の非対称性を無くすことを目的としているためです。

本文章は執筆者がセキュリティ診断のようなFlatt Securityのサービス提供に直接的に関わっているかのような誤解を防ぐために明記していますが、執筆にご興味を持たれた方はお問い合わせください。


  1. https://cel.dev/overview/cel-overview?hl=ja
  2. 公式ドキュメント内では、いくつかの呼び方がされていますが、”deny assignments”を訳して拒否割り当てと呼びます。