Amazon Web Services ブログ

SAP Private Link と AWS サービスで SAP BTP アプリを機能強化

イノベーションとアジリティは、デジタル環境で優位に立ち続けるための重要な鍵であり、そのため企業はますますクラウドサービスの力を活用しようとしています。しかし、多くの組織が直面する大きな障害は、インターネット上で機密データを送受信することによるリスクです。この課題を認識し、本ブログでは、SAP Private Link が SAP Business Technology Platform (BTP) 内で AWS の標準サービスを安全かつプライベートに利用できる経路を提供する方法を探っています。このアプローチによりセキュアで制御されたネットワーク環境でイノベーションを促進することができます。

概要
AWS と SAP BTP 間のプライベート接続には、特にセキュリティに関して、いくつかの利点があります。SAP Private Link との統合により、これらの既存の AWS の機能が拡張され、セキュリティが強化されます。これには、データプライバシーが向上した AWS の AI サービスの利用、アプリケーションに対して保護されたアクセスを持つ安定したストレージの提供、制御されたネットワーク環境内での信頼できるメッセージング/通知の処理、保護された Queue を介した第三者ソリューションとの統合、外部データベースへのインターネットへの公開を行うことなくアクセスできることが含まれます。実際のコード例から、詳細を示していきます。このサービスは2023 年 9 月 7 日に一般提供開始され、詳細と一般情報については、「SAP Private Link Service を使って SAP BTP サービスを AWS サービスに接続する方法」をご覧ください。

図 1 に示されたアーキテクチャは、SAP BTP で提供される機能を Amazon Simple Storage Service (S3) などの AWS サービスを活用して拡張する方法の概略を示したものです。現時点では多数の追加サービスが組み込まれており、今後さらに多くの統合が予想されます。一般的なユースケースとしては、ドキュメント管理、テキストと音声認識、IoT と AI/ML テクノロジーを使った予知保全、イベント管理などがあります。サンプルコードでは、Amazon S3 バケットを読み取り、それらを UI5 アプリに一覧表示できる基本的な SAPUI5 アプリケーションのワーキングプロトタイプを提供しています。
この例の焦点は、SAP BTP と AWS サービス間の統合の基礎を示すことにあります。

アーキテクチャ概要:

1:SAP Private Link による一般的な SAP BTP と AWS の統合

上記のアーキテクチャの主要コンポーネントは以下の通りです:

  • 企業の ID プロバイダー (IdP) – 省略可能な要素ですが、ほとんどの企業は社内ユーザーを SAP BTP に認証するために企業の ID プロバイダーを使用しています。
  • SAP Identity Authentication Service (IAS): SAP のクラウド ID プロバイダー。この場合は、企業の ID プロバイダーを SAP BTP に連携させるために利用します。
  • UI5 アプリ: ユーザーがビジネス機能を実行するために利用するアプリケーション。フロントエンドコンポーネントであり、バックエンド (CAP アプリ) とやり取りしてビジネスロジックを処理します。
  • CAP アプリ: CAP とは Cloud Application Programming の略で、Node.js ベースの SAP BTP 推奨のプログラミング フレームワークです。これは、BTP と AWS コンポーネント間の操作を調整する中核のコンポーネントです。
  • SAP BTP Credential Store: 資格情報を安全に管理し、SAP BTP サービス (この場合は CAP アプリケーションなど) と統合する BTP サービス。今回のシナリオでは、AWS サービスとのやり取りに必要な一時的トークン (STS) を提供するために IAM が使用するキーとシークレットを格納します。
  • Config: 上記の概要図でのジェネリックなコンポーネント。このコンポーネントは、アプリケーションが AWS とやり取りするために必要な構成情報が格納されています。可能性としては、DB テーブル、User-provided instance、MTA Extension ファイル、環境変数などが考えられます。今回の例では、AWS リージョンと S3 エンドポイントを格納する User-provided instance を使用しています。
  • SAP Private Link: AWS PrivateLink と通信し、接続に必要なサブコンポーネント (Elastic Network Interface、VPC エンドポイント) を作成するサービス。これは BTP と AWS 間のプライベート接続用のトンネルを定義するサービス インスタンスです。最終的に、BTP から AWS にアクセスするために、アプリケーションが使用する URL によって表されます。

AWS サービス:

