Oracle Cloud Infrastructureの環境では、暗号化キーといったセンシティブな情報の保存先として、ボールト(Vault)のサービスを使用できます。
ボールトのサービスを呼び出すPL/SQL SDKが提供されているので、これを使ってシークレットとして暗号キーを保存と取り出しを実装してみます。
ボールトが作成済みであることが前提条件です。
ボールトの作成手順については、日本オラクルから公開されている以下のチュートリアルが参考になります。
OCIチュートリアル - Oracle Cloud Infrastructureを使ってみよう
プライベート認証局と証明書の発行
https://oracle-japan.github.io/ocitutorials/intermediates/certificate/
シークレットの作成、更新、削除を行うAPIを呼び出す際に使用するクリデンシャルは、以下の手順に沿って作成します。
作成されるクリデンシャルはMY_OCI_CREDになります。
APEXにて以下のテスト用アプリケーションを作成します。以下の操作を行なっています。
APEXにて以下のテスト用アプリケーションを作成します。以下の操作を行なっています。
- Generate Keyをクリックし暗号化キーを生成する。暗号化キーはGenerated Keyに返される。
- Secret Nameを指定する。CreateをクリックしてGenerated Keyに表示されている暗号化キーをシークレットとして保存する。Secret IDとCurrent Version Numberが返される。
- Generate Keyをクリックし、暗号化キーを更新する。
- Updateをクリックし、Secret IDのシークレットを新たに生成されたGenerated Keyの値で更新する。Current Version Numberが返される。
- Getをクリックし、シークレットとして保存されている暗号化キーをKeyに返す。Generated Keyと同じ値になる。
- Time Of Deletionを設定し、Deleteをクリックする。Secret IDのシークレットの削除がスケジュールされる。
OCIコンソールより、一連の操作を行なったシークレットを確認します。
バージョンが2つあり、すでに削除がスケジュールされていることが確認できます。
以下より、実施した作業を紹介します。
マスター暗号化キーの作成
シークレットを作成する際に指定する暗号化キーを作成します。
OCIのコンソールよりボールトの詳細ページを開きます。
ボールトのOCIDはAPI呼び出しの際に使用するため、あらかじめコピーしておきます。
リソースのマスター暗号化キーを選択し、キーの作成をクリックします。
画面右よりドロワーが開きます。
保護モードとしてソフトウェアが使えます。名前は任意ですがSFM_MASTER_KEYとしました。キーのシェイプ: アルゴリズムはAES、キーの長さは選択できる最長の長さである256ビットを選択します。
以上で作成をクリックします。
少し時間がかかりますが、マスター暗号化キーが作成されます。
マスター暗号化キーのOCIDもAPIの呼び出しに使用するため、コピーをとっておきます。
ポリシーの作成
クリデンシャルMY_OCI_CREDの元となるユーザーがシークレットを操作できるように、ポリシーを作成します。
OCIコンソールよりアイデンティティのポリシーを開きます。
ポリシーの作成をクリックします。
作成するポリシーの名前はSecretAdminPolicyとしています。コンパートメントにはルートを選んでいます。ポリシー・ユースケースとしてキーおよびシークレット管理を選択し、共通ポリシー・テンプレートとしてセキュリティ管理者がボールト、キーおよびシークレットを管理できるようにしますを選択します。
ポリシーを適用するグループとしてAPEXObjectManagersを選択し、場所としてルートを指定しています。クリデンシャルMY_OCI_CREDを作成する手順にグループAPEXObjectManagersの作成も含まれています。
以上で作成をクリックします。
ポリシーSecretAdminPolicyが作成されます。
テナンシのOCIDの取得
シークレットを操作するAPIの呼び出しには、ボールト、マスター暗号化キーの他にコンパートメントのOCIDが必要です。
OCIコンソールよりコンパートメントを開き、使用するコンパートメントのOCIDをコピーしておきます。
PL/SQL SDKの実行権限の付与
シークレットを操作するPL/SQL SDKのパッケージの実行権限を、APEXのワークスペース・スキーマに与えます。以下の例ではワークスペース・スキーマをwksp_apexdevとしています。環境に合わせてワークスペース・スキーマ名を置き換えます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
grant execute on dbms_crypto to wksp_apexdev; | |
grant execute on dbms_cloud to wksp_apexdev; | |
-- 以下PL/SQL SDK | |
grant execute on DBMS_CLOUD_OCI_SC_SECRETS to wksp_apexdev; | |
grant execute on dbms_cloud_oci_sc_secrets_get_secret_bundle_by_name_response_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_secrets_secret_bundle_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_secrets_secret_bundle_content_details_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vault_base64_secret_content_details_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_secrets_base64_secret_bundle_content_details_t to wksp_apexdev; | |
grant execute on DBMS_CLOUD_OCI_VT_VAULTS to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vt_vaults_create_secret_response_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vault_create_secret_details_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vault_update_secret_details_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vt_vaults_update_secret_response_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vault_schedule_secret_deletion_details_t to wksp_apexdev; | |
grant execute on dbms_cloud_oci_vt_vaults_schedule_secret_deletion_response_t to wksp_apexdev; |
データベース・アクションの開発のSQLより実行します。
パッケージSFM_SECRET_UTILの作成
パッケージSFM_SECRET_UTILを作成します。
create_secret、update_secret、get_key_from_secret、delete_secretの4つのファンクションまたはプロシージャが実装されています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
OCIのボールト・サービスに暗号キーをシークレットとして保存する。 | |
シークレットの更新とシークレットから暗号キーを取り出す。 | |
*/ | |
create or replace package sfm_secret_util as | |
/** | |
OCIのボールト・サービスにシークレットをp_secret_nameとして作成し、 | |
暗号キーp_keyを保存する。 | |
戻り値はシークレットID - シークレットのOCID。それとバージョン番号を戻す。 | |
新規作成の場合はバージョンは1が返されるはず。 | |
*/ | |
function create_secret( | |
p_key in raw | |
,p_secret_name in varchar2 | |
,p_compartment_ocid in varchar2 | |
,p_master_key_ocid in varchar2 | |
,p_vault_ocid in varchar2 | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
,p_current_version_number out number | |
) return varchar2; | |
/** | |
すでに作成されたシークレットのOCIDを渡し、暗号キーを更新する。 | |
戻り値は更新されたバージョン番号。 | |
*/ | |
function update_secret( | |
p_key in raw | |
,p_secret_id in varchar2 | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
) return number; | |
/** | |
シークレットを指定して、保存されている暗号キーを取り出す。 | |
シークレットIDとバージョン番号も返す。 | |
*/ | |
function get_key_from_secret( | |
p_secret_name in varchar2 | |
,p_vault_ocid in varchar2 | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
,p_secret_id out varchar2 | |
,p_version_number out number | |
) return raw; | |
/** | |
すでに作成されたシークレットのOCIDを渡し、シークレットの削除をスケジュールする。 | |
*/ | |
procedure delete_secret( | |
p_secret_id in varchar2 | |
,p_time_of_deletion in timestamp with time zone | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
); | |
end; | |
/ | |
/** | |
パッケージ本体 | |
*/ | |
create or replace package body sfm_secret_util as | |
function generate_secret_content_details( | |
p_key in raw | |
) return dbms_cloud_oci_vault_base64_secret_content_details_t | |
as | |
begin | |
return new dbms_cloud_oci_vault_base64_secret_content_details_t( | |
content_type => 'BASE64' | |
,name => null | |
,stage => 'CURRENT' | |
,content => utl_raw.cast_to_varchar2(utl_encode.base64_encode(p_key)) | |
); | |
end generate_secret_content_details; | |
function create_secret( | |
p_key in raw | |
,p_secret_name in varchar2 | |
,p_compartment_ocid in varchar2 | |
,p_master_key_ocid in varchar2 | |
,p_vault_ocid in varchar2 | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
,p_current_version_number out number | |
) return varchar2 | |
as | |
l_key raw(32); -- 暗号キー | |
/* ボールトの呼び出しの引数 */ | |
l_response dbms_cloud_oci_vt_vaults_create_secret_response_t; | |
l_secret_detail dbms_cloud_oci_vault_create_secret_details_t; | |
l_secret_content_base64 dbms_cloud_oci_vault_base64_secret_content_details_t; | |
/* 失敗 */ | |
e_create_secret_exception exception; | |
begin | |
/* | |
* シークレットを作成する際に必要な引数を指定する。 | |
* | |
* マニュアルでは引数はOptionalとなっているが、Optionalはnullでもよいが引数としては指定が必須な模様。 | |
* key_idは(マスター・キーが複数登録済みであれば)、必須だと思われる。 | |
*/ | |
l_secret_detail := new dbms_cloud_oci_vault_create_secret_details_t( | |
compartment_id => p_compartment_ocid | |
, defined_tags => null | |
, description => null | |
, freeform_tags => null | |
, key_id => p_master_key_ocid | |
, metadata => null | |
, secret_content => generate_secret_content_details(p_key) | |
, secret_name => p_secret_name | |
, secret_rules => null | |
, vault_id => p_vault_ocid | |
); | |
/* | |
* シークレットを作成する。 | |
*/ | |
l_response := DBMS_CLOUD_OCI_VT_VAULTS.create_secret( | |
create_secret_details => l_secret_detail | |
,region => p_region | |
,credential_name => p_credential | |
); | |
if l_response.status_code <> 200 then | |
raise e_create_secret_exception; | |
end if; | |
/* シークレットのバージョンとOCIDを返す。 */ | |
p_current_version_number := l_response.response_body.current_version_number; | |
return l_response.response_body.id; | |
end create_secret; | |
function update_secret( | |
p_key in raw | |
,p_secret_id in varchar2 | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
) return number | |
as | |
l_key raw(32); -- 暗号キー | |
/* ボールトの呼び出しの引数 */ | |
l_response dbms_cloud_oci_vt_vaults_update_secret_response_t; | |
l_secret_detail dbms_cloud_oci_vault_update_secret_details_t; | |
l_secret_content_base64 dbms_cloud_oci_vault_base64_secret_content_details_t; | |
/* 失敗 */ | |
e_update_secret_exception exception; | |
begin | |
/* | |
* シークレットを更新する際に必要な引数を指定する。 | |
*/ | |
l_secret_detail := new dbms_cloud_oci_vault_update_secret_details_t( | |
current_version_number => null | |
, defined_tags => null | |
, description => null | |
, freeform_tags => null | |
, metadata => null | |
, secret_content => generate_secret_content_details(p_key) | |
, secret_rules => null | |
); | |
/* | |
* シークレットの更新を実行する。 | |
*/ | |
l_response := DBMS_CLOUD_OCI_VT_VAULTS.update_secret( | |
secret_id => p_secret_id | |
, if_match => null | |
, update_secret_details => l_secret_detail | |
,region => p_region | |
,credential_name => p_credential | |
); | |
if l_response.status_code <> 200 then | |
raise e_update_secret_exception; | |
end if; | |
/* 返却するのはバージョン番号。 */ | |
return l_response.response_body.current_version_number; | |
end update_secret; | |
function get_key_from_secret( | |
p_secret_name in varchar2 | |
,p_vault_ocid in varchar2 | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
,p_secret_id out varchar2 | |
,p_version_number out number | |
) return raw | |
as | |
l_response dbms_cloud_oci_sc_secrets_get_secret_bundle_by_name_response_t; | |
l_secret_bundle dbms_cloud_oci_secrets_secret_bundle_t; | |
l_content_detail dbms_cloud_oci_secrets_secret_bundle_content_details_t; | |
l_content_detail_base64 dbms_cloud_oci_secrets_base64_secret_bundle_content_details_t; | |
-- 取り出したシークレット。 | |
l_secret varchar2(64); | |
l_key raw(32); | |
l_base64_key raw(64); | |
e_get_key_exception exception; | |
begin | |
/* | |
* 名前を指定して、シークレットを取り出す。 | |
*/ | |
l_response := DBMS_CLOUD_OCI_SC_SECRETS.get_secret_bundle_by_name( | |
secret_name => p_secret_name | |
,vault_id => p_vault_ocid | |
,region => p_region | |
,credential_name => p_credential | |
); | |
if l_response.status_code <> 200 then | |
raise e_get_key_exception; | |
end if; | |
/* | |
* レスポンスからBAE64でエンコードされた、シークレットを取り出す。 | |
* 取り出しシークレットをRAW型の暗号キーに戻し、戻り値とする。 | |
*/ | |
l_secret_bundle := l_response.response_body; | |
p_secret_id := l_secret_bundle.secret_id; | |
p_version_number := l_secret_bundle.version_number; | |
l_content_detail := l_secret_bundle.secret_bundle_content; | |
l_content_detail_base64 := treat(l_content_detail as dbms_cloud_oci_secrets_base64_secret_bundle_content_details_t); | |
l_secret := l_content_detail_base64.content; | |
l_base64_key := utl_raw.cast_to_raw(l_secret); | |
l_key := utl_encode.base64_decode(l_base64_key); | |
return l_key; | |
end get_key_from_secret; | |
procedure delete_secret( | |
p_secret_id in varchar2 | |
,p_time_of_deletion in timestamp with time zone | |
,p_region in varchar2 | |
,p_credential in varchar2 | |
) | |
as | |
/* ボールトの呼び出しの引数 */ | |
l_response dbms_cloud_oci_vt_vaults_schedule_secret_deletion_response_t; | |
l_deletion_detail dbms_cloud_oci_vault_schedule_secret_deletion_details_t; | |
/* 失敗 */ | |
e_delete_secret_exception exception; | |
begin | |
/* | |
* シークレットを削除する際に必要な引数を指定する。 | |
*/ | |
l_deletion_detail := new dbms_cloud_oci_vault_schedule_secret_deletion_details_t( | |
time_of_deletion => p_time_of_deletion | |
); | |
/* | |
* シークレットの削除を実行する。 | |
*/ | |
l_response := DBMS_CLOUD_OCI_VT_VAULTS.schedule_secret_deletion( | |
secret_id => p_secret_id | |
, if_match => null | |
, schedule_secret_deletion_details => l_deletion_detail | |
,region => p_region | |
,credential_name => p_credential | |
); | |
if l_response.status_code <> 200 then | |
raise e_delete_secret_exception; | |
end if; | |
end delete_secret; | |
end; | |
/ |
SQLワークショップのSQLスクリプトを使って実行します。
検証用アプリケーションの作成
アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。アプリケーションの名前はシークレットの操作としました。
アプリケーションの作成を実行します。
アプリケーションが作成されます。
アプリケーション定義の置換を開き、置換文字列としてG_COMPARTMENT_OCID、G_MASTER_KEY_OCID、G_VAULT_OCID、G_REGION、G_CREDENTIALを設定します。
ページ・デザイナでホーム・ページを開きます。
ボタンおよびページを作成し、以下のように配置します。
ページ・アイテムP1_TIME_OF_DELETIONのみタイプが日付ピッカーです。それ以外はテキスト・フィールド、ボタンもデフォルトの設定です。
ボタンGENERATE_KEYのクリックに対応したプロセスを作成します。
識別の名前は暗号キーの作成、タイプはコードの実行を選択します。PL/SQLコードには以下を記述します。暗号化キーがGenerated Keyに設定されます。
:P1_GENERATED_KEY := rawtohex(dbms_crypto.randombytes(32));
識別の名前はシークレットの作成、タイプはAPIの呼び出しを選択します。パッケージSFM_SECRET_UTILに含まれるファンクションCREATE_SECRETを呼び出します。
ファンクションの結果として作成されたシークレットのOCIDが返されます。ページ・アイテムP1_SECRET_IDに設定します。
シークレットに保存する暗号化キーであるp_keyとして、以下のPL/SQL式を指定します。
hextoraw(:P1_GENERATED_KEY)
パラメータp_secret_nameはシークレット名です。ユーザーがページ・アイテムP1_SECRET_NAMEに入力します。すでに同名のシークレットが存在する場合、エラーが発生します。
出力パラメータのp_current_version_numberは、ページ・アイテムP1_CURRENT_VERSION_NUMGBERに設定します。シークレットの作成時は1が返されます。
引数p_compartment_ocid、p_master_key_ocid、p_vault_ocid、p_region、p_credentialについては、置換文字列のG_COMPARTMENT_OCID、G_MASTER_KEY_OCID、G_VAULT_OCID、G_REGION、G_CREDENTIALをアイテムとして設定します。
ボタンUPDATEのクリックに対応したプロセスを作成します。
識別の名前はシークレットのアップデート、タイプはAPIの呼び出しを選択します。パッケージSFM_SECRET_UTILに含まれるファンクションUPDATE_SECRETを呼び出します。
ファンクションの結果としてバージョン番号が返されます。アイテムP1_CURRENT_VERSION_NUMBERに設定します。通常、現在のバージョン番号より1つ大きい値になります。
パラメータp_keyの設定はCREATEと同じです。
パラメータp_secret_idとしてページ・アイテムP1_SECRET_IDの値が渡されます。ボタンCREATEを押したときに、ページ・アイテムP1_SECRET_IDには、作成されたシークレットのOCIDが設定されています。
ボタンGETのクリックに対応したプロセスを作成します。
識別の名前は暗号キーの取り出し、タイプはAPIの呼び出しを選択します。パッケージSFM_SECRET_UTILに含まれるファンクションGET_KEY_FROM_SECRETを呼び出します。
ファンクションの結果として暗号化キーがRAW型で返されます。パラメータのデータ型にVARCHAR2を選択しているため、暗黙でファンクションrawtohexが実行されてページ・アイテムP1_KEYに値が設定されます。
そのためP1_GENERATED_KEYとP1_KEYは同じ表示になります。
パラメータp_secret_nameにはP1_SECRET_NAMEが渡されます。
パラメータp_secret_idおよびp_version_numberは出力パラメータで、それぞれページ・アイテムP1_SECRET_ID、P1_CURRENT_VERSION_NUMBERに設定されます。
ボタンDELETEのクリックに対応したプロセスを作成します。
識別の名前はシークレットの削除、タイプはAPIの呼び出しを選択します。パッケージSFM_SECRET_UTILに含まれるファンクションDELETE_SECRETを呼び出します。
パラメータp_secret_idには、ページ・アイテムP1_SECRET_IDの値が渡されます。
パラメータP1_TIME_OF_DELETIONはシークレットが実際に削除される日付を指定します。シークレットは即時で削除することはできません。ページ・アイテムP1_TIME_OF_DELETIONも最低でも1日以上立ってから削除されるよう、日付ピッカーにて削除日時を選択します。
以上でアプリケーションは完成です。実行すると、記事の先頭のGIF動画のように、パッケージのコードの動作確認ができます。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/oci-secret-plsql-sdk.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完