Quantcast
Channel: Oracle Blogs 日本語のまとめ
Viewing all articles
Browse latest Browse all 760

[Functions] Key Managementを使った設定変数の暗号化と復号/Using Key Management To Encrypt And Decrypt Configuration Variables

$
0
0
原文はこちら

最近このブログではOracle Functionsに関連する様々なテーマについてご紹介してきましたが、今回ご紹介するのはもしかするとこのブログシリーズで最初に取り上げるべきだったかもしれないテーマです。以前のポストでアプリケーションとファンクションの設定変数の設定方法をご説明したんですが、こうした設定変数をセキュアに保持しておく方法はそのときにはご説明していませんでした。このポストでは、Oracle CloudテナンシーのKey Managementを使って設定を暗号化、復号することによってこのセキュアな保持を実現する方法をご説明します。

これをやるにはいくつかのステップを進んでいくことになるので、ここでアウトラインを示しておきますね:
  • KMS Vaultの作成
  • Master Encryption Keyの作成
  • Data Encryption Key (DEK) をMaster Encryption Keyから生成
  • DEK plaintext から返却された値を使って sensitive value を暗号化(オフライン)
  • 暗号化された sensitive value をサーバレスアプリケーションの設定変数として保存
  •  sensitive value の暗号化に使ったDEK ciphertext と initVector をファンクションの設定変数として保存
  • ファンクションの中でOCIDとCryptographic Endpointを使ってOCI KMS SDKを呼び出し、DEK ciphertext を復号して plaintext に戻す
  • 復号したDEK plaintext と initVectorを使ってsensitive value を復号する
ここで sensitive value と記載しているものは、暗号化が必要なものであればなんでも有り得ます。データベースパスワードだったり、APIキーだったり、などなど。Oracle Functionsの設定変数の中に平文ではなく暗号化して保存したいものを示すプレースホルダーであり、実態がなんであれ構いません。
なんかステップ多いな、って感じですよね。でも実のところ書いてあるとおり進めていけばそんなに大変じゃありません。それに、本当のところ、アプリケーションとファンクションにとってセキュリティというのは一番大事なことですからね。

始める前に

ここではOracle Functionsの中でリソースプリンシパルを活用することになります。これはすなわち、OCI SDKを使う際にOCI設定ファイルを含める必要は生じないということですが、一方で、KMS APIとやり取りするうえでの適切な許可ポリシーを備えたダイナミックグループを作成しておく必要があるということになります。
まず、'Dynamic Group'を作成して'rules'を以下のようにセットしてください(ファンクションをデプロイするコンパートメントのOCIDを使ってください):
ダイナミックグループとリソースプリンシパルをOracle Functionsと組み合わせて使う方法について詳しく知りたければドキュメントを参照ください。次のステップはダイナミックグループ用にポリシーを作り、 keysvaults と key-delegate の管理権限を与えることです。こちらについても、ポリシーのドキュメントを読んでどのようなポリシーを適用としているのか理解してください。

アプリケーションとファンクションの作成

では、サーバレスアプリケーションを作成しましょう(適切なサブネットOCISで置き換えてください):
fn create app --annotation oracle.com/oci/subnetIds='["ocid1.subnet.oc1.phx..."]' fn-kms
そしてファンクションを作成します:
fn init --runtime java fn-kms-demo

依存対象

KMS SDKのための依存関係を pom.xml に追加しておく必要があります:
<dependency>
    <groupId>com.oracle.oci.sdk</groupId>
    <artifactId>oci-java-sdk-keymanagement</artifactId>
    <version>1.6.0</version>
</dependency>
view rawpom.xml hosted with ❤ by GitHub
Java 11をお使いの場合は、 javax.activation-apiもマニュアルで追記しておきます:
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>javax.activation-api</artifactId>
    <version>1.2.0</version>
</dependency>
view rawpom.xml hosted with ❤ by GitHub

Dockerfile

