Amazon Web Services ブログ

マルチテナント型 SaaS アプリケーションのための Amazon Forecast の構成

この記事は “Configure Amazon Forecast for a multi-tenant SaaS application” を翻訳したものです。

本投稿は、AWS の Sr. Software Development Engineer の Gunjan Garg、Technical Account Manager の Matias Battaglia、ISV Solutions Architect の Rakesh Ramadas により寄稿されました。

Amazon Forecast は、 Amazon.com で予測に使用されているのと同じ技術をベースにしたフルマネージドサービスです。Forecast は、機械学習(ML)を用いて、時系列データと追加の変数を組み合わせ、高精度な予測を構築します。Forecast は、ML の経験がなくても始められます。お客様に必要なのは、過去のデータと予測に影響を与える可能性のある追加データだけです。

近年、ソフトウェアベンダーではマルチテナントのソリューションを提供するために、SaaS(Software as Service)モデルを使用するようになってきています。ベンダーは、規制やコンプライアンスの要件を満たすために、さまざまな異なるアーキテクチャモデルで SaaS アプリケーションを構築することができます。SaaS モデルによっては、Forecast のようなリソースがテナント間で共有されます。SaaS ソリューションを展開する際には、Forecast のデータアクセス、モニタリング、課金についてテナントごとに検討する必要があります。

この記事では、AWS Identity and Access Management (IAM) の Attribute Based Access Control (ABAC) を使用してテナントの分離を提供し、マルチテナントの SaaS アプリケーション内で Forecast を使用する方法を解説しています。ABAC は、テナント間でリソースを分離するために使用できる強力なアプローチです。

この記事では、ABAC の考え方と Forecast を使用してテナント用の IAM ポリシーを設定するためのガイダンスを提供します。構成のデモのために、TenantATenantB という 2 つのテナントを設定し、SaaS アプリケーションの文脈での Forecast のユースケースを紹介します。このユースケースでは、TenantBTenantA のリソースを削除することができず、その逆もまた同じです。次の図は、今回のアーキテクチャを表しています。

全体アーキテクチャ

TenantATenantB は、Amazon Elastic Kubernetes Service (Amazon EKS) 内でマイクロサービスとして動作するサービスを持っています。テナントアプリケーションは、ビジネスフローの一部として Forecast を使用しています。

Forecast のデータ取り込み

Forecast は、テナントの Amazon Simple Storage Service(Amazon S3)バケットから Forecast が管理する S3 バケットにデータをインポートします。データは、Forecast が管理する鍵、または AWS Key Management Service(AWS KMS)により管理されるテナント固有の鍵を使用して、転送時と保管時に自動で暗号化することができます。テナント固有の鍵は、オンボーディングの一部として SaaS アプリケーションによって作成されるか、テナントが AWS KMS を使用して独自のカスタマーマネージドキー(CMK)を提供することができます。テナント固有の鍵の権限を無効化すると、Forecast はテナントのデータを使用できなくなります。マルチテナント SaaS 環境では、テナント固有の鍵とテナントごとの IAM ロールを使用することを推奨しています。これにより、テナント単位でデータを保護することができます。

ソリューションの概要

Amazon S3 上のデータは、テナントのアクセスを分離するために複数の方法でパーティショニングすることができます。この記事では、2つの戦略について説明します。

  • テナントごとに1つの S3 バケットを使用する
  • 1つの S3 バケットを使用し、プレフィックスでテナントデータを区切る

アクセス分離の戦略の詳細については、Storing Multi-Tenant Data on Amazon S3 GitHub レポジトリーを参照してください。

1番目の方法の、テナントごとに1つのバケットを使用する方法の場合、IAM ポリシーを使用して、特定のテナント用の S3 バケットへのアクセスを制限します。たとえば、次のようになります。

s3://tenant_a    [ Tag tenant = tenant_a ]
s3://tenant_b     [ Tag tenant = tenant_b ]

テナントごとのバケット

考慮すべき点として、1アカウントあたりの S3 バケット数のハードリミットがあります。この制限を乗り越えるために、マルチアカウント戦略も検討する必要があります。

2番目の方法では、1つの S3 バケットで S3 プレフィックスを使用してテナントデータを分離します。IAM ポリシーを使用して、テナントごとにバケットプレフィックス内のアクセスを制限します。たとえば、次のようになります。

s3://<bucketname>/tenant_a

