複数のシステムで Amazon Athena を堅牢かつ柔軟に管理する  

# はじめに  

* 複数の AWS アカウントにまたがる複数のシステムが存在する  
* システム間で共通のテーブル構造を Athena で用いたい  
* ただ、お互いのシステムがお互いの管理するデータを参照するリスクは排除したい  
* 今後もシステムが増えていくことが予定されている  

このような要件があるシステム群においての秘匿性および柔軟性の高いアーキテクチャを考えてみました。  
当記事ではアーキテクチャの留意点やソースコードを踏まえつつまとめます。  

## 前提  

複数の AWS アカウントは１つの Organizations に属しているものとします。  

# サンプルコード

下記リポジトリをご参照ください。以降は当リポジトリのコードに基づいて解説をしていきます。    

https://github.com/blasg5884/aws-athena-robust-control/tree/main  

# アーキテクチャ  

中央アカウントに Athena で用いるデータバケットを用意します。  
各システムアカウントに対しては後述の構成要素のリソースを [AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html) および [AWS CloudFormation StackSets](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-concepts.html) で展開することで AWS アカウント・システムが増える場合のスケーラビリティを確保します。  

![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276241/43160a9a-aa52-fba2-fc48-a1e0a7430713.png)  

## 構成要素  

上記のアーキテクチャの構成要素をそれぞれ説明します。  

### ユニークな要素  

