データベースへのアクセスをアプリケーションに限定する

PostgreSQLBeginner
オンラインで実践に進む

はじめに

注文データベースは現在、アプリケーションの VPC 全体から接続を受け付け、アプリケーションはデータベース管理者のユーザーを使っています。ネットワークアクセスを絞り、注文の読み取りと追加はできても削除はできない専用ユーザーを作成します。

先にデータベース接続と SQL の実習を完了してください。この独立した環境には PostgreSQL プライマリ、3 件のサンプル注文、アプリケーションがあります。2 種類のアクセス境界をテストし、最後に練習用データベースを削除します。

認定試験との関連

この実習では、次の試験トピックに関する入門的な操作を練習します。

PostgreSQL のネットワーク接続元を制限する

このステップでは、VPC 全体からの広いインバウンドアクセスを、用意されたアプリケーションのセキュリティグループからのアクセスに置き換えます。

セキュリティグループは、どのネットワーク接続がデータベースに到達できるかを制御します。認証されたデータベースユーザーが実行できる SQL 操作は決定しません。その別の境界は次のステップで扱います。

用意された設定を読み込みます。

cd /home/labex/project
source database.env

データベースのグループと提供された 2 つのルール文書を確認します。

aws ec2 \
  describe-security-groups \
  --group-ids "$DB_SECURITY_GROUP_ID" \
  --query 'SecurityGroups[].{Group:GroupName,Ingress:IpPermissions}'
cat broad-rule.json
cat application-rule.json

既存のルールは 10.70.0.0/16 から TCP ポート 5432 へのアクセスを許可するため、同じ VPC のほかのクライアントも接続を試せます。置換ルールは UserIdGroupPairs でアプリケーショングループを参照します。グループ参照は、その接続元グループに関連付けられたリソースからの通信を許可し、グループのルールをコピーするものではありません。

広いルールを削除し、アプリケーションのルールを追加します。

aws ec2 \
  revoke-security-group-ingress \
  --group-id "$DB_SECURITY_GROUP_ID" \
  --ip-permissions file://broad-rule.json
aws ec2 \
  authorize-security-group-ingress \
  --group-id "$DB_SECURITY_GROUP_ID" \
  --ip-permissions file://application-rule.json

アプリケーションの接続元から新しい接続をテストします。

psql \
  "service=orders-db" \
  --command 'SELECT count(*) FROM orders;'

3 が表示されます。続いてアプリケーショングループの外にある提供済みのクライアントをテストします。

psql \
  "service=orders-db-external" \
  --command 'SELECT count(*) FROM orders;'

外部クライアントの接続は失敗します。データベースのパスワードは有効ですが、接続元のネットワークが許可されていません。用意された接続名はこの 2 つの接続元を選びます。実習のアクセスルールは Terminal や AWS View への管理接続を変更しません。

AWS View では、広い VPC CIDR の代わりにアプリケーショングループが PostgreSQL の接続元として表示されます。

アプリケーション用のデータベースユーザーを作成する

このステップでは、管理権限や削除権限を与えずに、注文の読み取りと挿入を許可します。

PostgreSQL の ロールは権限を持つことができます。LOGIN のあるロールはデータベースユーザーとして認証できます。データベースロールは AWS IAM の ID とは別です。IAM は RDS リソースの管理を認可し、これらの SQL 権限は PostgreSQL 内部の操作を制御します。

提供された非公開のアプリケーションパスワードファイルで orders_app を作成します。--set は psql 変数を定義し、:'app_password' は値を SQL 文字列として安全に引用します。

ネットワークアクセス、ログイン、SQL 権限は別々の境界

概念図:ネットワークアクセス、ログイン、SQL 権限は別々の境界。

psql \
  "service=orders-db" \
  --set=ON_ERROR_STOP=1 \
  --set=app_password="$(cat app-password.txt)" <<'SQL'
