Oracle Database 23ai セキュリティ新機能: SQL Firewall

Oracle Database 23ai(旧23c)では、セキュリティの注目すべき新機能としてSQL Firewallがあります。この機能はOracle Databaseで予め許可したSQL以外はアクセスさせないというファイア・ウォールの機能に相当します。同様な機能としてAudit Vault and Database Firewallというネットワーク製品にSQLブロッキングの機能がありますが、その機能がDatabaseのみで使用できるネイティブ機能として移植されたといっても過言ではありません。

主な特徴としてはこちらのスライドをご覧ください。SpekerDeckに[資料](https://speakerdeck.com/oracle4engineer/oracle-database-23c-sekiyuriteixin-ji-neng)をアップロードしてあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562423/5bf2d58d-a00f-65bf-cb7b-8e31f98f8ee2.png)


本機能は、23aiのFree版でも試すことができますので、ここではSQL Firewallの機能を一通り確認する手順を紹介したいと思います。

## Oracle Database 23aiの準備
Oracle Database 23ai Freeを[公式サイト](https://www.oracle.com/database/free/)から入手し準備します。Qiitaには様々な方がインストール手順の記事を記載されておりますので、参考にすれば簡単にセットアップできると思います。もしくは、Oracle CloudのBaseDBでも最新の23aiのインスタンスが作成可能です。

## サンプル・スキーマの準備
SQL Firewallの動作検証では、SQLのポリシーの対象となるテーブルが必要なので、ここではHRのサンプル・スキーマをインストールします。以降は、Oracle Linux上に23aiをインストールした環境として説明します

```sql
#サンプルスキーマをGithubからダウンロード
wget https://github.com/oracle-samples/db-sample-schemas/archive/refs/tags/v23.2.zip

#解凍し実行(freepdb1にインストール)
@./db-sample-schemas-23.2/human_resources/hr_install.sql

#確認
sqlplus hr/hr@localhost:1521/freepdb1
select table_name from user_tables;
```

## SQL Firewallの基本設定
ここからはSQL Firewallの設定から動作確認までの一連の手順を説明していきます。管理者とアクセスするユーザーの２つの役割で行っていきますので、以下のようにターミナルを二つ用意してそれぞれで実行すると手順が分かりやすいかと思います
1. SQL Firewallの設定を行うsysユーザー用のターミナル -> A
2. SQLの動作を確認するHRユーザー用のターミナル-> B

```sql
#SQL Firewallの有効化(A)
exec DBMS_SQL_FIREWALL.ENABLE;

#HRユーザーのアクセスをキャプチャする定義を作成。作成後キャプチャが自動開始される(A)
BEGIN
  DBMS_SQL_FIREWALL.CREATE_CAPTURE (
    username         => 'HR',
    top_level_only   => TRUE,
    start_capture    => TRUE
  );
END;
/

#HRユーザでキャプチャさせるSQLを実行(B)
sqlplus hr/hr@ホスト名:1521/freepdb1  <--localhostではなくホスト名で
select * from employees;
select * from employees where employee_id=100;
select employee_id,first_name,salary from employees;

#キャプチャを停止(A)
EXEC DBMS_SQL_FIREWALL.STOP_CAPTURE('HR');

#キャプチャしたSQLを確認(A)
SELECT SQL_TEXT FROM DBA_SQL_FIREWALL_CAPTURE_LOGS WHERE USERNAME = 'HR';
SQL_TEXT
--------------------------------------------------------------------------------
SELECT * FROM EMPLOYEES
SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=:"SYS_B_0"
SELECT EMPLOYEE_ID,FIRST_NAME,SALARY FROM EMPLOYEES

#キャプチャしたSQLから許可リストを生成(A)
exec DBMS_SQL_FIREWALL.GENERATE_ALLOW_LIST('HR')

#生成した許可リストの中身を確認(A)
-SQL許可リスト
select ALLOWED_SQL_ID, SQL_TEXT from DBA_SQL_FIREWALL_ALLOWED_SQL where username = 'HR';
-IPアドレス・許可リスト
select * from  dba_sql_firewall_allowed_ip_addr  where username = 'HR';
-プログラム・許可リスト
select * from  dba_sql_firewall_allowed_os_prog  where username = 'HR';
-OSユーザー・許可リスト
select * from  dba_sql_firewall_allowed_os_user  where username = 'HR';

# 許可リストを有効化し、SQLのブロックを開始する(A)
BEGIN
DBMS_SQL_FIREWALL.ENABLE_ALLOW_LIST(
username=>'HR',
enforce=>DBMS_SQL_FIREWALL.ENFORCE_ALL,
block=>TRUE
);
END;
/

# HRでSQLを実行し動作を確認する(B)
-登録している以外のSQLを実行するとviolationになる
SQL> select first_name,last_name from employees;
ERROR at line 1:
ORA-47605: SQL Firewall violation

-Where句のValueが違ってもSQLの構文は同じなのでアクセスできる
select * from employees where employee_id=110;

#違反したSQLのログはディクショナリビューで確認(A)
select * from DBA_SQL_FIREWALL_VIOLATIONS;

#許可リストの停止(A)
exec DBMS_SQL_FIREWALL.DISABLE_ALLOW_LIST('HR');

#許可リストの削除(A)
exec DBMS_SQL_FIREWALL.DROP_ALLOW_LIST('HR');

```
手順から分かる通り、SQL Firewallが動作に必要なポリシーを作成するには、まずキャプチャするための<font color="RED">DBMS_SQL_FIREWALL.START_CAPTURE</font color>を実行しラーニングとしてSQLやIPアドレスなどのセッション情報を記録します。
次に、<font color="RED">DBMS_SQL_FIREWALL.GENERATE_ALLOW_LIST</font color>でキャプチャした情報を実際のFirewallのポリシーとなる許可リストとして生成し、最終的に<font color="RED">DBMS_SQL_FIREWALL.ENABLE_ALLOW_LIST</font color>で許可リストを有効化するという流れになります。
上記の手順では、取得したSQLをすべて許可リストとして反映していますが、実際には収集されたSQLから許可したいものだけを選別するチューニングが必要になることがほとんどです。

次は、作成した許可リストからSQLやIPアドレス等を許可するものだけに絞っていくチューニングを行っていきます。ここでのチューニングとは、許可リストに対して許可するSQLやIPアドレスを追加・削除するという意味合いになります。

## 許可リストから許可SQLを削除する
許可リスト内に許可したくないSQLがある場合には、<font color="RED">DBMS_SQL_FIREWALL.DELETE_ALLOWED_SQL</font color>を実行しリストから削除する

```sql
# 許可リストのSQLを確認する(A)
select ALLOWED_SQL_ID,SQL_TEXT from DBA_SQL_FIREWALL_ALLOWED_SQL;
ALLOWED_SQL_ID   SQL_TEXT
--------------   -----------------
1                SELECT * FROM EMPLOYEES
2                SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=:"SYS_B_0"
3                SELECT EMPLOYEE_ID,FIRST_NAME,SALARY FROM EMPLOYEES

# SALARYが含まれている3のSQLをAllowリストから削除(A)
BEGIN
  DBMS_SQL_FIREWALL.DELETE_ALLOWED_SQL (
    username         => 'HR',
    allowed_sql_id   => 3
   );
END;
/

# 削除したSQLを実行(B)
SELECT EMPLOYEE_ID,FIRST_NAME,SALARY FROM EMPLOYEES;
ERROR at line 1:
ORA-47605: SQL Firewall violation
```

## 許可リストに許可SQLを追加する
許可リストにSQLを追加する方法には、キャプチャログもしくは違反ログに該当のSQLが記録されている必要があります。キャプチャログから場合は、前述のキャプチャログを開始するプロセスを再度実行しログを取得し直します。
個別にSQLを追加する場合は、<font color="RED">DBMS_SQL_FIREWALL.APPEND_ALLOW_LIST_SINGLE_SQL</font color>でSQLシグネチャを指定して実行します。以下は、違反ログにあるSQLを許可リストに追加する手順です。

```sql

#違反ログから追加したいSQLのSQLシグネチャを確認する(A)
SELECT SQL_TEXT,SQL_SIGNATURE FROM DBA_SQL_FIREWALL_VIOLATIONS;

SQL_TEXT                                     SQL_SIGNATURE
-------------------------------------------  ------------------------------------
select first_name,last_name from employees;  35CA877B463698EC57122E8XXXXXXXXXXXX      

#SQLシグネチャを指定して許可リストに追加(A)
BEGIN
DBMS_SQL_FIREWALL.APPEND_ALLOW_LIST_SINGLE_SQL(
 username       => 'HR',
 sql_signature  => '35CA877B463698EC57122E8XXXXXXXXXXXX',
 current_user   => 'HR',
 top_level      => 'Y',
 source         => DBMS_SQL_FIREWALL.VIOLATION_LOG
);
END;
/
```

## 許可リストのIPアドレスを削除する
許可リストのIPアドレスから許可したくないIPアドレスがある場合には、<b>DBMS_SQL_FIREWALL.DELETE_ALLOWED_CONTEXT</b>を実行しリストから削除する

```sql
# 許可リストのIPアドレスを確認する(A)
select * from  dba_sql_firewall_allowed_ip_addr  where username = 'HR';
USERNAME        IP_ADDRESS
--------------  -----------------------
HR              10.0.0.58

begin
  dbms_sql_firewall.delete_allowed_context (
    username     => 'HR',
    context_type => dbms_sql_firewall.ip_address,
    value        => '10.0.0.58');
end;
/

# 許可されていないIPアドレス以外だとデータベースに接続できなくなる
sqlplus hr/hr@ホスト名:1521/freepdb1
ERROR:
ORA-47605: SQL Firewall violation
```

## 許可リストのIPアドレスを追加する
IPアドレス、OSユーザー、プログラムのセッション情報は、<b>DBMS_SQL_FIREWALL.ADD_ALLOWED_CONTEXT</b>で任意の値を直接許可リストに追加することができる
```sql
# 削除したIPアドレスを追加
begin
  dbms_sql_firewall.add_allowed_context (
    username     => 'HR',
    context_type => dbms_sql_firewall.ip_address,
    value        => '10.0.0.58');
end;
/
```

その他のSQL Firewallのプロシージャの使い方に関しては、最新の[ドキュメント](https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/dbms_sql_firewall.html#GUID-4A41615C-D3D5-4386-A299-5C3D08350E8C)を参考にして下さい。

GUIにて運用したい場合は、OCIのDataSafeの機能ではSQL Firewallの操作をサポートしています。詳しくは、[こちら](https://qiita.com/orakurara/items/87b5c24ff3cec31aae49#_reference-b854024cf3f0bae6fdcc)をが参考にすると良いかと思います。ただ、SQLでの設定をすべて網羅しているわけではないので、一部の設定は引き続きSQLを併用する必要があります。

SQL Firewallは、かなり厳密なSQLでのアクセス制御を行いますので、要件によっては他のアクセス制御の機能、Database Vault、VPD、Label Securityなどが使いやすい場合もありますので、併せて検討されると良いかと思います。