プレフィックスでテナントデータを区切る

この記事では、1つのバケット内で S3 プレフィックスを割り当てる2番目の方法を使用します。また、AWS KMS の CMK を使用してテナントデータの暗号化をします。

テナントのオンボーディング

SaaS アプリケーションは、新しいテナントを環境に導入するためのスムーズな方法が重要です。このため、新しいテナントを作成するために必要なすべての要素のプロビジョニングと設定を成功させるために、複数のコンポーネントをオーケストレーションする必要があります。このプロセスは、SaaS アーキテクチャでは、テナントオンボーディングと呼ばれます。このプロセスは、テナントが直接開始することもできますし、プロバイダーが管理するプロセスの一部として開始することもできます。次の図は、オンボーディングプロセスの一環として、テナントごとに Forecast を構成するフローを表しています。

テナントごとに Forecast を構成するフロー

リソースには、テナント情報がタグ付けされています。この記事では、リソースにテナントの値(例:tenant_a)をタグ付けしています。

Forecast ロールの作成

この IAM ロールは、Forecast がテナントごとに引き受けるものです。Forecast が顧客アカウントの Amazon S3 および AWS KMS とやり取りできるように、以下のポリシーを適用する必要があります。ロールは tenant タグでタグ付けされます。たとえば、次のようになります。

TenantA create role Forecast_TenantA_Role  [ Tag tenant = tenant_a ]
TenantB create role Forecast_TenantB_Role [ Tag tenant = tenant_b ]

ポリシーを作成する

この次のステップでは、Forecast ロールのポリシーを作成します。この記事では読みやすくするために2つのポリシーに分割していますが、要件に応じて作成してください。

ポリシー1: Forecast の読み取り専用アクセス

以下のポリシーは、Forecast リソースの describelistquery の権限を与えるものです。このポリシーでは、Forecast を読み取り専用に制限します。以下のコードの tenant タグを検証する Condition 部分では、リソースの tenant タグの値がプリンシパルの tenant タグと一致することをチェックします。詳細については、太字のコードを参照してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DescribeQuery",
            "Effect": "Allow",
            "Action": [
                "forecast:GetAccuracyMetrics",
                "forecast:ListTagsForResource",
                "forecast:DescribeDataset",
                "forecast:DescribeForecast",
                "forecast:DescribePredictor",
                "forecast:DescribeDatasetImportJob",
                "forecast:DescribePredictorBacktestExportJob",
                "forecast:DescribeDatasetGroup",
                "forecast:DescribeForecastExportJob",
                "forecast:QueryForecast"
            ],
            "Resource": [
                "arn:aws:forecast:*:<accountid>:dataset-import-job/*",
                "arn:aws:forecast:*:<accountid>:dataset-group/*",
                "arn:aws:forecast:*:<accountid>:predictor/*",
                "arn:aws:forecast:*:<accountid>:forecast/*",
                "arn:aws:forecast:*:<accountid>:forecast-export-job/*",
                "arn:aws:forecast:*:<accountid>:dataset/*",
                "arn:aws:forecast:*:<accountid>:predictor-backtest-export-job/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/tenant":"${aws:PrincipalTag/tenant}"
                }
            }
        },
        {
            "Sid": "List",
            "Effect": "Allow",
            "Action": [
                "forecast:ListDatasetImportJobs",
                "forecast:ListDatasetGroups",
                "forecast:ListPredictorBacktestExportJobs",
                "forecast:ListForecastExportJobs",
                "forecast:ListForecasts",
                "forecast:ListPredictors",
                "forecast:ListDatasets"
            ],
            "Resource": "*"
        }
    ]
}

ポリシー2:Amazon S3 および AWS KMS のアクセスポリシー

以下のポリシーは、AWS KMS への権限付与と、S3 テナントプレフィックスへのアクセス権を付与するものです。以下のコードの tenant タグを検証する Condition 部分では、リソースの tenant タグの値がプリンシパルの tenant タグと一致することをチェックします。詳細については、太字のコードを参照してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "KMS",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt",
                "kms:Encrypt",
                "kms:RevokeGrant",
                "kms:GenerateDataKey",
                "kms:DescribeKey",
                "kms:RetireGrant",
                "kms:CreateGrant",
                "kms:ListGrants"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/tenant":"${aws:PrincipalTag/tenant}"
                }
            }
        },
        {
            "Sid": "S3Access",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject", 
                "s3:PutObject",
                "s3:GetObjectVersionTagging",
                "s3:GetObjectAcl",
                "s3:GetObjectVersionAcl",
                "s3:GetBucketPolicyStatus",
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads",
                "s3:ListAccessPoints",
                "s3:GetObjectVersion"
            ],
            "Resource": [
                "arn:aws:s3:::<bucketname>/*",
                "arn:aws:s3:::<bucketname>"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "${aws:PrincipalTag/tenant}",
                        "${aws:PrincipalTag/tenant}/*"
                    ]
                }
            }
        }
    ]
}