これらは、SAP BTP が利用するサービスです。サポートされるサービスのリストについては、SAP のドキュメントを参照してください。
私たちの事例では、アプリケーションが S3 バケットにアクセスします。

  • Identity and Access Management (IAM)AWS Security Token Service (STS) は、AWS のセキュリティアーキテクチャにとって不可欠な要素ですが、アクセスと認証の異なる側面を担っています。
    • IAM は、AWS サービスとリソースに対する細かな権限を中央で管理するように設計されています。IAM を使用すると、管理者は誰または何 (ユーザー、アプリケーションなど) が特定の AWS リソースにアクセスできるかを定義でき、そのアクセスの範囲も指定できます。IAM では、IAM ユーザーアクセスキーのような、一貫したアクセスニーズに対応する永続的な認証情報の仕組みを提供しています。
    • 一方、STS はリソースへのアクセスを直接管理するものではありません。代わりに、一時的で権限の限られた認証情報を発行します。これらの認証情報は、あらかじめ定義された IAM ロールまたはユーザーから権限を引き継ぎます。STS は特に、短期間のアクセスが必要な場合や、AWS の認証情報を永続的に手渡すことなく権限を付与する必要がある場合に有利です。主な例として、SAP BTP アプリなどのサードパーティアプリケーションに一時的に特定の AWS リソースへのアクセス権を付与することがあげられます。

要するに、IAM はアクセス権限の定義に注力する一方で、STS は権限に基づいて一時的な認証情報を発行することに集中しています。

上の図からは、次のようなアプリケーションの流れが見て取れます:

  1. ユーザーが BTP アプリの URL にアクセスします。
  2. BTP は IAS 経由で企業の ID プロバイダーに認証をアウトソースし、ユーザーはクレデンシャルと多要素認証の入力にリダイレクトされます。トークンが BTP に返されます。
  3. ユーザーが UI5 アプリにアクセスします。
  4. UI5 アプリは AWS サービス (Amazon S3、Amazon SNS、Amazon SQS、AWS Lambda など) へのアクセス許可が必要です。
  5. CAP アプリは BTP の資格情報ストアからキーとシークレットを取得します。
  6. アプリはサービス構成情報を、MTA 拡張ファイル (デプロイ時)、環境変数、ユーザー提供のサービス、またはアプリケーション DB にて取得できます。
  7. CAP アプリは、IAM キーとシークレットを使用して、AWS STS の AssumeRole API を呼び出し、一時的な資格情報を要求します。
  8. CAP アプリはキー、シークレット、セッショントークンを使用して、AWS サービスにアクセスします。

ビジネスシナリオの例 – ドキュメント管理

SAP ビジネステクノロジープラットフォーム上で動作するカスタムアプリケーションが、ドキュメントやファイルの保存や取得のために Amazon S3 を利用するのが一般的なビジネスシナリオです。
これにより、Amazon S3 バケットに耐障害性と低コストのドキュメント管理を実現し、複数のアプリケーション (SAP ビジネステクノロジープラットフォームと AWS の両方) からアクセスできるようになります。

基本的なシナリオ: このケースでは、ユーザーが BTP アプリケーションと対話し、ファイル (金融取引の書類添付、検査のための図面、資産メンテナンスの写真など) をアップロードする必要があります。BTP アプリケーションはファイルを処理し、Amazon S3 にアップロードします。そして、ファイルの場所をアプリケーションデータベースに記録し、ファイルの取得に使用されます。

高度なシナリオ: このシナリオは、AWS サービスとやり取りするための AWS Lambda 関数を利用することで更に拡張できます。
BTP アプリは、保存が必要なドキュメントとファイルのメタデータ (つまり文書番号、場所、作成日付や担当者名など) を Lambda に送信します。
次に Lambda 関数は、
・ファイルを Amazon S3 に保存し、
・メタデータと S3 ファイルへの参照を Amazon DynamoDB に保存します。
添付ファイルにアクセスするには、BTP が必要なファイル (ID など) への参照を送信し、Lambda が必要なファイルと関連するメタデータを返します。
このアプローチにより、この Lambda 関数を他のアプリケーション (サードパーティアプリと AWS ネイティブアプリを含む) 間で再利用できるようになります。

前提条件 : 必要なサービス/コンポーネント