当アーキテクチャにおいて１つだけ必要な要素になります。   
リポジトリのコードとしては [00-unique.yaml](https://github.com/blasg5884/aws-athena-robust-control/blob/main/00-unique.yaml) です。  

* [Amazon S3 - Bucket](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/Welcome.html)  
  Athena で利用するデータソース、およびクエリ実行結果ファイルの保管に用います。  
  S3 バケットの第一階層はテーブル名（および出力結果フォルダ）、第二階層はシステム名に対応します。  

  * バケット構成  
      ```
      athena-sample-bucket
         ├── table-1/System-1/
         ├── table-1/System-2/
         ├── table-1/System-3/
         ├── table-2/System-1/
         ├── table-2/System-2/
         ├── table-2/System-3/
         ├── output/System-1/
         ├── output/System-2/
         └── output/System-3/
      ```
   
### アカウント単位の要素

当アーキテクチャにおいて AWS アカウントごとに１つだけ必要な要素になります。  
リポジトリのコードとしては [01-account.yaml](https://github.com/blasg5884/aws-athena-robust-control/blob/main/01-account.yaml) です。  
AWS CloudFormation StackSets で Organizations 内の AWS アカウントに展開されるものとします。  
（当記事では StackSets の管理・展開方法については割愛します）   

* [AWS Glue - Database](https://docs.aws.amazon.com/ja_jp/glue/latest/dg/define-database.html)  
  複数の AWS Glue - Table の管理に用います。  

* [AWS Glue - Table](https://docs.aws.amazon.com/ja_jp/glue/latest/dg/tables-described.html)  
  テーブルの構造、データの参照を管理します。   
  テーブルは原則システムごとにパーティション化されます。   

* [AWS IAM - Policy](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies.html)  
  後述の AWS IAM - Role のアタッチするポリシーとして用います。  
  ポリシー記述内で *`${aws:PrincipalTag/System}`* などと記述することで IAM ロールに付与された *`System`* タグの値に応じてアクセス制御が可能となります。  
    ```yaml
    'arn:aws:s3:::${BucketName}/*/${aws:PrincipalTag/System}/*' 
    ```

### システム単位の要素    

当アーキテクチャにおいてシステムごとに１つだけ必要な要素になります。  
リポジトリのコードとしては [02-system.yaml](https://github.com/blasg5884/aws-athena-robust-control/blob/main/02-system.yaml) です。  
AWS CloudFormation で展開されるものとします。  

* [AWS Glue - Partition](https://docs.aws.amazon.com/ja_jp/glue/latest/dg/partition-indexes.html)  
  テーブルのパーティションに用います。  
  システムごとの S3 バケットに保管されているデータ参照先を管理します。  
* [Amazon Athena - Workgroup](https://docs.aws.amazon.com/ja_jp/athena/latest/ug/user-created-workgroups.html)  
  ワークグループ単位での実行履歴の管理やクエリ実行時の実行結果ファイルの保管に用います。  
  システムごとの実行結果ファイルの保管先を管理します。  
  *`s3://${S3Bucket}/output`* 配下にシステムごとに実行結果が出力されるように設定します。  

* [AWS IAM - Role](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html)  
  [Attribute based access control (ABAC)](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/introduction_attribute-based-access-control.html) を用いて、IAM ロールのタグに基づいた S3 バケットに対するアクセス制御に用います。
  システムに付与されたタグの値を含む S3 バケットのパスのみアクセス可能な状態を担保します。  

## このアーキテクチャのねらい

S3 を１つだけ用意することで、すべてのシステムのデータを１つのバケットに集約します。  
S3 を参照するシステムとはクロスアカウントとなるので、明示的なバケットポリシーでの許可が必要となります。  

以下の通りのバケットポリシーを S3 バケットに設定することで同じ Organizations に属する AWS アカウントの指定のロールからのアクセスを許可します。  

```（抜粋）00-unique.yaml
Statement:
  - Action:
      - s3:GetBucketLocation
      - s3:GetObject
      - s3:ListBucket
      - s3:ListBucketMultipartUploads
      - s3:ListMultipartUploadParts
      - s3:AbortMultipartUpload
      - s3:PutObject
    Condition:
      StringEquals:
        aws:PrincipalOrgID:
          - !Ref OrganizationID
      StringLike:
        aws:PrincipalArn:
          - arn:aws:iam::*:role/SystemAccess*
    Effect: Allow
    Principal: '*'
    Resource:
      - !Sub 'arn:aws:s3:::${S3Bucket}'
      - !Sub 'arn:aws:s3:::${S3Bucket}/*'
```

ただ、このままでは各システムが横断的に S3 のファイルにアクセスできてしまうため、データの秘匿性を確保できません。そこで、下記のコードのようにシステムが利用する IAM ポリシーにて S3 の参照できるパスを制御します。  

*`${aws:PrincipalTag/System}`* によって IAM ロールに付与される *`System`* タグによって、参照できるパスを動的に変えることが可能になります。  

```（抜粋） 01-account.yaml
Statement:
  - Action:
      - s3:GetBucketLocation
      - s3:GetObject
      - s3:ListBucket
      - s3:ListBucketMultipartUploads
      - s3:ListMultipartUploadParts
      - s3:AbortMultipartUpload
      - s3:PutObject
    Effect: Allow
    Resource:
      - !Sub 'arn:aws:s3:::${BucketName}'
      - !Join
        - ''
        - - 'arn:aws:s3:::'
          - !Ref BucketName
          - '/*/${aws:PrincipalTag/System}/*'
```

システムが受け持つ IAM ロールに上記のポリシーをアタッチした上で作成し、そのロールに対して *`System`* タグを付与します。  
これにより例えば、システム1 が利用する IAM ロールの *`System`* タグの値が *`System-1`* であれば、*`s3://${BucketName}/*/System-1/*`* のみ参照可能な状態を構築することができます。  

実際に Athena でクエリを実行する際は *`system`* パーティションに具体的な値を指定して実行することで、S3 の適切なロケーションに保管されているデータに対してクエリすることが可能です。  

```sample-athena-ok.sql
SELECT * FROM sample_table WHERE system = 'System-1';
```

![success-system-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276241/fc0c8ae8-22b1-ed41-84f6-f793a3589ee8.png)


この場合、システム1 においては *`s3://${BucketName}/*/System-1/*`* 配下のファイルしか参照をすることができず、システム2 が管理する *`s3://${BucketName}/*/System-2/*`* 配下のファイルを参照しようとすると IAM ポリシーにより拒否されます。  

```sample-athena-ng.sql
SELECT * FROM sample_table WHERE system = 'System-2';
```

![fail-system-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276241/4dbc6e64-8a2b-1d0c-c83a-6d865ab8a067.png)

実際にシステム1 の権限を用いて、システム2 が管理するデータに対してクエリを実行すると参照権限が不足しており、クエリ実行結果の確認ができません。  

また、パーティション未指定でのクエリに対しても参照権限の不足により失敗してしまいます。  

![fail-all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276241/49598cd2-99dd-aba7-841f-1e381a148951.png)

これらの権限管理により、システム間ではお互いのデータを参照することができず、データの参照リスクを排除することができます。  

# おわりに

当記事ではシステムごとに制御としていますが、ユーザーごとやアカウントごと、またはリージョンごとといった任意の分け方にも応用できるかと思います。  

また、当記事では触れませんでしたが、特定の管理者がシステム横断的にデータを分析するケースがある場合は、横断的に S3 バケットを参照する権限を付与したロールを特定の管理者に付与した上で、パーティションを未指定でクエリを実行することで、全システムのデータを横断的に解析することが可能になります。   

![success-all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276241/4da74316-fb20-d06c-564f-4b1a6b7d8d8b.png)  

Amazon Athena を利用するにあたっての構成に悩んでいる方の参考になれば幸いです。  

# 参考文献  

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/cross-account-permissions.html