Rust での Flex Gateway カスタムポリシーの実装

概要

WebAssembly (WASM) 拡張機能を使用して、Envoy 上でカスタムフィルターとして機能するカスタムポリシーを実装できます。これらのポリシーは、 WebAssembly for Proxies (Rust SDK)​ を使用して実装します。以下の例では、Rust と関連ツールに関する知識があることを前提としています。

ライフサイクルイベント

カスタムポリシーは、Envoy に依存しないイベント駆動型の L4/L7 プロキシ用ローレベルインターフェースである proxy-wasm ABI​ に基づいています。このインターフェースは、WASM 拡張機能とホストがやり取りする方法を指定し、リスン可能なライフサイクルイベントである HTTP 要求ライフサイクルイベントに加えて WASM フィルターライフサイクルイベントも使用します。

SDK は、各ライフサイクルイベントに関連した情報を取得するためのメソッドを提供します。たとえば、​on_http_request_headers​ イベントから取得されるデータを使用して、関連するカスタムポリシーロジックを実行でき、その結果によって、要求を処理する必要があるかどうかをランタイムに通知できます。

HTTP 要求ライフサイクルイベント

  • on_http_request_headers

    エンドポイントが完全な HTTP 要求ヘッダーのセットを受け取るとトリガーされます。

  • on_http_request_body

    エンドポイントが HTTP 要求の最初の数バイトを受け取るとトリガーされます。

  • on_http_response_headers

    エンドポイントが完全な HTTP 応答ヘッダーのセットを受け取るとトリガーされます。

  • on_http_response_body

    エンドポイントが HTTP 応答本文の最初の数バイトを受け取るとトリガーされます。

ライフサイクルイベントの絞り込み

  • on_configure

    WASM フィルターが使用可能な設定で開始されるとトリガーされます。

    Flex Gateway は、ポリシー設定を JSON にシリアル化し、シリアル化された設定は Envoy WASM フィルターの設定に使用されます。この JSON をカスタムポリシーで使用されるデータに非シリアル化して解析できます。

ライフサイクルイベントの実装例

次の Envoy フィルターテンプレートは、ライフサイクルイベントの実装方法を示しています。

use proxy_wasm::traits::*;
use proxy_wasm::types::*;

proxy_wasm::main! {{
    proxy_wasm::set_log_level(LogLevel::Trace);
    proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
        Box::new(HttpConfigHeaderRoot {
            header_content: String::new(),
        })
    });
}}

struct HttpConfigHeader {
    header_content: String,
}

impl Context for HttpConfigHeader {}

impl HttpContext for HttpConfigHeader {
    fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
        Action::Continue
    }

    fn on_http_request_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action {
        Action::Continue
    }

    fn on_http_response_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
        Action::Continue
    }

    fn on_http_response_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action {
        Action::Continue
    }
}

struct HttpConfigHeaderRoot {
    header_content: String,
}

impl Context for HttpConfigHeaderRoot {}

impl RootContext for HttpConfigHeaderRoot {
    fn on_configure(&mut self, _: usize) -> bool {
        if let Some(config_bytes) = self.get_plugin_configuration() {
            self.header_content = String::from_utf8(config_bytes).unwrap()
        }
        true
    }

    fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
        Some(Box::new(HttpConfigHeader {
            header_content: self.header_content.clone(),
        }))
    }

    fn get_type(&self) -> Option<ContextType> {
        Some(ContextType::HttpContext)
    }
}

カスタム認証ヘッダーポリシーの例

次のチュートリアルでは、すでに Exchange にパブリッシュされている​ポリシー定義​のポリシー実装の例を作成する方法を示しています。 このポリシー例では、​x-custom-auth​ ヘッダーがユーザー設定の値と一致しない要求をブロックしています。

ポリシー開発プロセスは、以下のステップで行います。

  1. ポリシー定義 JSON ファイルを作成する。

  2. プロジェクトをセットアップする。

  3. カスタムポリシーを開発する。

ポリシー定義 JSON ファイルの作成

ポリシー実装例では、ポリシー定義 JSON ファイルが必要です。Flex Gateway は、ポリシー設定をポリシー定義構造と一緒に JSON として Envoy WASM フィルターに渡します。

認証ヘッダーポリシーの例には、ヘッダーで渡す必要のある値のパラメーターのみを使用しています。ポリシー定義 JSON ファイルは次の内容と一致している必要があります。

{
  "title": "Custom Auth Header",
  "type": "object",
  "description": "Enforces HTTP authentication matching x-custom-auth value to what is configured in the policy.",
  "properties": {
    "secret-value": {
      "title": "Custom Auth Header Password",
      "type": "string",
      "@context": {
        "@characteristics": [
          "security:sensitive"
        ]
      }
    }
  },
  "required": [
    "secret-value"
  ],
  "unevaluatedProperties": false,
  "@context": {
    "@vocab": "anypoint://vocabulary/policy.yaml#",
    "security": "anypoint://vocabulary/policy.yaml#"
  },
  "$id": "custom-auth-header-simple",
  "$schema": "https://json-schema.org/draft/2019-09/schema"
}

プロジェクトのセットアップ

  1. 次のコマンドで Rust プロジェクトを作成します。

    cargo new flex_custom_policy_example --lib

    これにより ​flex_custom_policy_example​ ディレクトリが作成されます。

  2. 次の内容を ​flex_custom_policy_example​ ディレクトリの ​Cargo.toml​ にコピーします。

    [package]
    name = "flex_custom_policy_example"
    version = "0.1.0"
    edition = "2021"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [lib]
    crate-type = ["cdylib"]
    name="flex_custom_policy_example"
    path="src/lib.rs"
    
    [dependencies]
    proxy-wasm = { git = "https://github.com/proxy-wasm/proxy-wasm-rust-sdk.git", tag = "v0.2.0" }
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0"
    
    [profile.release]
    opt-level = "z"
    lto = "fat"

