[Oracle Cloud] DBaaS (VM) を使ってみる その4 - TDE表領域暗号がどんなふうに実装されてるか見てみる

# はじめに

OCIのDBaaSでは、デフォルトで表領域が暗号化されている。普通に使っている分にはあまり気にかける必要はないのだが、古いバックアップからリストアしたり、別のDBaaSインスタンスにバックアップをリストアしたりするときにいろいろ考慮してあげる必要がありそう。そういえば、今までTDEをまともに扱ったことがなかったので調べてみた。

※TDE表領域暗号化を前提として書きます。
※Vaultサービスは使ってません
※確認用に使用したDBaaSは、19c Standard Edition (シングル) です

# Oracle Databaseの透過的暗号化ってなんだっけ

そもそもTDE表領域暗号化とはなんぞやというところは、この記事 ( [Oracle Databaseの「透過的データ暗号化機能」とは何か (1/3)](https://www.atmarkit.co.jp/ait/articles/1710/24/news053.html)  ) で確認。

## 暗号化のアーキテクチャ
ポイントは、

- ディスク書き込みのタイミングで暗号化され、読み込みのタイミングで復号化される。
- メモリ上 ( SGAのバッファキャッシュ ) では暗号化されない。よって、キャッシュヒットする限り、暗号化のオーバヘッドは0。バッファキャッシュ上で暗号化されてなくていいの？的な疑問への回答は、[えっ、知らなかったでは済まされない。データベース・セキュリティの勘所](http://otndnld.oracle.co.jp/ondemand/technight/technight07.pdf)のp.37参照
- ディスクI/Oが発生した場合のみ、復号化/暗号化のオーバヘッドが発生するが、IntelのAES-NIやなどのハードウェアベースの暗号化アクセラレータを利用することで、オーバヘッドを最小化することができる。
- さらに、Advanced Compression機能と併用することができるので、表を圧縮してI/O量を減らすことで復号化オーバヘッドの総量を減らすことができる


DBaaSの実際の構成に当てはめた、暗号化のアーキテクチャは下の図のような感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456818/ce046afb-a88f-2326-a94a-c09a5e885a11.png)


登場人物は、キーストア、マスター鍵、表領域暗号化鍵。

- 実データは、表領域暗号化鍵によって暗号化される。[FAQ](https://www.oracle.com/database/technologies/faq-tde.html)によると表領域暗号化鍵は、表領域ごとに作成され、データファイルのヘッダに格納されるらしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456818/819d078c-439a-905b-2b01-943f12dfc7cc.png)

- 表領域暗号鍵は、マスター鍵によって暗号化される。
- マスター鍵は、キーストアと呼ばれるOSファイルの中に格納され、キーストアはパスワードで保護される。
- マスター鍵は、PDBごとに作成される。キーストアはCDB全体で共有すること(統一モード)もPDB単位で別のキーストアにすること(分離モード)もできる。DBaaSでは統一モードが使用される。詳しくは[Advanced Security Guide](https://docs.oracle.com/cd/F19136_01/asoag/managing-keystores-encryption-keys-in-united-mode.html#GUID-FA569DEC-6FF3-4CA1-86AA-D27F29EDCA3C)を参照

実データを暗号化する鍵をデータベースの中に格納し、その鍵をデータベースの外にある別の鍵で暗号化することで、暗号化ファイルと鍵が一緒に盗まれることを防いでいるのか。

## インスタンスがマスター鍵をオープンする方法

んで、インスタンスの起動時に、暗号化された表領域にアクセスするタイミングで、マスター鍵をゲットする必要がある。マスター鍵がどこにあるかどうやってわかるのだろうか。

DBaaSでは、sqlnet.oraのENCRYPTION_WALLET_LOCATIONでインスタンスに場所を教えてあげているみたい。

```CONSOLE
$ cat sqlnet.ora 
ENCRYPTION_WALLET_LOCATION=(SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=/opt/oracle/dcs/commonstore/wallets/tde/$ORACLE_UNQNAME)))
…
```
パスには、環境変数を含むことができる。DBaaSの設定では$ORACLE_UNQNAMEを指定している。

クラスタから起動された時も$ORACLE_UNQNAMEが設定されるようにクラスタリソースに登録されている

```CONSOLE
$ crsctl stat res ora.xxxxx.db -p -attr USR_ORA_ENV
NAME=ora.xxxx.db
USR_ORA_ENV=ORACLE_UNQNAME=xxxx TZ=Asia/Tokyo
```

キーストアはパスワードで保護されているため、鍵を開くときにはパスワードを入力してあげる必要がある。インスタンス起動時に、自動でキーストアを開くためには、自動ログインの設定をしてあげる必要がある。自動ログイン用の認証情報はキーストアと同ディレクトリのcwallet.sso(自動ログインウォレット)に格納される。ここまでを絵にまとめるとこんな感じか。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456818/ff231277-ed06-8c91-548b-034b1da8182f.png)


マスター鍵をオープンしたら、メモリ上にキャッシュするみたいです。なので、暗号化されている表領域上にアクセスした後に、キーストアにアクセスできなくなった場合(*1)でも稼働を続けることができるよう。
(*1 たとえば、APXインスタンス障害とかで一時的にACFSにアクセスできなくなったような場合)

```SQL
# 暗号化している表領域内のオブジェクトにアクセス後、
SQL>  select count(*) from test;

  COUNT(*)
----------
     20000

# キーストアが格納されているファイルシステムをアンマウントしても、
$ srvctl stop filesystem -d $(df -t acfs --output=source /opt/oracle/dcs/commonstore |sed '1d')
---

# 継続してアクセスすることができます。(インスタンスを再起動場合は、アクセスするとエラーになる)
SQL> insert into test select level,sysdate,'TEST'||level from dual connect by level <=10000;

10000 rows created.

SQL> commit;

Commit complete.

SQL> select count(*) from test;

  COUNT(*)
----------
     30000
```

## DBaaS作成時に実行されるコマンド

dcs-agentのログから、DB作成時に実行されているSQLが確認できました。

```CONSOLE
# cd /opt/oracle/dcs/log/
# grep ADMINISTER *.log
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT CREATE KEYSTORE '/opt/oracle/dcs/commonstore/wallets/tde/xxxx' identified by ******** ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT CREATE KEYSTORE '/opt/oracle/dcs/commonstore/wallets/tde/xxxx' identified by ******** ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN identified by ******** container=all ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN identified by ******** container=all ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT SET KEY using tag 'defaultTag' identified by ******** with backup using 'defaultTag' container=all ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT SET KEY using tag 'defaultTag' identified by ******** with backup using 'defaultTag' container=all ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT CREATE AUTO_LOGIN KEYSTORE FROM KEYSTORE '/opt/oracle/dcs/commonstore/wallets/tde/xxxx' identified by ******** ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT CREATE AUTO_LOGIN KEYSTORE FROM KEYSTORE '/opt/oracle/dcs/commonstore/wallets/tde/xxxx' identified by ******** ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT SET KEYSTORE CLOSE identified by ******** ;
dcs-agent-debug-2020-04-13.log:ADMINISTER KEY MANAGEMENT SET KEYSTORE CLOSE identified by ******** ;
```

1. ADMINISTER KEY MANAGEMENT CREATE KEYSTORE ～でキーストアを作成し、
2. ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN ～キーストアを開き、
3. ADMINISTER KEY MANAGEMENT SET KEY using tag ～でマスター鍵を作成し、
4. ADMINISTER KEY MANAGEMENT CREATE AUTO_LOGIN ～で自動ログイン設定をしています。

# Oracle Cloudのマニュアルに書いてあること

Oracle Cloudのマニュアル ( [Configuring a DB System](https://docs.cloud.oracle.com/en-us/iaas/Content/Database/Tasks/configuringDB.htm) ) には、以下のように記載されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456818/929301da-c4b5-a759-501a-bbfce8a375e5.png)

- デフォルトでは、表領域が暗号化されるように初期化パラメータで制御されているけど、したくない場合はしなくてもよい(変更してもよい)

- PDBを新規で作成した場合、dbcli update-tdekeyコマンドを発行してPDB用のマスターキーを作成する必要がある。

PDBごとにマスター鍵を作ってあげる必要があるので、PDB作成したらdbcliコマンド使ってマスター鍵新規に登録してねってことですね。

# デフォルトで暗号化される表領域

PDBを作成しようとして、create pluggable databaseコマンドを実行すると普通に成功するので、一見マスター鍵の登録が必要ないように思えます。でも、新規作成したPDBで表領域を作成すると以下のようにエラーになります。
※2020.8 追記：手動でcreate pluggable databaseを実行すると以下に記載したようになりますが、dbcli create-pdbからPDBを作成することで、TDEの鍵登録も管理系表領域の暗号化も実行されることを確認しました。


```SQL
SQL> create pluggable database pdb02 admin user pdbadmin identified by xxxxx;

Pluggable database created.

SQL> alter pluggable database pdb02 open;

Pluggable database altered.

SQL> alter session set container=pdb02;

Session altered.

SQL> create tablespace test datafile size 100m;
create tablespace test datafile size 100m
*
ERROR at line 1:
ORA-28361: master key not yet set
```

初期化パラメータはCLOUD_ONLYに設定されており、Cloud環境では新規作成する表領域はデフォルトで表領域暗号化されている設定になっているのですが、

```SQL
SQL> show parameter encrypt_new_tablespaces
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
encrypt_new_tablespaces              string      CLOUD_ONLY
```

管理系表領域は、デフォルトで暗号化されていませんでした。( CDB$ROOTも同じ。USERS表領域のみ暗号化されています ) 新規に作成する表領域に対してだけが暗号化されます。

```SQL
SQL> select TABLESPACE_NAME,ENCRYPTED from dba_tablespaces;

TABLESPACE_NAME                ENC
------------------------------ ---
SYSTEM                         NO
SYSAUX                         NO
UNDOTBS1                       NO
TEMP                           NO

# デフォルトの状態の場合、アラートログにも以下のような警告がでてます。

****************************************************************
PDB02(4):Database Characterset for PDB02 is AL32UTF8
PDB02(4):Warning: Oracle detects unencrypted tablespace SYSTEM (pdb 4) in the 
PDB02(4):         Oracle Cloud.  Oracle mandates all tablespaces to be
PDB02(4):         encrypted in the Cloud shortly after migration. Please refer
PDB02(4):         to the Oracle Database Advanced Security Administrator's 
PDB02(4):         Guide to encrypt this tablespace.
PDB02(4):Warning: Oracle detects unencrypted tablespace SYSAUX (pdb 4) in the 
PDB02(4):         Oracle Cloud.  Oracle mandates all tablespaces to be
PDB02(4):         encrypted in the Cloud shortly after migration. Please refer
PDB02(4):         to the Oracle Database Advanced Security Administrator's 
PDB02(4):         Guide to encrypt this tablespace.

```

そのため、PDBを新規で作成したらマニュアルにもあるとおりに、dbcli update-tdekeyコマンドを実行します

```
$ dbcli list-databases
ID                                       DB Name    DB Type  DB Version           CDB        Class    Shape    Storage    Status        DbHomeID                                
---------------------------------------- ---------- -------- -------------------- ---------- -------- -------- ---------- ------------ ----------------------------------------
c48f231a-2ee2-4b17-90e4-xxxxxxxxxxxx     ora19c     Si       19.6.0.0.200114      true       Oltp              ASM        Configured   718d4bd3-d712-4472-89a2-xxxxxxxxxxxx    


$ dbcli update-tdekey -i c48f231a-2ee2-4b17-90e4-xxxxxxxxxxxx -n pdb02 -p 
TDE Admin wallet password: ★DBaaS作成時に設定したsys/systemパスワードと一緒★

# dbcli list-jobs
…
a248e305-0c31-4ff6-8fe9-3ef33e3443f8     TDE update ora19c                                                           April 28, 2020 11:34:33 PM JST      Success  
```
ここまでを絵にすると、こんな感じかな。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456818/0f7a3ab8-ec51-bfcc-955b-e477e65b173f.png)

# 余談
ちなみに、encrypt_new_tablespaces初期化パラメータがCLOUD_ONLYの場合、クラウド環境の場合はデフォルトで暗号化されるのだけれども、インスタンスはどうやってこの環境がクラウドであると知るのだろう。

どうも、[インスタンス・メタデータ](https://docs.oracle.com/cd/E97706_01/Content/Compute/Tasks/gettingmetadata.htm)にアクセスできるかどうかで判断してるぽい

* インスタンスメタデータにアクセスできない場合

```CONSOLE
# iptables -I OUTPUT 1 -d 169.254.169.254/32 -j DROP
# su - oracle
$ srvctl stop database -db ora19c
$ srvctl start database -db ora19c
→ この間アラートログ見ていると、管理系表領域が暗号化されていないことに対する警告が出力されない
$ sqlplus / as sysdba
SQL> show parameter encrypt_new_tablespaces

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
encrypt_new_tablespaces              string      CLOUD_ONLY

SQL> alter session set container=pdb02;

Session altered.

SQL> create tablespace users datafile size 100m;

Tablespace created.

SQL>  select TABLESPACE_NAME,ENCRYPTED from dba_tablespaces;

TABLESPACE_NAME                ENC
------------------------------ ---
SYSTEM                         NO
SYSAUX                         NO
UNDOTBS1                       NO
TEMP                           NO
USERS                          NO　★暗号化されていない

SQL> exit

$ exit
```

* インスタンス・メタデータにアクセスできる場合

```
# iptables -D OUTPUT -d 169.254.169.254/32 -j DROP
# su - oracle
$ srvctl stop database -db ora19c
$ srvctl start database -db ora19c

$ sqlplus / as sysdba
SQL> alter session set container=pdb02;

Session altered.

SQL> create tablespace users2 datafile size 100m;

Tablespace created.

SQL> select TABLESPACE_NAME,ENCRYPTED from dba_tablespaces;

TABLESPACE_NAME                ENC
------------------------------ ---
SYSTEM                         NO
SYSAUX                         NO
UNDOTBS1                       NO
TEMP                           NO
USERS                          NO
USERS2                         YES ★ 暗号化されてる
```