SAP BTP:

  1. BTP サブアカウント (評価版または正式版): BTP サブアカウントの評価版または正式版を設定します。手順は SAP のドキュメントに従ってください。
  2. プライベートリンク権利 (プライベートリンクのセットアップ): プライベートリンク権利を取得し、SAP ドキュメントの「プライベートリンクのセットアップ」のセクションに従って設定します。
  3. Cloud Foundry ランタイム権利: SAP のドキュメントに記載された手順に従って、Cloud Foundry ランタイムを有効化します。
  4. Cloud Foundry スペース: SAP のドキュメントの手順に従って、リソースとアプリケーション用の専用の Cloud Foundry スペースを確立します。
  5. 認証情報ストア権利: SAP のドキュメントに従い、認証情報を安全に管理するための認証情報ストアの権利を取得します。
  6. Business Application Studio: Business Application Studio をセットアップします。あるいは、好みに応じて Visual Studio や他の互換性のある IDE を使用することができます。

AWS:

  1. AWS アカウント: 必要なサービスを利用するための有効な AWS アカウントが必要です。
  2. S3 バケット: オブジェクトを格納する S3 バケットを指定します。
  3. IAM ロール: AWS 内のアクションとリソースに対するアクセス権限を定義する Identity and Access Management (IAM) ロールを作成します。
  4. シークレットキーを持つ IAM ユーザー: 認証に必要なシークレットキーを持つ IAM ユーザーを設定します。

サービス / コンポーネントのセットアップ:

  1. AWS – S3 バケットの作成 :
    1. AWS ドキュメントに従って、S3 バケットを作成します。
    2. Amazon S3 の「アクセスポイント」セクションから、アクセスポイントを設定します。
  2. AWS – IAM ユーザーの作成:
    • AWS ドキュメントに従って、IAM ユーザーを作成します。
    • AWS ドキュメントに従って、必要なアクセスキーを取得します。