カスタムポリシーの開発

  1. 次のポリシーブートストラップコードを新しい Rust ソースファイルに追加します。

    use proxy_wasm::traits::*;
    use proxy_wasm::types::*;
    use serde::Deserialize;
    
    proxy_wasm::main! {{
        proxy_wasm::set_log_level(LogLevel::Trace);
        proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
            Box::new(CustomAuthRootContext {
                config: CustomAuthConfig::default(),
            })
        });
    }}

    このコードは、WASM フィルターを Envoy にデプロイするために必要です。​main​ ブロックは、ルートコンテキストを設定します。このルートコンテキストは、受け取った各 HTTP 要求に対して子コンテキストを生成するために使用する Envoy エンティティです。

  2. 次のルートコンテキスト実装コードを追加します。

    struct CustomAuthRootContext {
        config: CustomAuthConfig,
    }
    
    impl Context for CustomAuthRootContext {}
    
    impl RootContext for CustomAuthRootContext {
    
        fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
            Some(Box::new(CustomAuthHttpContext {
                config: self.config.clone(),
            }))
        }
    
        fn get_type(&self) -> Option<ContextType> {
            Some(ContextType::HttpContext)
        }
    }

    各 Envoy フィルターは、ルートコンテキスト実装を提供するために必要です。​RootContext​ trait には、実装可能な便利なメソッドが含まれています。この例では、Envoy が子コンテキストを生成できるように、HTTP フィルターは ​create_http_context​ と ​get_type​ を実装しています。

  3. 次の ​Struct​ を追加して、この JSON の解析を有効にします。

    #[derive(Default, Clone, Deserialize)]
    struct CustomAuthConfig {
    
        #[serde(alias = "secret-value")]
        secret_value: String,
    }

    Flex Gateway は、ポリシー定義で指定されている JSON を使用してポリシーを設定します。解析が必要な JSON には ​secret-value​ という項目が 1 つあります。

  4. 設定を非シリアル化します。

    基本的なポリシー設定構造を作成したら、逆シリアル化するために ​RootContexton_configure​ メソッドを実装する必要があります。

    CustomAuthRootContext​ の ​RootContext​ の実装内で、次のスニペットのように ​on_configure​ メソッドを追加します。

    impl RootContext for CustomAuthRootContext {
    
        fn on_configure(&mut self, _: usize) -> bool {
            if let Some(config_bytes) = self.get_plugin_configuration() {
                self.config = serde_json::from_slice(config_bytes.as_slice()).unwrap();
            }
    
            true
        }
    
        // Other implemented methods
        // ...
    }
  5. 次の HTTP コンテキストコードを追加します。

    struct CustomAuthHttpContext {
        pub config: CustomAuthConfig,
    }
    
    impl Context for CustomAuthHttpContext {}
    
    impl HttpContext for CustomAuthHttpContext {}

    受け取った各要求によって新しい ​CustomAuthHttpContext​ が作成されます。この ​CustomAuthHttpContext​ は、HTTP 要求が有効である間は有効です。これにより (要求間の状態データではなく) 要求に関連した状態データを格納できます。

  6. 次のコアポリシー機能ロジックを追加します。

    impl HttpContext for CustomAuthHttpContext {
        fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
    
            if let Some(value) = self.get_http_request_header("x-custom-auth") {
                if self.config.secret_value == value {
                    return Action::Continue;
                }
            }
    
            self.send_http_response(401, Vec::new(), None);
    
            Action::Pause
        }
    }

    proxy-wasm​ ABI は、受け取った要求のヘッダー値を取得して、値を ​secret-value​ と比較します。​secret-value​ がヘッダーと一致すると、実装は ​Action::Continue​ を返します。そうでない場合は ​Action::Pause​ を返し、フィルター自身が ​send_http_response​ を使用して呼び出し側のクライアントに応答を返します。

    注意: Rust SDK には、​get_http_request_header​ を使用した非 UTF-8 値の読み込みの問題があります。この問題を回避するには、​get_http_request_headers_bytes​ メソッドを使用し、​from_utf8 メソッド​を使用してバイトを読み取ります。次に例を示します。

    let header = self.get_http_request_header_bytes("x-custom-auth").map(String::from_utf8).and_then(Result::ok);
  7. wasm32​ をターゲットとして追加して、コンパイルを有効にします。

    rustup target add wasm32-wasi

    ポリシーに含まれるすべてのサードパーティライブラリは、​wasm32-wasi​ Rust コンパイルターゲットと互換性がある必要があります。

    一部のライブラリは wasm32-wasi ターゲットと適切に互換性があっても、Flex Gateway にデプロイすると適切に動作しないことがあります。この状況が発生した場合のデプロイメントエラーの例は次のとおりです。

    • Failed to load Wasm module due to a missing import (インポートが欠落しているため Wasm モジュールの読み込みに失敗しました)

    • Wasm VM failed to initialize Wasm code (Wasm VM が Wasm コードの初期化に失敗しました)

    • Plugin configured to fail closed failed to load (フェールクローズに設定されたプラグインの読み込みに失敗しました)

    これらの問題を解決するには、サードパーティライブラリのオーナーに問い合わせるか、別のライブラリを使用してください。

  8. 次のコマンドでカスタムポリシーをコンパイルします。

    cargo build --target wasm32-wasi --release

    コンパイルにより、バイナリ ​.wasm​ ファイルが ​./target/wasm32-wasi/release​ ディレクトリに出力されます。

    Exchange へのカスタムポリシーのパブリッシュついての詳細は、​「Flex Gateway または Mule 4 カスタムポリシーのパブリッシュ」​を参照してください。