CREATE ROLE orders_app LOGIN PASSWORD :'app_password';
GRANT CONNECT ON DATABASE orders TO orders_app;
GRANT USAGE ON SCHEMA public TO orders_app;
GRANT SELECT, INSERT ON orders TO orders_app;
SQL

CONNECT はデータベース接続を許可します。USAGE はテーブルを含む名前空間であるスキーマを通じたアクセスを許可します。SELECT と INSERT は必要な注文操作だけを許可します。DELETE、UPDATE、CREATEDB、CREATEROLE、スーパーユーザー権限は与えていません。

非公開のパスワード値を psql に渡し、アプリケーションロールをテストします。

PGPASSWORD="$(cat app-password.txt)" psql \
  "service=orders-db-app" \
  --command 'SELECT current_user, count(*) FROM orders;'

ユーザー orders_app と 3 件の注文が表示されます。

存在しない注文 ID の削除を試します。誤って権限が広すぎた場合でも、この文は実際の注文を削除できません。

PGPASSWORD="$(cat app-password.txt)" psql \
  "service=orders-db-app" \
  --command 'DELETE FROM orders WHERE order_id = -1;'

permission denied for table orders が表示されます。これはネットワーク接続とデータベースへのログインが成功した後の失敗なので、先ほどの外部クライアントの接続失敗とは異なる境界を示します。

アプリケーションで制限されたユーザーを使用する

このステップでは、アプリケーションにロールを設定し、通常の注文操作が機能することを確認します。

2 つのエンドポイントフィールドを保持し、データベースユーザーとパスワードファイルの参照を変更します。

jq \
  '.user = "orders_app" | .password_file = "app-password.txt"' \
  app-config.json > app-config.new
mv app-config.new app-config.json

現在の注文一覧を読み取ります。

curl \
  --silent \
  --show-error \
  --fail \
  http://127.0.0.1:8080/application/orders | jq

元の 3 件の注文と user: orders_app が表示されます。

アプリケーションの HTTP エンドポイントから 1 件の注文を追加します。Content-Type はリクエスト本文が JSON であることを伝えます。

curl \
  --silent \
  --show-error \
  --fail \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{"order_id":104,"customer":"Kai","total":"5.00"}' \
  http://127.0.0.1:8080/application/orders

saved: true と注文 ID 104 が表示されます。もう一度一覧を読み取ります。

curl \
  --silent \
  --show-error \
  --fail \
  http://127.0.0.1:8080/application/orders | jq

Kai の 5.00 の注文を含む 4 件が表示されます。AWS View には同じ保存データと制限されたアプリケーションユーザーが表示されます。権限を絞っても必要な動作は維持されています。 アプリケーションが制限された orders_app データベースユーザーで元の注文と新しい注文を読み取っています。

練習用データベースを削除する

このステップでは、サンプル注文と SQL ユーザーを含む実習用データベースを削除します。

aws rds \
  delete-db-instance \
  --db-instance-identifier orders-db \
  --skip-final-snapshot \
  --query 'DBInstance.DBInstanceIdentifier'
aws rds \
  wait db-instance-deleted \
  --db-instance-identifier orders-db
aws rds \
  describe-db-instances \
  --query 'DBInstances[].DBInstanceIdentifier'

[] が表示されます。エンジンとロールは削除され、アプリの古いエンドポイントは接続できません。提供されたネットワークと制限済みのデータベースグループを保持してください。後片付けのためにアクセスを再び開放する必要はありません。

まとめ

PostgreSQL 接続をアプリケーションの接続元に限定し、アプリに専用の制限されたロールを割り当てました。外部クライアントは接続できず、アプリのロールは注文を削除できませんでしたが、通常の読み取りと挿入は機能しました。その後、ネットワークの制限を保持して練習用データベースを削除しました。

次の実習では、手動の RDS スナップショットを新しいデータベースに復元して、失われた注文を取り戻します。