いくつかの環境変数に依存することになるので、お手製の Dockerfile を作成する必要があります。デフォルトで実行される Dockerfile との違いはファンクションをデプロイする際のDockerビルドでのテスト実行をスキップするところだけです。なぜこれをスキップするかというと、現状ではでファンクションをデプロイする際に環境変数をDockerビルドコンテキストに渡す術がないからです。以下のお手本を使ってください:
FROM fnproject/fn-java-fdk-build:jdk11-1.0.98 as build-stage
WORKDIR /function
ENV MAVEN_OPTS -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttps.proxyHost= -Dhttps.proxyPort= -Dhttp.nonProxyHosts= -Dmaven.repo.local=/usr/share/maven/ref/repository
ADD pom.xml /function/pom.xml
RUN ["mvn", "package", "dependency:copy-dependencies", "-DincludeScope=runtime", "-DskipTests=true", "-Dmdep.prependGroupId=true", "-DoutputDirectory=target", "--fail-never"]
ADD src /function/src
RUN ["mvn", "package", "-DskipTests=true"]
FROM fnproject/fn-java-fdk:jre11-1.0.98
WORKDIR /function
COPY --from=build-stage /function/target/*.jar /function/app/
CMD ["codes.recursive.KmsDemoFunction::handleRequest"]
view rawDockerfile hosted with ❤ by GitHub
ファンクションをデプロイする前にマニュアルでテストを実行することをお忘れなく!

VaultとMaster Encryption Keyの作成

OCIコンソールのサイドバーから、'Governance and Administration'の配下の'Security'→'Key Management'を選択してください:
'Create Vault'をクリックしてVaultの詳細を入力します:
Vaultが作成できたら、Vaultの名前をクリックするとVaultの詳細が表示されます。詳細画面で'Create Key'をクリックして新しいMaster Encryption Keyを作成し、ダイアログの入力欄を埋めていきます:
Master Encryption KeyのOCIDとVaultの'Cryptographic Endpoint'をコピーしておいてください。これはあとでDBパスワード用のData Encrption Key(DEK)を作成する際に使います。

Data Encryption Key (DEK)の作成

以下のようにData Encrption Key(DEK)をOCI CLIから作成します:
oci kms crypto generate-data-encryption-key \
--key-id ocid1.key.oc1.phx.... \
--include-plaintext-key true \
--key-shape "{\"algorithm\": \"AES\", \"length\": 16}" \
--endpoint [Cryptographic Endpoint]
view rawgenerate-dek.sh hosted with ❤ by GitHub
 generate-data-encryption-key から返ってくる ciphertext と plaintext の値を手元に持っておいてください。あとですぐに必要になります。
DEK ciphertextの例
I...[random chars]...​AAAAAA==
ciphertextをアプリケーションの設定変数として保持させます:
fn config app fn-kms DEK_CIPHERTEXT I...[random chars]...​AAAAAA==
DEK plaintextの例
0…​[random chars]...=

パスワードの暗号化

このステップではパスワードをファンクションの中ではなく、オフラインで暗号化します。あとでファンクションは稼働時に復号を行うことになります。スタンドアロンのJavaプログラムの中で先程のDEKを使ってパスワードを暗号化します。以下のお手本を使ってもらってもよいです。
注意:Plug in your DEK plaintext の値をプラグインして initVectorにランダムな16byteのStringを与えてください。 initVector はあとで復号の際に使えるように設定変数として保持します。
importjavax.crypto.Cipher;
importjavax.crypto.SecretKey;
importjavax.crypto.spec.GCMParameterSpec;
importjavax.crypto.spec.IvParameterSpec;
importjavax.crypto.spec.SecretKeySpec;
importjava.security.SecureRandom;
importjava.util.Base64;
classMain {
    privatestaticString key ="0...=="; //DEK plaintext value
    privatestaticString initVector ="abcdefghijklmnop"; //must be 16 bytes
    publicstaticvoidmain(String[] args) {
        System.out.println(encrypt("hunter2"));
    }
    publicstaticStringencrypt(Stringvalue) {
        try {
            IvParameterSpec iv =newIvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec =newSecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher =Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            returnBase64.getEncoder().encodeToString(encrypted);
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        returnnull;
    }
}
view rawEncrypt.java hosted with ❤ by GitHub
ランダムな16 byteの initVector Stringをアプリケーションの設定変数として保持させます:
fn config app fn-kms INIT_VECTOR_STRING [Random 16 byte string]
上述のプログラムの出力した値をコピーしてください。これが暗号化されたパスワードです。これもアプリケーションの設定変数として保持させます:
fn config app fn-kms ENCRYPTED_PASSWORD N...==
最後に、Master Encryption KeyのOCIDとCryptographic Endpointをアプリケーションの設定変数として格納します:
fn config app fn-kms KEY_OCID ocid1.key.oc1.phx...
fn config app fn-kms ENDPOINT https://...-crypto.kms.us-phoenix-1.oraclecloud.com

サーバレスファンクション

これでサーバレスファンクションに暗号化されたパスワードを復号するよう修正を加えることができるようになりました。以下のようになります:
packagecodes.recursive;
importcom.oracle.bmc.auth.AbstractAuthenticationDetailsProvider;
importcom.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
importcom.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider;
importcom.oracle.bmc.keymanagement.KmsCryptoClient;
importcom.oracle.bmc.keymanagement.model.DecryptDataDetails;
importcom.oracle.bmc.keymanagement.requests.DecryptRequest;
importcom.oracle.bmc.keymanagement.responses.DecryptResponse;
importjavax.crypto.Cipher;
importjavax.crypto.spec.IvParameterSpec;
importjavax.crypto.spec.SecretKeySpec;
importjava.io.IOException;
importjava.util.Base64;
importjava.util.Map;
publicclassKmsDemoFunction {
    privatefinalString initVector;
    publicKmsDemoFunction() {
        this.initVector =System.getenv().get("INIT_VECTOR_STRING");
    }
    publicMap<String, String>decryptSensitiveValue() throwsIOException {
        Boolean useResourcePrincipal =Boolean.valueOf(System.getenv().getOrDefault("USE_RESOURCE_PRINCIPAL", "true"));
        String encryptedPassword =System.getenv().get("ENCRYPTED_PASSWORD");
        String cipherTextDEK =System.getenv().get("DEK_CIPHERTEXT");
        String endpoint =System.getenv().get("ENDPOINT");
        String keyOcid =System.getenv().get("KEY_OCID");
        /*
        * when deployed, we can use a ResourcePrincipalAuthenticationDetailsProvider
        * for our the auth provider.
        * locally, we'll use a ConfigFileAuthenticationDetailsProvider
        */
        AbstractAuthenticationDetailsProvider provider =null;
        if( useResourcePrincipal ) {
            provider =ResourcePrincipalAuthenticationDetailsProvider.builder().build();
        }
        else {
            provider =newConfigFileAuthenticationDetailsProvider("/.oci/config", "DEFAULT");
        }
        KmsCryptoClient cryptoClient =KmsCryptoClient.builder().endpoint(endpoint).build(provider);
        DecryptDataDetails decryptDataDetails =DecryptDataDetails.builder().keyId(keyOcid).ciphertext(cipherTextDEK).build();
        DecryptRequest decryptRequest =DecryptRequest.builder().decryptDataDetails(decryptDataDetails).build();
        DecryptResponse decryptResponse = cryptoClient.decrypt(decryptRequest);
        String decryptedDEK = decryptResponse.getDecryptedData().getPlaintext();
        String decryptedPassword = decrypt(encryptedPassword, decryptedDEK);
        /*
        * returning the decrypted password for demo
        * purposes only. in your production function,
        * obviously you should not do this.
        */
        returnMap.of(
                "decryptedPassword",
                decryptedPassword
        );
    }
    privateStringdecrypt(Stringencrypted, Stringkey) {
        try {
            IvParameterSpec iv =newIvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec =newSecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher =Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
            returnnewString(original);
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        returnnull;
    }
}

