ノート:

Oracle Cloud API Gatewayを使用したファンクション認証によるセキュアなオブジェクト・ストレージ・ダウンロード・サービスの作成

イントロダクション

Oracle Cloud Object Storageは、大規模または小規模なファイルを格納するための非常に安価で効果的な方法です。Object Storageでは、イベント主導のアーキテクチャで作業できるため、プロセスの実行はほぼリアルタイムになります。オブジェクト・ストレージでイベントをトリガーするようにイベントを構成したり、RESTサービスを作成して、オブジェクト・ストレージに対するファイルの書込みまたは読取りを安全、迅速かつ低コストで実行することもできます。

イベント駆動型アーキテクチャの実装方法を確認する場合は、Oracle Cloud Infrastructure Data FlowでのAutonomous DatabaseおよびKafkaでの大規模ファイルの処理を参照してください。

アーキテクチャ

アーキテクチャ

Oracle API Gateway統合

APIゲートウェイ統合

このチュートリアルでは、外部アイデンティティ・プロバイダまたはOracle Cloud Infrastructure (OCI)自体(Oracle Identity Cloud Service)を介してJWTトークンを柔軟に認証できるファンクションを介してRESTサービスを実装するようにOracle API Gatewayを構成する方法を示します。プロセスに関係なく、JWTトークンを検証し(属性はファイル名の一部にできるため)、SDKおよび他のカスタマイズ可能なプロセスを介してOCIサービスを使用できます。

目的

前提条件

タスク1: Oracle Identity Cloud Serviceでのアプリケーションの構成

Auth0Oracle Identity Cloud ServiceなどのJWTと連携するようにREST API外部アイデンティティ・プロバイダを構成できます。

このチュートリアルでは、Oracle Identity Cloud Serviceを使用して、ファンクション内で認証をコールします。

タスク2: コードの確認

これは、オブジェクト・ストレージでファイルをダウンロードするための事前認証リンクを認可するためにAPIゲートウェイ・デプロイメントで使用されるPythonコードです。

Pythonコードは、Python fn Projectからダウンロードできます。

python
import io
import json
import logging
import datetime
import jwt
import requests
import base64
import oci

from datetime import timedelta
from fdk import response
from py_zipkin import Encoding #import Zipkin package
from py_zipkin.zipkin import zipkin_span #import Zipkin package
from transport import http_transport #import Zipkin transport

@zipkin_span(service_name='Status: Load File', span_name='statusGetFile')
def auth(ctx, data: io.BytesIO = None):
    auth_token = "invalid"
    token = "invalid"
    apiKey = "invalid"
    expiresAt = (datetime.datetime.utcnow() + timedelta(seconds=60)).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()

    config = oci.config.from_file("config")
    object_storage = oci.object_storage.ObjectStorageClient(config)
    namespace = object_storage.get_namespace().data

    try:
        auth_token = json.loads(data.getvalue())
        secretID = auth_token.get("secretID")
        clientID = auth_token.get("clientID")

        details = oci.object_storage.models.CreatePreauthenticatedRequestDetails(name="data", access_type="AnyObjectReadWrite", bucket_listing_action="ListObjects", time_expires=expiresAt)

        preauth = object_storage.create_preauthenticated_request(namespace_name=namespace, bucket_name="data", create_preauthenticated_request_details=details)
        preauthstr = str(preauth.data)

        auth = clientID + ":" + secretID
        auth_bytes = auth.encode("ascii")
        auth_base64_bytes = base64.b64encode(auth_bytes)
        auth_base64_message = auth_base64_bytes.decode("ascii")

        headers = {"Authorization": "Basic " + auth_base64_message, "Content-Type": "application/x-www-form-urlencoded"}

        scope = "cihxxxxxxxxxxxxxxxxxowu.apigateway.us-ashburn-1.oci.customer-oci.com/super-scope"
        grant_type = "client_credentials"

        body = {"scope": scope, "grant_type": grant_type}

        url_post = "https://Oracle Identity Cloud Service-4fxxxxxxxxxxxxxxxxxxxxxx9.identity.oraclecloud.com/oauth2/v1/token"
        post_response = requests.post(url_post, headers=headers, data=body)

        jwtTokenDecoded = jwt.decode(post_response.json()['access_token'], options={"verify_signature": False})

        return response.Response(
            ctx,
            status_code=200,
            response_data=json.dumps({"active": True, "principal": "foo", "scope": "bar", "clientId": "1234", "expiresAt": expiresAt, "context": {"username": "wally", "token":
post_response.json()['access_token'], "jwtTokenDecoded": jwtTokenDecoded, "objectID": preauthstr}})
        )

    except (Exception, ValueError) as ex:
        logging.getLogger().info('error parsing json payload: ' + str(ex))
        pass

    return response.Response(
        ctx,
        status_code=401,
        response_data=json.dumps({"active": False, "wwwAuthenticate": "API-key"})
    )