テナント固有の鍵の作成

ここで、テナントごとに AWS KMS にテナント固有の鍵を作成し、tenant タグの値でタグ付けします。あるいは、テナントが自分の鍵を AWS KMS に持ち込むことも可能です。先ほど作ったロール(Forecast_TenantA_Role または Forecast_TenantB_Role)に、テナント固有の鍵へのアクセス権を付与します。

例えば、次のスクリーンショットは、tenanttenant_a のキーと値のペアを示しています。

tenant と tenant_a のキーと値のペア

次のスクリーンショットは、この鍵を使用できる IAM ロールを示しています。

鍵を使用できる IAM ロール

アプリケーションロールの作成

2番目に作成するロールは、テナントごとの SaaS アプリケーションに引き受けられます。アプリケーションが Forecast、Amazon S3、AWS KMS と対話できるように、次のポリシーを適用する必要があります。ロールは tenant タグでタグ付けします。例えば、次のようになります。

TenantA create role TenantA_Application_Role [ Tag tenant = tenant_a ]
TenantB create role TenantB_Application_Role [ Tag tenant = tenant_b ]

ポリシーの作成

次に、アプリケーションロールのポリシーを作成します。この記事では、読みやすくするために2つのポリシーに分割していますが、要件に応じて作成してください。

ポリシー1: Forecast へのアクセス

次のポリシーは、Forecast リソースの作成、更新、削除の権限を与えるものです。このポリシーは、リソース作成時のタグの適用を強制します。さらに、リソースの listdescribedelete の操作にそれぞれのテナントを制限します。このポリシーは、Forecast がロールを引き受けることを許可する IAM PassRole を持ちます。

次のコードの tenant タグを検証する Condition 部分では、tenant タグの値がテナントと一致することを確認します。詳細については、太字のコードを参照してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "CreateDataSet",
            "Effect": "Allow",
            "Action": [
                "forecast:CreateDataset",
                "forecast:CreateDatasetGroup",
                "forecast:TagResource"
            ],
            "Resource": [
                "arn:aws:forecast:*:<accountid>:dataset-import-job/*",
                "arn:aws:forecast:*:<accountid>:dataset-group/*",
                "arn:aws:forecast:*:<accountid>:predictor/*",
                "arn:aws:forecast:*:<accountid>:forecast/*",
                "arn:aws:forecast:*:<accountid>:forecast-export-job/*",
                "arn:aws:forecast:*:<accountid>:dataset/*",
                "arn:aws:forecast:*:<accountid>:predictor-backtest-export-job/*"
            ],
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "aws:TagKeys": [ "tenant" ]
                },
                "StringEquals": {
                    "aws:RequestTag/tenant": "${aws:PrincipalTag/tenant}"
                }
            }
        },
        {
            "Sid": "CreateUpdateDescribeQueryDelete",
            "Effect": "Allow",
            "Action": [
                "forecast:CreateDatasetImportJob",
                "forecast:CreatePredictor",
                "forecast:CreateForecast",
                "forecast:CreateForecastExportJob",
                "forecast:CreatePredictorBacktestExportJob",
                "forecast:GetAccuracyMetrics",
                "forecast:ListTagsForResource",
                "forecast:UpdateDatasetGroup",
                "forecast:DescribeDataset",
                "forecast:DescribeForecast",
                "forecast:DescribePredictor",
                "forecast:DescribeDatasetImportJob",
                "forecast:DescribePredictorBacktestExportJob",
                "forecast:DescribeDatasetGroup",
                "forecast:DescribeForecastExportJob",
                "forecast:QueryForecast",
                "forecast:DeletePredictorBacktestExportJob",
                "forecast:DeleteDatasetImportJob",
                "forecast:DeletePredictor",
                "forecast:DeleteDataset",
                "forecast:DeleteDatasetGroup",
                "forecast:DeleteForecastExportJob",
                "forecast:DeleteForecast"
            ],
            "Resource": [
                "arn:aws:forecast:*:<accountid>:dataset-import-job/*",
                "arn:aws:forecast:*:<accountid>:dataset-group/*",
                "arn:aws:forecast:*:<accountid>:predictor/*",
                "arn:aws:forecast:*:<accountid>:forecast/*",
                "arn:aws:forecast:*:<accountid>:forecast-export-job/*",
                "arn:aws:forecast:*:<accountid>:dataset/*",
                "arn:aws:forecast:*:<accountid>:predictor-backtest-export-job/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/tenant": "${aws:PrincipalTag/tenant}"
                }
            }
        },
        {
            "Sid": "IAMPassRole",
            "Effect": "Allow",
            "Action": [
                "iam:GetRole",
                "iam:PassRole"
            ],
            "Resource": "--Provide Resource ARN--"
        },
        {
            "Sid": "ListAccess",
            "Effect": "Allow",
            "Action": [
                "forecast:ListDatasetImportJobs",
                "forecast:ListDatasetGroups",
                "forecast:ListPredictorBacktestExportJobs",
                "forecast:ListForecastExportJobs",
                "forecast:ListForecasts",
                "forecast:ListPredictors",
                "forecast:ListDatasets"
            ],
            "Resource": "*"
        }
    ]
}