テスト

前述のように、ファンクションのテストをマニュアルで行う必要があります。
ローカルでファンクションをテストできるようにするには、いくつかの環境変数をセットしておく必要があります。GitHubプロジェクトのルートにある env.sh を見るとどの変数をセットする必要があるかわかります(あるいは↓からコピーしてください)。これらの値は全て上述のステップから取得できます(格納しておいたアプリケーションの設定変数と対応していることに留意ください)。
#!/usr/bin/env bash
export ENCRYPTED_PASSWORD=
export INIT_VECTOR_STRING=
export KEY_OCID=
export DEK_CIPHERTEXT=
export ENDPOINT=
export USE_RESOURCE_PRINCIPAL=false
view rawenv.sh hosted with ❤ by GitHub
必要な環境変数をセットしたら、ユニットテストを書きましょう:
packagecodes.recursive;
importcom.fasterxml.jackson.databind.ObjectMapper;
importcom.fnproject.fn.testing.*;
importorg.junit.*;
importjava.io.IOException;
importjava.util.Map;
import staticorg.junit.Assert.*;
publicclassKmsDemoFunctionTest {
    @Rule
    publicfinalFnTestingRule testing =FnTestingRule.createDefault();
    @Test
    publicvoidshouldDecryptPassword() throwsIOException {
        testing.givenEvent().enqueue();
        testing.thenRun(KmsDemoFunction.class, "decryptSensitiveValue");
        FnResult result = testing.getOnlyResult();
        System.out.println(result.getBodyAsString());
        Map<String, String> resultMap =newObjectMapper().readValue(result.getBodyAsString(), Map.class);
        assertEquals("hunter2", resultMap.get("decryptedPassword"));
    }
}

デプロイ

以下でファンクションをデプロイします:
fn deploy --app fn-kms
呼び出しは以下:
fn invoke fn-kms fn-kms-demo
復号されたパスワードが返却されるでしょう:
{"decryptedPassword":"hunter2"}

まとめ

このポストではOCI KMSを使ってVaultとMaster Encryption Keyを作成しました。そしてそのMaster Encryption Keyを使ってData Encrption Key(DEK)を作成し、DEKを使って機密情報を暗号化し、その暗号化された機密情報をサーバレスファンクションの設定変数に保存しました。そしてこの暗号化された機密情報にデプロイしたファンクションからアクセスし、ファンクションで使えるように復号しました。

参考情報

Oracle Functions関連のわたしの他のポストもチェックしてみてください:
ご参考までに、デモの中で使ったコードはGitHubで利用可能です。https://github.com/recursivecodes/fn-kms-demo

Viewing all articles
Browse latest Browse all 760

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>