@zipkin_span(service_name='Status: Load File', span_name='statusGetFile')
def handler(ctx, data: io.BytesIO = None):
    with zipkin_span(
            service_name="Status: Load File", #You can change it as you need
            span_name="statusGetFile", #You can change it as you need
            transport_handler=http_transport, #zipkin transport, will use it to upload trace data to OCI APM
            encoding = Encoding.V2_JSON,
            binary_annotations = {"status":"Load File", "objectID":json.loads(data.getvalue()).get("objectID")}, #Custom tag
            sample_rate=100 # this is optional and can be used to set custom sample rates
    ):
        return auth(ctx, data)


次のことを理解するために、コードを部分的に分析します。

コードのこの部分は、OCI可観測性のステータス位置を保存します。Zipkinフレームワークを使用してOCI APM可観測性に公開

python
@zipkin_span(service_name='Status: Load File', span_name='statusGetFile')
def handler(ctx, data: io.BytesIO = None):
    with zipkin_span(
            service_name="Status: Load File", #You can change it as you need
            span_name="statusGetFile", #You can change it as you need
            transport_handler=http_transport, #zipkin transport, will use it to upload trace data to OCI APM
            encoding = Encoding.V2_JSON,
            binary_annotations = {"status":"Load File", "objectID":json.loads(data.getvalue()).get("objectID")}, #Custom tag
            sample_rate=100 # this is optional and can be used to set custom sample rates
    ):

これはコードのOCI APMコンソール・ビューで、次のような問合せを含むファイルを見つけることができます:

**ServiceName** = 'Status: Load File' and **objectID** = '50 - DR-HA OIC.pdf'

ジプキン社

次のコードは、オブジェクト・ストレージ・ファイルの有効期限日時をスタブ化します。事前認証が生成され、属性 expiresAtがこの目標に使用されます。timedeltaは、ファイルをダウンロードするための現在の時間から60秒を追加します。

python
expiresAt = (datetime.datetime.utcnow() + timedelta(seconds=60)).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()

ここで、OCI CLIインストールに保存されている資格証明に基づいてOCI Object Storageフレームワークを初期化する必要があります。OCI CLI構成では、~/.oci/configおよび証明書の.pemファイルを使用します。そのため、OCI CLIをローカルにインストールし、オブジェクト・ストレージにアクセスするようにユーザーを構成(OCIドキュメントを参照してOCI CLIおよびオブジェクト・ストレージ・ポリシーを「関連リンク」セクションにインストール)してから、これらの2つのファイルをこのfnプロジェクトにコピーできます。

python
config = oci.config.from_file("config")
object_storage = oci.object_storage.ObjectStorageClient(config)
namespace = object_storage.get_namespace().data

次の手順は、Bodyパラメータ値 secretIDclientID、および objectIDから取得します。

#secretID = the Oracle Identity Cloud Service secretID from the application created to validate the JWT Token
#clientID = the Oracle Identity Cloud Service clientID from the application created to validate the JWT Token
#objectID = the file name in the Object Storage
python
try:
    auth_token = json.loads(data.getvalue())
    secretID = auth_token.get("secretID")
    clientID = auth_token.get("clientID")
    objectID = auth_token.get("objectID")

OCI SDKは、ファイルの読取りや書込み、バケットのコンテンツのリストなど、多くのサービスでObject Storageをサポートできます。たとえば、コンシューマが事前認証済のURLを持つバケットのすべてのコンテンツをリストできるようにすると、コードのこの部分に生成されます。変数bucket_nameには、以前に作成したオブジェクト・ストレージ内のバケットの名前が含まれ、time_expiresはファイルをダウンロードするための日時失効を表します。

python
details = oci.object_storage.models.CreatePreauthenticatedRequestDetails(name="data", access_type="AnyObjectReadWrite", bucket_listing_action="ListObjects", time_expires=expiresAt)

preauth = object_storage.create_preauthenticated_request(namespace_name=namespace, bucket_name="data", create_preauthenticated_request_details=details)
preauthstr = str(preauth.data)

コードのこの部分は、Oracle Identity Cloud ServiceをコールしてclientIDsecretIDを検証し、JWTトークンを取得します。JWTは、署名なしでJSON文字列にデコードできますが、署名は証明書を使用して簡単に検証できます。

python
auth = clientID + ":" + secretID
auth_bytes = auth.encode("ascii")
auth_base64_bytes = base64.b64encode(auth_bytes)
auth_base64_message = auth_base64_bytes.decode("ascii")

headers = {"Authorization": "Basic " + auth_base64_message, "Content-Type": "application/x-www-form-urlencoded"}

scope = "xxxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com/super-scope"
grant_type = "client_credentials"

body = {"scope": scope, "grant_type": grant_type}