ポリシー2:Amazon S3、AWS KMS、Amazon CloudWatch、およびリソースグループへのアクセス

以下のポリシーは、Amazon S3 、AWS KMS リソース、そして Amazon CloudWatch へのアクセス権限を与えるものです。これは、テナント固有の S3 プレフィックスとテナント固有の CMK へのアクセスを制限しています。テナントを検証する Condition は、太字のコードです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "S3Storage",
            "Effect": "Allow",
            "Action": [
                "s3:*" ---> To be modifed based on application needs
            ],
            "Resource": [
                "arn:aws:s3:::<bucketname>",
                "arn:aws:s3:::<bucketname>/*"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [ "${aws:PrincipalTag/tenant}", "${aws:PrincipalTag/tenant}/*" ]
                }
            }
        },
  {
            "Sid": "ResourceGroup",
            "Effect": "Allow",
            "Action": [
                "resource-groups:SearchResources",
                "tag:GetResources",
                "tag:getTagKeys",
                "tag:getTagValues",
                "resource-explorer:List*",
                "cloudwatch:PutMetricData"
            ],
            "Resource": "*"
        },
        {
            "Sid": "KMS",
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:CreateGrant",
                "kms:RevokeGrant",
                "kms:RetireGrant",
                "kms:ListGrants",
                "kms:DescribeKey",
                "kms:GenerateDataKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/tenant": "${aws:PrincipalTag/tenant}"
                }
            }
        }
    ]
}

リソースグループの作成

リソースグループを使用すると、タグ付けされたすべてのリソースをテナントから照会できるようになります。次のサンプルコードでは、AWS Command Line Interface(AWS CLI)を使用して、TenantA 用のリソースグループを作成しています。

aws resource-groups create-group --name TenantA --tags tenant=tenant_a --resource-query '{"Type":"TAG_FILTERS_1_0", "Query":"{\"ResourceTypeFilters\":[\"AWS::AllSupported\"],\"TagFilters\":[{\"Key\":\"tenant\", \"Values\":[\"tenant_a\"]}]}"}'

Forecast アプリケーションフロー

次の図は、Forecast アプリケーションの流れを示しています。アプリケーションサービスはテナントの IAM ロールを引き受け、そのビジネスフローの中で Forecast API を呼び出します。

Forecast アプリケーションの流れ

TenantB 用の Predictor を作成する

作成されたリソースには、tenant タグを付ける必要があります。以下のコードは、Python(Boto3)API を使用して、TenantB 用の Predictor(予測子)を作成します(詳細は太字のコードを参照してください)。

