ブラウザ環境から Intelligent Media Management (IMM) にアクセスすることは、サーバー環境からアクセスするよりも安全性が低くなります。特に、アクセスが長期または固定のアクセス認証情報に基づいている場合はなおさらです。AccessKey ペアの漏洩のリスクを軽減するために、Alibaba Cloud の Security Token Service (STS) を使用して、最小権限の原則に基づいて有効期間と必要な権限を持つ STS トークンを発行できます。これにより、安全性の低いブラウザ環境が原因で長期の静的 AccessKey ペアが公開されるのを防ぎます。
背景情報
STS の主な利点の 1 つは、所有している Alibaba Cloud アカウントの AccessKey ペアを公開することなく、他のユーザーにリソースへのアクセスを許可できることです。Alibaba Cloud アカウントレベルでの AccessKey ペアの公開は、深刻なセキュリティ上の問題を引き起こす可能性があります。たとえば、侵害された Alibaba Cloud アカウント内のデータは、攻撃者によって操作または盗難される可能性があります。企業の技術チームが、Web アプリケーションユーザーがブラウザから企業が管理する Object Storage Service (OSS) バケットにファイルを直接アップロードし、IMM を使用してバケット内のデータを処理して、同時実行性、パフォーマンス、安定性を向上させたいと考えているとします。
セキュリティリスク
ブラウザから IMM と直接対話することで、すべてのビジネスリクエストの中間サーバーとしてアプリケーションサーバーを使用する必要がなくなり、サーバー側の負荷が軽減され、パフォーマンスが向上します。
企業は、次のソリューションを使用して Web アプリケーションから直接アップロードを実装するデータ処理サービスを構築することを計画しています。
ブラウザ環境はユーザー側にあり、企業は制御できません。その結果、次の課題が発生します。
認証情報の漏洩: ブラウザは信頼できない環境と見なされます。ブラウザに AccessKey ペアを保存すると、認証情報のセキュリティに高いリスクが生じます。
粗いアクセス制御: ほとんどの場合、アプリケーションの Resource Access Management (RAM) ユーザーには、他のクラウドサービスを管理およびアクセスするための過剰なアクセス許可が付与されています。このような RAM ユーザーの AccessKey ペアが漏洩すると、より深刻な結果が生じます。
ソリューション
リスクに対処するために、技術チームは当初提案されたソリューションに一時的なアクセス認証を追加します。このようにして、企業はアップロード効率を維持し、次の利点を得ることができます。
認証と認可の強化: STS は、特定の期間内に有効なトークンを生成します。トークンが漏洩した場合でも、トークンはすぐに無効になるため、セキュリティリスクが大幅に軽減されます。
きめ細かいアクセス制御: STS を使用すると、技術チームは最小権限の原則に従って権限を構成できます。技術チームは、Web アプリケーションに必要な権限のみを付与できます。きめ細かいアクセス制御により、認証情報の漏洩による危険が軽減されます。
最終的に、技術チームは次のソリューションを使用してデータ処理サービスを構築します。
デプロイ
このセクションでは、OSS、IMM、および STS を使用して、Web アプリケーション用のブラウザベースのデータ処理サービスをデプロイする方法を示します。
準備
OSS バケットと IMM プロジェクトを作成します。
パラメータ
例
リージョン
中国 (杭州)
バケット名
web-direct-upload
プロジェクト名
web-direct-project
STS トークンを取得するためのアプリケーションサーバーとして使用される Elastic Compute Service (ECS) インスタンスを作成します。
説明STS トークンを取得するために使用される STS API 操作を独自のアプリケーションサーバーに統合する場合は、この手順をスキップできます。
パラメータ
例
課金方法
従量課金制
リージョン
中国 (杭州)
パブリック IP アドレス
パブリック IPv4 アドレスを割り当てる
セキュリティグループ
HTTP-TCP:80-open
詳細については、コンソールで ECS インスタンスを作成および管理する (エクスプレスバージョン)を参照してください。
OSS バケットのクロスオリジンリソース共有 (CORS) を構成します。
パラメータ
例
送信元
http://ECS-public-IP-address
許可されるメソッド
PUT
許可されるヘッダー
*
詳細については、CORSを参照してください。
手順
手順 1: RAM コンソールで RAM ユーザーを作成する
RAM ユーザーを作成し、RAM ユーザーの AccessKey ペアを取得します。RAM ユーザーを作成するときは、アクセスモードパラメータを Openapi アクセス に設定します。AccessKey ペアは、アプリケーションサーバーにアクセスして管理するための長期アクセス認証情報として使用されます。
Alibaba Cloud アカウントまたはアカウント管理者として、RAM コンソール にログオンします。
左側のナビゲーションペインで、ID > ユーザー を選択します。
ユーザーページで、ユーザーの作成 をクリックします。
ログオン名 パラメータと 表示名 パラメータを構成します。
アクセスモード セクションで、Openapi アクセス を選択し、OK をクリックします。
アクションセクションの コピー をクリックして AccessKey ペア (
AccessKey ID
とAccessKey シークレット
を含む) をコピーし、安全に保存されたファイルに貼り付けます。
手順 2: RAM コンソールで RAM ユーザーに AssumeRole 操作を呼び出す権限を付与する
RAM ユーザーを作成した後、RAM ユーザーに STS の AssumeRole 操作を呼び出す権限を付与します。このようにして、RAM ユーザーは RAM ロールを引き受けて STS トークンを取得できます。
左側のナビゲーションペインで、ID > ユーザー を選択します。
ユーザー ページで、RAM ユーザーを見つけて、アクション列の 権限の追加 をクリックします。
権限の付与 パネルで、AliyunSTSAssumeRoleAccess システムポリシーを選択します。
説明AliyunSTSAssumeRoleAccess ポリシーにより、RAM ユーザーは AssumeRole 操作を呼び出すことができます。ポリシーの権限は、RAM ユーザーが STS トークンを取得して IMM にリクエストを開始するために必要な権限とは無関係です。
権限の付与 をクリックします。
手順 3: RAM コンソールで RAM ロールを作成する
Alibaba Cloud アカウントの RAM ロールを作成し、RAM ロールの Alibaba Cloud Resource Name (ARN) を取得します。RAM ロールは後で RAM ユーザーによって引き受けられます。
左側のナビゲーションペインで、ID > ロール を選択します。
ロールページで、ロールの作成 をクリックします。ロールの作成ウィザードのロールタイプの選択手順で、信頼できるエンティティの選択パラメータを Alibaba Cloud アカウント に設定し、次へ をクリックします。
ロールの作成ウィザードのロールの構成手順で、RAM ロール名 フィールドに名前を入力し、信頼できる Alibaba Cloud アカウントの選択パラメータを 現在の Alibaba Cloud アカウント に設定します。
OK をクリックします。RAM ロールの作成後、閉じる をクリックします。
ロールページで、検索ボックスに RAM ロール名 (例:
imm-web-upload
) を入力します。次に、ロールの名前をクリックします。基本情報セクションで、ARN フィールドの横にある コピー をクリックして、RAM ロールの ARN を記録します。
手順 4: RAM コンソールでカスタムポリシーを作成する
OSS のカスタムポリシーを作成する
RAM ロールが指定されたバケットにデータをアップロードできるようにするカスタムポリシーを作成します。
左側のナビゲーションペインで、権限 > ポリシー を選択します。
ポリシーページで、ポリシーの作成 をクリックします。
ポリシーの作成 ページで、JSON をクリックします。次のサンプルスクリプトをコピーしてエディターに貼り付けます。
<BucketName>
を、以前に作成したバケットの名前に置き換えます。この例では、バケット名はweb-direct-upload
です。
次のサンプルスクリプトは参照用にのみ提供されています。ユーザーに過剰な権限が付与されないように、要件に基づいてきめ細かい RAM ポリシーを構成する必要があります。きめ細かいポリシーの構成方法の詳細については、OSS のカスタムポリシーを参照してください。
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": "oss:PutObject",
"Resource": "acs:oss:*:*:<BucketName>/uploads/*"
}
]
}
ポリシー情報を編集するには次へ をクリックします。
基本情報 セクションで、ポリシー名を入力し、OK をクリックします。
IMM のカスタムポリシーを作成する
OSS のカスタムポリシーを作成する で説明されている手順を繰り返して、RAM ロールが指定されたプロジェクトでのみ操作を実行できるようにするカスタムポリシーを作成します。
次のスクリプトはサンプルポリシーを提供します。
次のサンプルスクリプトは参照用にのみ提供されています。ユーザーに過剰な権限が付与されないように、要件に基づいてきめ細かい RAM ポリシーを構成する必要があります。きめ細かいポリシーの構成方法の詳細については、RAM ユーザーに権限を付与するを参照してください。
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": "imm:*",
"Resource": "acs:imm:cn-hangzhou:1413397765616316:project/<ProjectName>"
}
]
}
手順 5: RAM コンソールでカスタムポリシーを RAM ロールにアタッチする
カスタムポリシーを RAM ロールにアタッチします。このようにして、RAM ロールが引き受けられたときに、RAM ロールは必要な権限を提供できます。
左側のナビゲーションペインで、ID > ロール を選択します。
ロール ページで、RAM ロールを見つけて、アクション列の 権限の付与 をクリックします。
権限の付与 パネルのポリシーセクションで、ポリシータイプドロップダウンリストから カスタムポリシー を選択し、上記のカスタムポリシーを選択します。
権限の付与 をクリックします。
手順 6: アプリケーションサーバーから STS トークンを取得する
Web アプリケーションを開き、STS SDK をアプリケーションサーバーに統合して、/get_sts_token 操作を実装します。詳細については、STS SDK の概要を参照してください。HTTP GET メソッドを使用して /get_sts_token
操作を呼び出すと、システムは STS トークンを生成して返します。
次の手順では、ECS インスタンスで Flask フレームワークを使用して Web アプリケーションを構築し、アプリケーションサーバーから STS トークンを取得する方法について説明します。
ECS インスタンスに接続します。
詳細については、コンソールで ECS インスタンスを作成および管理する (エクスプレスバージョン)を参照してください。
Python 3 をインストールします。
プロジェクトフォルダーを作成し、プロジェクトディレクトリに切り替えます。
mkdir my_web_sample cd my_web_sample
依存関係をインストールします。
pip3 install Flask pip3 install attr pip3 install yarl pip3 install async_timeout pip3 install idna_ssl pip3 install attrs pip3 install aiosignal pip3 install charset_normalizer pip3 install alibabacloud_tea_openapi pip3 install alibabacloud_sts20150401 pip3 install alibabacloud_credentials
バックエンドコードを記述します。
main.py
という名前のファイルを作成します。次の Python コードをファイルに追加します。
import json from flask import Flask, render_template from alibabacloud_tea_openapi.models import Config from alibabacloud_sts20150401.client import Client as Sts20150401Client from alibabacloud_sts20150401 import models as sts_20150401_models from alibabacloud_credentials.client import Client as CredentialClient app = Flask(__name__) # <YOUR_ROLE_ARN> を RAM ロールの ARN に置き換えます。 role_arn_for_oss_upload = '<YOUR_ROLE_ARN>' # STS のリージョンを指定します。例: cn-hangzhou。 region_id = 'cn-hangzhou' @app.route("/imm") def imm(): return render_template('imm_example.html') @app.route('/get_sts_token', methods=['GET']) def get_sts_token(): # CredentialClient を初期化するときにパラメータを指定しない場合、デフォルトの認証情報チェーンが使用されます。 # コンピューターでファイルを実行する場合は、ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数と ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を構成することで、AccessKey ペアを指定できます。 # ECS、Elastic Container Instance (ECI)、または Container Service for Kubernetes (ACK) でファイルを実行する場合は、ALIBABA_CLOUD_ECS_METADATA 環境変数を構成することで、バインドされたインスタンスのロールを指定できます。SDK は自動的に STS トークンを取得します。 config = Config(region_id=region_id, credential=CredentialClient()) sts_client = Sts20150401Client(config=config) assume_role_request = sts_20150401_models.AssumeRoleRequest( role_arn=role_arn_for_oss_upload, # <YOUR_ROLE_SESSION_NAME> をセッションのカスタム名に置き換えます。 role_session_name='<YOUR_ROLE_SESSION_NAME>' ) response = sts_client.assume_role(assume_role_request) token = json.dumps(response.body.credentials.to_map()) return token app.run(host="0.0.0.0", port=80)
サンプルコードの
<YOUR_ROLE_ARN>
を、手順 3 で作成した RAM ロールの ARN に置き換えます。<YOUR_ROLE_SESSION_NAME>
をセッションのカスタム名に置き換えます。例:role_session_test
。
手順 1 で取得した AccessKey ペアを使用してアプリケーションを起動します。
ALIBABA_CLOUD_ACCESS_KEY_ID=<YOUR_AK_ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<YOUR_AK_SECRET> python3 main.py
ブラウザで
http://<ECS インスタンスのパブリック IP アドレス>/get_sts_token
にアクセスします。次の図は成功応答を示しています。
手順 7: ブラウザで STS トークンを使用して IMM にリクエストを開始する
アプリケーションサーバーで /get_sts_token 操作を構成した後、Alibaba Cloud CDN を使用して Web アプリケーションのフロントエンドページに OSS SDK for JavaScript をインポートして、データのアップロードをリッスンします。バケットにデータをアップロードするときは、/get_sts_token
操作を呼び出して STS トークンを取得し、STS トークンを使用してデータをアップロードします。ブラウザから IMM にリクエストを送信する場合は、ハッシュベースのメッセージ認証コード (HMAC) を使用してデータを暗号化する必要があります。この例では、データ暗号化機能は crypto.js ライブラリの HmacSHA1 関数を使用して実装されています。この例では、DetectImageFaces 操作を統合しています。
次の手順は、IMM 操作を Web アプリケーションのフロントエンドに統合する方法を示しています。
Ctrl+C
を押して Web アプリケーションを停止します。フロントエンドプロジェクトディレクトリを作成します。
mkdir templates
HTML ファイルを作成します。
templates
ディレクトリにimm_example.html
という名前のファイルを作成します。vim templates/imm_example.html
次のHTMLコードをHTMLファイルに追加します。次のサンプルコードは、ファイルのアップロードと画像の顔検出機能を実装しています。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>imm_example</title> <script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.17.0.min.js"></script> <script src="https://unpkg.com/crypto-js@4.1.1/crypto-js.js"></script> <style> .app form div { display: flex; margin: 12px; } .app label { display: flex; width: 100px; } .app form input { width: 200px; } </style> </head> <body> <div class="app"> <h2>1. OSS にファイルをアップロードします。</h2> <form id="oss-upload-form"> <div> <label for="file" class="form-label">ファイルを選択:</label> <input type="file" class="form-control" id="file" name="file" required /> </div> <button type="submit" class="btn btn-primary">アップロード</button> </form> <div id="oss-result"></div> </div> <div class="app"> <h2>2. IMM の DetectImageFaces 操作を呼び出します。</h2> <form id="imm-form"> <button type="submit">リクエスト</button> </form> </div> <div> <h2>3. 結果を表示します。</h2> <div id="result"></div> </div> <script> /** * STS トークンの有効期限が切れているかどうかを確認します。 **/ function isCredentialsExpired(credentials) { if (!credentials) { return true; } const expireDate = new Date(credentials.Expiration); const now = new Date(); // STS トークンの残りの有効期間が 1 分未満の場合、STS トークンは期限切れと見なされます。 return expireDate.getTime() - now.getTime() <= 60000; } let credentials = null; let uploadResult = null; // ############# OSS-UPLOAD ################# const ossForm = document.querySelector("#oss-upload-form"); ossForm.addEventListener("submit", async (event) => { event.preventDefault(); // 元のトークンの有効期限が切れている場合にのみ、STS トークンを再取得します。これにより、STS への呼び出し回数が削減されます。 if (isCredentialsExpired(credentials)) { const response = await fetch("/get_sts_token", { method: "GET", }); if (!response.ok) { // HTTP ステータスコードに基づいてエラーを処理します。 throw new Error( `STS トークンの取得に失敗しました: ${response.status} ${response.statusText}` ); } credentials = await response.json(); } const client = new OSS({ // <YOUR_BUCKET> を OSS バケットの名前に置き換えます。 bucket: "web-direct-upload", // <YOUR_REGION> を OSS バケットが配置されているリージョンの ID に置き換えます。例: oss-cn-hangzhou。 region: "oss-cn-hangzhou", accessKeyId: credentials.AccessKeyId, accessKeySecret: credentials.AccessKeySecret, stsToken: credentials.SecurityToken, }); const fileInput = document.querySelector("#file"); const file = fileInput.files[0]; const result = await client.put("/" + file.name, file); if (result) { uploadResult = result; document.querySelector("#oss-result").textContent = "アップロードが成功しました。"; } }); // ################################################## // ################# IMM ############################ function getTimestamp() { const pad2 = (number) => { return String(number).padStart(2, "0"); }; let date = new Date(); let YYYY = date.getUTCFullYear(); let MM = pad2(date.getUTCMonth() + 1); let DD = pad2(date.getUTCDate()); let HH = pad2(date.getUTCHours()); let mm = pad2(date.getUTCMinutes()); let ss = pad2(date.getUTCSeconds()); return `${YYYY}-${MM}-${DD}T${HH}:${mm}:${ss}Z`; } function generateSecureNonce(length = 32) { const array = new Uint8Array(length); window.crypto.getRandomValues(array); return Array.from(array, (byte) => byte.toString(36)) .join("") .substring(0, length); } function normalize(params) { const list = []; const flated = params; const keys = Object.keys(flated).sort(); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = flated[key]; list.push([encode(key), encode(value)]); } return list; } function canonicalize(normalized) { const fields = []; for (let i = 0; i < normalized.length; i++) { const [key, value] = normalized[i]; fields.push(key + "=" + value); } return fields.join("&"); } function encode(str) { const result = encodeURIComponent(str); return result .replace(/!/g, "%21") .replace(/'/g, "%27") .replace(/\(/g, "%28") .replace(/\)/g, "%29") .replace(/\*/g, "%2A"); } async function sha1(data, key = null) { const keyData = CryptoJS.enc.Utf8.parse(key); // HmacSHA1 メソッドを使用して HMAC を計算します。 const hmac = CryptoJS.HmacSHA1(data, keyData); // HMAC (CryptoJS オブジェクト) を Base64 エンコードされた文字列に変換します。 const base64Hmac = hmac.toString(CryptoJS.enc.Base64); return base64Hmac; } async function getRPCSignature(signedParams, method, secret) { const normalized = normalize(signedParams); const canonicalized = canonicalize(normalized); const stringToSign = `${method}&${encode("/")}&${encode( canonicalized )}`; const key = secret + "&"; return await sha1(stringToSign, key); } async function getStsToken() { const response = await fetch("/get_sts_token", { method: "GET", }); if (!response.ok) { // HTTP ステータスコードに基づいてエラーを処理します。 throw new Error( `STS トークンの取得に失敗しました: ${response.status} ${response.statusText}` ); } return await response.json(); } // let credentials = null; async function getParams({ Action, ProjectName, SourceURI }) { if (isCredentialsExpired(credentials)) { credentials = await getStsToken(); } const params = { AccessKeyId: credentials.AccessKeyId, Format: "JSON", Action, ProjectName, Timestamp: getTimestamp(), SecureTransport: true, SignatureMethod: "HMAC-SHA1", SignatureNonce: generateSecureNonce(), SignatureVersion: "1.0", SourceURI, Version: "2020-09-30", SecurityToken: credentials.SecurityToken, }; params.Signature = await getRPCSignature( params, "POST", credentials.AccessKeySecret ); return params; } async function detect(requestParams) { const params = await getParams(requestParams); const url = `${requestParams.EndPoint}?${new URLSearchParams( params ).toString()}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error( `操作に失敗しました: ${response.status} ${response.statusText}` ); } return await response.json(); } const immForm = document.querySelector("#imm-form"); immForm.addEventListener("submit", (e) => { e.preventDefault(); detect({ // IMM 操作を指定します。 Action: "DetectImageFaces", // <YOUR_PROJECT_NAME> を IMM プロジェクト名に置き換えます。 ProjectName: "web-direct-project", //<YOUR_OSS_FILE_PATH> をバケット内のオブジェクトへのパスに置き換えます。例: oss://imm-example-cn-hangzhou/test.jpg。 SourceURI: `oss://web-direct-upload/${uploadResult.name}`, // <YOUR_END_POINT> を IMM のエンドポイントに置き換えます。例: https://imm.cn-beijing.aliyuncs.com。 EndPoint: "https://imm.cn-hangzhou.aliyuncs.com", }) .then((res) => { // console.log(res); document.querySelector("#result").textContent = JSON.stringify(res); }) .catch((err) => { document.querySelector("#result").textContent = err; }); }); </script> </body> </html>
手順 1 で取得した AccessKey ペアを使用してアプリケーションを起動します。
ALIBABA_CLOUD_ACCESS_KEY_ID=<YOUR_AK_ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<YOUR_AK_SECRET> python3 main.py
ブラウザで
http://<ECS インスタンスのパブリック IP アドレス>/imm
にアクセスします。Web ページで、顔情報を検出する画像をアップロードします。
ソリューションの検証とリソースのクリーンアップ
ソリューションの検証
アップロード操作が完了したら、オブジェクトがバケットに存在するかどうかを確認します。
左側のナビゲーションペインで、バケット をクリックします。
オブジェクト ページで、オブジェクトが存在するかどうかを確認します。
データ処理リクエストが完了したら、顔検出結果が Web ページに表示されるかどうかを確認します。
{
"RequestId": "63661E75-3A41-5FBF-B023-867DA6A6AA81",
"Faces": [
{
"Beard": "none",
"MaskConfidence": 0.764,
"Gender": "female",
"Boundary": {
"Left": 182,
"Top": 175,
"Height": 381,
"Width": 304
},
"BeardConfidence": 0.987,
"FigureId": "047d8d12-c3b6-4e22-9b9a-b91facc650fb",
"Mouth": "open",
"Emotion": "happiness",
"Age": 45,
"MouthConfidence": 0.999,
"FigureType": "face",
"GenderConfidence": 1,
"HeadPose": {
"Pitch": -16.206,
"Roll": -5.124,
"Yaw": 3.421
},
"Mask": "none",
"EmotionConfidence": 0.984,
"HatConfidence": 1,
"GlassesConfidence": 0.976,
"Sharpness": 1,
"FigureClusterId": "figure-cluster-id-unavailable",
"FaceQuality": 0.942,
"Attractive": 0.044,
"AgeSD": 7,
"Glasses": "glasses",
"FigureConfidence": 1,
"Hat": "none"
}
]
}
リソースのクリーンアップ
このソリューションの実装では、ECS インスタンスと OSS バケットを作成しました。ソリューションをテストした後、不要なコストを避けるために、次のリソースクリーンアップ操作を検討してください。
ECS インスタンスをリリースします。詳細については、インスタンスのリリースを参照してください。
バケットを削除します。詳細については、バケットの削除を参照してください。
IMM プロジェクトを削除します。詳細については、DeleteProject を参照してください。
RAM ユーザーと RAM ロールを削除します。詳細については、RAM ユーザーの削除とRAM ロールの削除を参照してください。