url_post = "https://Oracle Identity Cloud Service-xxxxxxxxxxxxxxxxxxxxxxx.identity.oraclecloud.com/oauth2/v1/token"
post_response = requests.post(url_post, headers=headers, data=body)

jwtTokenDecoded = jwt.decode(post_response.json()['access_token'], options={"verify_signature": False})

これが最後の部分で、すべてのデータがコード200で返されます。アプリケーションに必要なすべての情報を返すことができ、コードのこの部分では認証が検証され、コード200 (承認/成功)または401 (未承認)になります。

python
    return response.Response(
        ctx,
        status_code=200,
        response_data=json.dumps({"active": True, "principal": "foo", "scope": "bar", "clientId": "1234", "expiresAt": expiresAt, "context": {"username": "wally", "token": post_response.json()['access_token'], "jwtTokenDecoded": jwtTokenDecoded, "objectID": preauthstr}})
    )

except (Exception, ValueError) as ex:
    logging.getLogger().info('error parsing json payload: ' + str(ex))
    pass

return response.Response(
    ctx,
    status_code=401,
    response_data=json.dumps({"active": False, "wwwAuthenticate": "API-key"})
)



タスク3: fnで認証するためのOCI APIゲートウェイの構成

OCI APIゲートウェイは、APIをOCIファンクションとしてデプロイできます。前述のとおり、コードは、APIゲートウェイ構成によって渡されたclientIDsecretIDおよびobjectID情報を含むbodyを使用します。デプロイメントを構成できます。

APIゲートウェイ1

APIゲートウェイ2

APIゲートウェイ3

デプロイメントを保存した後、ここでRESTエンドポイントを取得できます。Object Storage REST APIサービスの完全なエンドポイントは、このエンドポイントに末尾に"/"を加えたものです(前述の/をパスとして宣言しました)。

APIエンドポイント

タスク4: APIゲートウェイ・デプロイメントのテスト

CURLまたはPostmanでアプリケーションをテストできます:

bash
curl --location 'https://xxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com/dummyauthorizer/' \
--header 'Content-Type: text/plain' \
--data '{"clientID": "e3exxxxxxxxxxxxxxxxxc2f", "secretID": "8exxxxxxx-fa5e-xxcf-8xxxc-xxxxxxxxxxx87", "objectID": "any-file.txt"}'

ノート: 属性objectIDは、OCIコンソールのトレース・エクスプローラでの可観測性とそのステータスの表示方法を示すためにのみここで使用されます。

郵便配達人

すべてが正常な場合は、成功コード200が表示されます。

成功結果

事前認証済オブジェクト・ストレージのURLが生成され、ファイルを60秒間ダウンロードできます。次の例に従ってファイルをダウンロードできます。

https://objectstorage.us-ashburn-1.oraclecloud.com + [access_uri]

or

https://objectstorage.us-ashburn-1.oraclecloud.com/p/eL5C0R0luN_cTNn-vUF7_Dx_z2N4w7IXemKr5y61cSRxZZPRXcR2Yj1dNCaJBDK8/n/idavixsf5sbx/b/data/o/calico.yaml

次のコマンドラインを使用して、バケット・ファイルをダウンロード、アップロードまたは表示できます。

- TO UPLOAD A FILE: `curl https://objectstorage.us-ashburn-1.oraclecloud.com/p/HoPudIF45Bj6J5-Qy3J1D9dOplLuKtECRFhvOTkKAtBjJXkOTDx0Pt8gXbOOEoRx/n/idavixsf5sbx/b/data/o/ --upload-file func.py`

- TO DOWNLOAD A FILE: `curl https://objectstorage.us-ashburn-1.oraclecloud.com/p/3ZyXd6PchrTFrp1oxmedamSG1ojwQa3BxPUyonAA-q1mf3QAe5STpDrt89eYITPf/n/idavixsf5sbx/b/data/o/func.py`

- TO LIST BUCKET: `curl https://objectstorage.us-ashburn-1.oraclecloud.com/p/ODVRMB71kD0SHWuoY4ojVd93nmIiy8u0zrxA56T7FBaohAgA7k8KOLAIlhxjcveE/n/idavixsf5sbx/b/data/o/``

タスク5: 可観測性ダッシュボードの構成

基本的に、すべてのOCIリソースがダッシュボードにメトリックを表示でき、これらのリソース上の多くのイベントがアクションをトリガーできます。このデモでは、オブジェクト・ストレージで書込みまたは読取りが行われたファイルの数を示すダッシュボードを構成できます。

監視

ダッシュボードクエリーは、次のように設定できます。

謝辞

その他の学習リソース

docs.oracle.com/learnで他のラボをご覧いただくか、Oracle Learning YouTubeチャネルでより無料のラーニング・コンテンツにアクセスしてください。また、education.oracle.com/learning-explorerにアクセスして、Oracle Learning Explorerになります。

製品ドキュメントについては、Oracle Help Centerを参照してください。