# Run under TenantB role TenantB_Application_Role
session = boto3.Session() 
forecast = session.client(service_name='forecast') 
...
response=forecast.create_dataset(
                    Domain="CUSTOM",
                    DatasetType='TARGET_TIME_SERIES',
                    DatasetName=datasetName,
                    DataFrequency=DATASET_FREQUENCY, 
                    Schema = schema,
                    Tags = [{'Key':'tenant','Value':'tenant_b'}],
                    EncryptionConfig={'KMSKeyArn':'KMS_TenantB_ARN', 'RoleArn':Forecast_TenantB_Role}
)
...
create_predictor_response=forecast.create_predictor(
                    ...
                    EncryptionConfig={ 'KMSKeyArn':'KMS_TenantB _ARN', 'RoleArn':Forecast_TenantB_Role}, Tags = [{'Key':'tenant','Value':'tenant_b'}],
                      ...
                      }) 
predictor_arn=create_predictor_response['PredictorArn']

TenantB の Predictor で予測を作成する

次のコードは、Python(Boto3)API を使用して、先ほど作成した Predictor で予測を作成するものです。

# Run under TenantB role TenantB_Application_Role
session = boto3.Session() 
forecast = session.client(service_name='forecast') 
...
create_forecast_response=create_forecast_response=forecast.create_forecast(
ForecastName=forecastName,
             PredictorArn=predictor_arn,
             Tags = [{'Key':'tenant','Value':'tenant_b'}])
tenant_b_forecast_arn = create_forecast_response['ForecastArn']

Forecast リソースへのアクセスを検証する

ここでは、各テナントのみが Forecast リソースにアクセスできることを確認します。異なるテナントの Forecast リソースにアクセス、変更、削除を行うとエラーが発生します。以下のコードでは、Python(Boto3)API を使用して、TenantA が TenantB Forecast リソースを削除しようとする様子を示しています。

# Run under TenantA role TenantA_Application_Role
session = boto3.Session() 
forecast = session.client(service_name='forecast') 
..
forecast.delete_forecast(ForecastArn= tenant_b_forecast_arn)

ClientError: An error occurred (AccessDeniedException) when calling the DeleteForecast operation: User: arn:aws:sts::<accountid>:assumed-role/TenantA_Application_Role/tenant-a-role is not authorized to perform: forecast:DeleteForecast on resource: arn:aws:forecast:<region>:<accountid>:forecast/tenantb_deeparp_algo_forecast

Predictor のリストと監視

次のサンプルコードでは、Python (Boto3) API を使用して、リソースグループを使用して TenantA の Forecast の Predictors をクエリしています。

# Run under TenantA role TenantA_Application_Role
session = boto3.Session() 
resourcegroup = session.client(service_name='resource-groups')

query="{\"ResourceTypeFilters\":[\"AWS::Forecast::Predictor\"],\"TagFilters\":[{\"Key\":\"tenant\", \"Values\":[\"tenant_a\"]}]}" # <-- Tenant Tag needs to be specified.

response = resourcegroup.search_resources(
    ResourceQuery={
        'Type': 'TAG_FILTERS_1_0',
        'Query': query
    },
    MaxResults=20
)

predictor_count=0
for resource in response['ResourceIdentifiers']:
    print(resource['ResourceArn'])
    predictor_count=predictor_count+1

AWS Well-Architected Framework で説明されているように、サービスクォータ(サービスリミットとも呼ばれる)を監視することも重要です。Forecast にはアカウントごとの制限があります。詳細はガイドラインとクォータを参照してください。

次のコードは、CloudWatch のメトリックに予測子の総数を入力する例です。

cloudwatch = session.client(service_name='cloudwatch')
cwresponse = cloudwatch.put_metric_data(Namespace='TenantA_PredictorCount',MetricData=[
    {
        'MetricName': 'TotalPredictors',
        'Value': predictor_count
    }]
)

その他考慮すべき点

リソース制限とスロットリングは、テナント間でアプリケーションによって管理される必要があります。Forecast の制限に対応できない場合は、マルチアカウント構成を検討する必要があります。

Forecast List API またはリソースグループのレスポンスは、tenant タグの値に基づいてアプリケーションでフィルタリングする必要があります。

まとめ

この投稿では、マルチテナントの SaaS アプリケーションで ABAC テクニックを使用して Forecast アクセスを分離する方法を示しました。tenant タグを使用して、テナントごとに Forecast へのアクセスを制限する方法を示しました。さらにタグを適用してポリシーをカスタマイズしたり、この戦略を他の AWS サービスに適用したりすることができます。

認可戦略としての ABAC の使用に関する詳細については、AWS の ABAC とはを参照してください。

翻訳はソリューションアーキテクト 前川 泰毅 が担当しました。原文はこちらです。