Oracle Database 12cからのHTTPSアクセスに必要な設定

Oracle Application Container Cloudで作ったREST APIに対して、Oracle Databaseから PL/SQLでUTL_HTTPパッケージを使ってアクセスするプログラムを書いたとき、HTTPSでアクセスするための設定がいろいろ必要だったので、備忘録です。ちなみに、Windows上のOracle DBで試しました。

#dbms_network_acl_adminの設定
Oracle DBからHTTPSリクエストを発行する場合、dbms_network_acl_adminで接続先に対してポート443でアクセスするための設定を行う必要があります。dbms_network_acl_adminは、11gと12cでは設定が少し違います。

今回、12cR2の PDBからアクセスしてみたので、その手順で書きます。

dbms_network_acl_adminの設定は、SQL*PlusからSYSDBAでログインしてPDB上で実行します。


```
connect sys/Password as sysdba
ALTER SESSION SET CONTAINER = orclpdb;


declare
    host_name varchar2(100) := '{アクセス先のDNS名}';
begin
 　dbms_network_acl_admin.append_host_ace (
      host       => host_name,
      lower_port => 443,
      upper_port => 443,
      ace        => xs$ace_type(privilege_list => xs$name_list('http'),
                                principal_name => '{DBユーザー名}',
                                principal_type => xs_acl.ptype_db));
  end;
/

declare
    host_name varchar2(100) := '{アクセス先のDNS名}';
begin
    dbms_network_acl_admin.append_host_ace (
      host       => host_name,
      lower_port => 443,
      upper_port => 443,
      ace        => xs$ace_type(privilege_list => xs$name_list('http_proxy'),
                                principal_name => '{DBユーザー名}',
                                principal_type => xs_acl.ptype_db));
  end;
/
```
#Oracle WalletにCA登録
Oracle DBから外部にHTTPSでアクセスするには、Oracle WalletにCA認証書を登録する必要があります。この手順は以下の記事を参考にさせていただきました。
http://cosol.jp/techdb/2015/12/https-using-utl-http-how-to-aviod-ora-29024.html

Windowsでは以下のようにやってみました。
###Oracle Walletの作成
orapki wallet createコマンドで、空のOracleウォレットを作成します。

```
set ORACLE_WALLET_PATH={Oracle Walletのディレクトリ}
set ORACLE_WALLET_PWD={Walletのパスワード}

call orapki wallet create -wallet %ORACLE_WALLET_PATH% -pwd %ORACLE_WALLET_PWD% -auto_login
```

###CA証明書の準備
テスト用に試したので、
http://curl.haxx.se/ca/cacert.pem
にアクセスして、ルートCA証明書cacert.pemをダウンロードしたものを使いました。
このままでは適切に Oracle Walletに組み込めないため、インポート可能な形式に変換します。
Windowsの場合、gawkをインストールして以下を実行します。

```
gawk " split_after == 1 { n++; split_after=0 } /-----END CERTIFICATE-----/ { split_after=1 } { print > \"cert\" n \".pem\" }" < cacert.pem
```
これにより多数のpemファイルが生成されます。
###CA証明書を追加
次に `orapki wallet add -trusted_cert -cert` コマンドでOracleウォレットにルートCA証明書を追加します。
バッチファイルで、たとえば以下を実行します。

```
for %%f in (*.pem) do (
  orapki wallet add -wallet %ORACLE_WALLET_PATH% -trusted_cert -cert %%f -pwd %ORACLE_WALLET_PWD%
  echo %%f
)
```
#PL/SQLからのHTTPSアクセス
### PL/SQLでのSSLの有効化
PL/SQLでHTTPSアクセスするには、まず
`UTL_HTTP.SET_WALLET('Oracle Walletのパス', 'Walletのパスワード')`　を行います。
### 実際にアクセスする
以下は、dbms_network_acl_adminに登録したDBユーザーから登録したアクセス先に対し、HTTPSでPUTメソッドを使ってJSONを送る例です。DBのキャラクタセットはAL32UTF8を使っています。

```
connect c##adm/test
ALTER SESSION SET CONTAINER = orclpdb;

DECLARE
    v_walletpath VARCHAR2(4000) := 'file:d:\app\test\wallet'; -- 「file:」を入れるのを忘れずに
    v_walletpwd VARCHAR2(100) := 'WalletPasswd123';

    v_req   UTL_HTTP.REQ;
    v_resp  UTL_HTTP.RESP;
    v_json  VARCHAR2(4000) := '{ "id" : "1234", "name" : "テスト"}';
    v_buf VARCHAR2(4000); 

BEGIN
   -- Walletを指定
   UTL_HTTP.SET_WALLET(v_walletpath, v_walletpwd); 

   -- HTTPリクエスト
   v_req := UTL_HTTP.BEGIN_REQUEST('https://{登録したホスト名}/cache/1234', 'PUT', UTL_HTTP.HTTP_VERSION_1_1);

    UTL_HTTP.SET_HEADER(v_req, 'Content-Type', 'application/json; charset=utf-8');
    UTL_HTTP.SET_HEADER(v_req, 'Content-Length', LENGTHB(v_json)); -- LENGTHBにする

    UTL_HTTP.WRITE_RAW(v_req, UTL_RAW.CAST_TO_RAW(v_json)); -- JSONは文字列ではなくRAWにすること
    
    -- レスポンスを取得
    v_resp := UTL_HTTP.GET_RESPONSE(v_req);
    DBMS_OUTPUT.PUT_LINE('Status Code: ' || TO_CHAR(v_resp.STATUS_CODE));
    DBMS_OUTPUT.PUT_LINE('Message: ' || v_resp.REASON_PHRASE);

    BEGIN
      LOOP
        UTL_HTTP.READ_LINE(v_resp, v_buf);
        DBMS_OUTPUT.PUT_LINE(v_buf);
      end LOOP;
      UTL_HTTP.END_RESPONSE(v_resp);
    EXCEPTION
      WHEN UTL_HTTP.END_OF_BODY 
      THEN
        UTL_HTTP.END_RESPONSE(v_resp);
    END;

END;
/
```
- Walletのパスには「file:」を入れること。
- Content-Lengthにセットする送信データのサイズは、LENGTH（文字数）ではなくLENGTHB（バイト数）を使うこと。あまりに久々にPL/SQL使ったので、忘れてて文字化けしました...。
- あと、送信データはRAW型に変換してから送るようにします。

これを実行した後、以下のようにアクセスすると、登録したデータを取得できました。

```
SQL> SELECT UTL_HTTP.REQUEST('https://{ホスト名}/cache/1234') FROM DUAL;

UTL_HTTP.REQUEST('HTTPS://{ホスト名}/CACHE/
--------------------------------------------------------------------------------
{"id":"1234","name":"テスト"}
```