AWS – IAM ロールを作成する

  1. カスタム信頼ポリシーによる IAM ロールの作成」の手順に従って IAM ロールを作成し、カスタム信頼ポリシーを付与してください。
  2. アカウント “123456789012” とユーザー “youruser” の例における信頼ポリシーのサンプル
    {
      "Version": "2012-10-17",
      "Statement": [ 
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "s3.amazonaws.com",
            "AWS": "arn:aws:iam::123456789012:user/youruser"
          },
          "Action": "sts:AssumeRole"
        }
      ] 
    }
    
  3. S3 バケットの読み取りや、必要な他のアクションを許可する許可ポリシーを付与します。
    1. 自分のロール → 許可 → 許可の追加 をクリックします
    2. 下に許可ポリシーの例を示します。必要に応じてカスタマイズしてください。
{
    "Version": "2012-10-17",
    "Statement": [ 
        {
            "Effect": "Allow",
            "Action": [ 
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [ 
                "arn:aws:s3:::specific-bucket-name",
                "arn:aws:s3:::specific-bucket-name/*"
            ] }
    ] 
}

BTP – Private Link サービスインスタンス (ソース SAP-samples)

始めるには、以下のコマンドを実行して SAP Private Link サービスインスタンスを作成します:

# Adjust the region in the service name if using a different region 

 cf create-service privatelink my-privatelink -c '{"serviceName": "com.amazonaws.eu-east-1.s3"}'

注意: このコマンドは Cloud Foundry CLI で実行する必要があります。まだインストールしていない場合は、Cloud Foundry CLI をダウンロードしてください。

代替的な方法として、BTP Cockpit を使用して Private Link サービスインスタンスを作成する場合は、サブアカウントをクリックしてから「Instances and subscriptions」をクリックします。

「サービス名」を入力し、「作成」をクリックしてください。

BTP – ユーザープロビジョニングサービスを作成する

リージョンおよびバケットが保存される場所の追加設定を定義するためのユーザー提供サービスを作成します:

#  adapt the properties according to your setup 

 cf cups my-service-config -p '{"region": "eu-east-1"}'

上の例では、リージョンが定義されています。この値は後に CAP アプリケーションで取得されます。

これらのパラメータを定義する 1 つの方法は、ユーザーが提供するサービスを使うことです。ハイレベルなアーキテクチャの手順 6 で説明したように、これらの値は、さまざまな場所に保存できます。

BTP – 認証情報ストアのセットアップ

  1. 初期設定 (SAP ヘルプ): SAP ドキュメンテーションに詳しく記載されている必要なパラメータを設定し、初期設定から始めます。
  2. 認証情報ストアの作成 (SAP ヘルプ): SAP ドキュメンテーションに沿って、認証情報を管理および安全に保管する認証情報ストアを設定します。
  3. ネームスペースの作成 (SAP ヘルプ): SAP ドキュメンテーションの手順に従い、リソースを隔離および分類するためのネームスペースを定義します。
  4. AWS シークレットキーを使ったパスワードの作成 (SAP ヘルプ): SAP ドキュメンテーションの方法に従い、AWS シークレットキーを利用してパスワードを生成します。

手順に沿ったコードサンプル

SAP BTP サブアカウント内の ビジネスアプリケーションスタジオを起動し、標準の Node.js CAP アプリケーションを開始してください。注: サンプルアプリケーションを作成する際は、プロダクティビティツールを利用することもできます。詳細については、SAP サポートを参照してください。

下に示されている index.html ページでは、基本的なウェブアプリケーションのインターフェースを提示しています。
「Load Buckets」ボタンがクリックされると、loadBuckets() 関数がトリガーされます。
この関数が起動すると、/catalog/Buckets API エンドポイントに GET リクエストを送信し、バケットに関連するデータを取得します。
データが取得されると、ページ内の指定されたコンテナにバケットごとにフォルダを動的に生成して表示します。
各フォルダはバケットを視覚的に表し、その ID と作成日を表示します。

<!DOCTYPE HTML>
<html>

<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <title>App</title>
</head>

<body>
    <h1>Business Application</h1>
    <h1>Buckets</h1>
    <button class="button" onclick="location.href='/catalog/'">Catalog</button>
    <br />
    <button class="button" onclick="location.href='/catalog/$metadata'">Metadata</button>
    <br />
    <button class="button" onclick="loadBuckets()">Load Buckets</button>
    <div id="folder-container"></div>
    <script> function loadBuckets() { // Make a GET request to the API  fetch('/catalog/Buckets') .then(response => response.json()) .then(data => { // Create a folder for each bucket in the data const folderContainer = document.getElementById('folder-container'); data.value.forEach(bucket => { const folder = document.createElement('div'); folder.classList.add('folder'); folder.innerHTML = ` <i class="fa fa-folder"></i> <p>${bucket.id}</p> <small>${bucket.created}</small> `; folderContainer.appendChild(folder); }); }) .catch(error => console.error(error)); } </script>

</body>

</html>
HTML

下記の srv/catalog-service.cds ファイルは、CAP アプリケーションのバックエンドコンポーネントとして機能します。
その中に「Buckets」という名前のエンティティがあり、idnamecreated というフィールドで構成されるデータ構造を定義しています。
このエンティティのデータは、同梱の catalog-service.js スクリプトから取得されます。

さらに、{ CatalogService as my } のようなインポートエイリアスが catalog-service からの関数やエンティティを参照するために使用されています。CatalogService は /catalog パスにマッピングされており、getBuckets() 関数が “Buckets” エンティティの配列を返すようになっています。
このセットアップは、本質的に CAP アプリケーションのデータモデルとサービスエンドポイントを確立し、バケット関連データの取得を可能にします。

using { CatalogService as my } from './catalog-service';

 service CatalogService @(path : '/catalog') {

   entity Buckets {
     key id: String ;
     name: String ;
     created: DateTime ;
  }

   function getBuckets() returns array of my.Buckets ;

}

srv/catalog-service.js ファイルは、AWS バケット情報を取得および管理するためのバックエンドロジックを形成しています。これは srv/catalog-service.cds データモデルに付随しています。
この JavaScript コードは、主に S3 バケットサービスなど、様々な AWS サービスと対話するために AWS SDK のインポートに依存しています。

主な関数である getBuckets() は、いくつかのタスクを行います:

  1. 構成収集: まず getConfig() を介して必要な構成を取得します。この構成には、プライベートリンクサービスのホスト名、ユーザーが指定したリージョンの詳細、クレデンシャルストアからの長期アクセスキーが含まれます。
  2. STS 資格情報: これらの構成を利用して、getSTSCredentials() 関数を使って AWS のセキュリティトークンサービス (STS) から一時的なセキュリティ認証情報を取得します。
  3. S3 クライアントの初期化: 取得した一時的な認証情報を使って、getS3Client() 関数で S3 クライアントを初期化します。このクライアントはプライベートリンクホストエンドポイントを使用するため、AWS のプライベートネットワークを経由して接続します。
  4. バケット一覧: 最後に、ListBucketsCommand を呼び出して S3 バケットの一覧を取得します。成功した場合はバケットをコンソールに記録して返し、失敗した場合は発生したエラーをキャプチャしてログに記録します。

このコードは、複数の機能を統合しています。AWS の STS によるセキュアでの短期認証情報の発行、S3 サービスによるバケット操作、そしてユーザーが提供したデータと内部サービスの両方から取得した設定に依存しています。

import  { fromIni } from "@ aws-sdk/credential-provider-ini";
 import { ListBucketsCommand, S3Client } from "@ aws-sdk/client-s3";
 import cfenv from 'cfenv';
 import { readCredential, readCredentialValue } from './lib/credStore.js';
 import AWS from 'aws-sdk';
 import Config from './config.js';

 export default async function() {

   this.on('READ', 'Buckets', async (req) => {
     const buckets = await getBuckets();
     return buckets ;
  });

 const getBuckets = async () => {
   const config = await getConfig();

  //Getting temporary STS credentials 
   const STScredentials = await getSTSCredentials(config, 'arn:aws:iam::6205:role/S3Read', 'MySession');

  // Calling getS3Client 
   const s3Client = getS3Client(config, STScredentials);
  
   const command = new ListBucketsCommand({});
   try {
   const { Owner, Buckets } = await s3Client.send(command);
   console.log(
   ` ${ Owner.DisplayName } owns ${ Buckets.length } bucket ${
     Buckets.length === 1 ? "" : "s"
  }:` 
  );
   console.log(` ${ Buckets.map((b) => ` • ${ b.Name } `).join("\n")} `);
   return Buckets ;
  } catch (err) {
   console.error(err);
   return [] ;
  }
  };  


 async function getConfig() {
     const config = new Config();
    
    // Private-link 
     const myPrivatelinkCreds = await getCfEnv().getServiceCreds('my-privatelink');
     const host = myPrivatelinkCreds.hostname.replace(/^\ * \./, '');
     config.setEndpointHostname(host);

    // Getting region defined in user-provided, Can be used for more params as needed  
     const s3ServiceCredentials = await getCfEnv().getServiceCreds('my-service-config');
     config.setRegion(s3ServiceCredentials.region);
    
    //Credential Store 
     const credaccessKeyId = await credStore.readCredential('app', 'password', 'accessKeyId');
     const credsecretAccessKey = await credStore.readCredential('app', 'password', 'secretAccessKey');
     config.setAccessKeyId(credaccessKeyId.value);
     config.setSecretAccessKey(credsecretAccessKey.value);

     return config ;
  }

   function getSTSCredentials(config, roleArn, roleSessionName) {
    // Set the AWS region and credentials using long-term access and secret keys 
     AWS.config.update({
       region: config.getRegion(),
       accessKeyId: config.getAccessKeyId(),
       secretAccessKey: config.getSecretAccessKey()
    });
  
     const sts = new AWS.STS();
  
     const assumeRoleParams = {
       RoleArn: roleArn,
       RoleSessionName: roleSessionName 
    };
  
     return new Promise((resolve, reject) => {
       sts.assumeRole(assumeRoleParams, (err, data) => {
         if (err) {
           reject(err);
        } else {
           const credentials = data.Credentials ;
           resolve({
             accessKeyId: credentials.AccessKeyId,
             secretAccessKey: credentials.SecretAccessKey,
             sessionToken: credentials.SessionToken 
          });
        }
      });
    });
  }
  
  
   function getS3Client(config, credentials) {
    // Set the AWS region and credentials using the temporary credentials 
     AWS.config.update({
       region: config.getRegion(),
       credentials: {
         accessKeyId: credentials.accessKeyId,
         secretAccessKey: credentials.secretAccessKey,
         sessionToken: credentials.sessionToken 
      }
    });
    
    // Set the S3 endpoint 
     const endpoint = `https://bucket.${ config.getEndpointHostname()} ` ;
     const s3Endpoint = new AWS.Endpoint(endpoint);
    // Create a new instance of the S3 service client 
     const s3Client = new AWS.S3({ endpoint: s3Endpoint });
     return s3Client ;
  }

  
   function getCfEnv() {
     return cfenv.getAppEnv();
  }

}

概要

SAP Private Link for AWS は、SAP BTP と AWS サービス間のプライベート接続を提供します。
このブログに含まれるステップとコードスニペットは、SAP BTP の提供するビジネスプラットフォームと AWS の提供するイノベーションサービスの両方を活用し、SAP Private Link と一時的な認証情報を利用したセキュアな認証を用いて、この環境を実装するための基盤となります。
これで、このブログのコード例を使用して、BTP アプリケーションと AWS を安全に接続することができます。

SAP PrivateLink for AWS の詳細については、SAP BTP で Amazon Web Services を利用する方法のガイドをご覧ください。

翻訳は Partner SA 松本が担当しました。原文はこちらです。