はじめに
MarketLedger は、小規模なマーケットの出店者向け精算レポートを必要としています。レポートが読むべきなのは昨日の集計であり、顧客のメールアドレスや銀行参照情報ではありません。また、注文を書き込むべきでもありません。現在のアカウント作成ポリシーは、この仕事に必要な範囲より広くなっています。
そのポリシーを狭め、OpenBao を通じて実際の一時 PostgreSQL ログインを取得し、付属のクエリで完全なタブ区切りレポートを保存します。基本的なターミナル操作とパスの知識が必要です。SQL 権限、資格情報ファイル、関連するコマンドオプションは使う前に説明します。Python プログラミングやクラウドアカウントは不要です。
店舗と必要なレポートを確認する
このステップでは、権限を変更する前に、稼働中の業務と通常のレポートを確認します。この実習では一貫して Terminal を使います。cd はディレクトリを変更します。絶対パスはどこからでも使えます。
cd /home/labex/project/market-report
PostgreSQL は準備済みのデータベースです。OpenBao は、そのデータベースへ期間限定の新しいログインの作成を依頼できる、準備済みのシークレットサービスです。どちらも実習のコンテナ内で動く実際のプロセスです。ホスティングされたデータベースや、本番用のシークレットサービス環境ではありません。
準備済みの shop.py は、稼働中の店舗サービス用の小さなクライアントです。実行するために Python を書く必要はありません。list は今日の注文を表示します。
python3 shop.py list
最初は空の一覧 [] になるはずです。通常の注文を 1 件作成します。--order は注文を、--vendor は出店者を識別し、--cents は整数のセント数で金額を指定します。
python3 shop.py place --order O201 --vendor V101 --cents 600
合成の取引日 2026-10-29 に対して、支払い済み注文が保存されるはずです。コマンドを繰り返すと、サービスは ID の重複を拒否します。削除や上書きをせず、list で既存の注文を確認してください。
精算の対象は前日 2026-10-28 です。今日の新しい注文によって、昨日の合計が変わってはいけません。cat はテキストファイルを表示します。準備済みの正常なレポート例を確認します。
cat demo.tsv
出店者 3 行が表示されるはずです。
vendor_id vendor_name paid_orders total_cents
V101 Birch Pantry 2 4700
V102 Elm Bakery 1 2000
V103 Maple Kitchen 1 2700
区切りはタブ文字なので、拡張子は .tsv です。支払い済み注文は 4 件で、合計は 9400 セントです。キャンセルされた Elm の注文は除外されます。このデモは別の準備済み読み取りアカウントで取得されています。コピーしても、新しいデータベース ID がレポートを実行できる証拠にはなりません。
ビューは、選ばれた結果を公開する名前付きのデータベースクエリです。reporting.daily_settlement は出店者名と支払い済み注文の合計を結合し、顧客メールアドレスや銀行参照情報を除外します。元のレコードは、非公開の public.orders テーブルと public.vendors テーブルに残ります。ここで reporting と public は、データベースオブジェクトをまとめるスキーマ名です。public という名前でも、すべてのロールがアクセスすべきという意味ではありません。
role.sql の編集、OpenBao ロール settlement-report の登録、そのロールの資格情報取得、レポートと資格情報ファイルの作成は可能です。店舗、元のデータベースレコード、ほかのレポートロール、付属クエリ、環境ファイル、オペレータートークン、準備済みランタイムは変更しないでください。データベース管理者パスワードや OpenBao の root トークンは不要です。
通常の No such file or directory エラーが出た場合は、上の絶対パスの cd で戻り、ls で実際のファイル名を確認してから再実行します。綴り間違いの修正に環境をリセットしないでください。
アカウント作成ポリシーを精算ビューだけに制限する
このステップでは、レポートに不要な権限を削除します。Terminal の /home/labex/project/market-report から始めてください。
現在のアカウント作成文を読みます。
cat role.sql
OpenBao は資格情報の発行時に 3 つのプレースホルダーを置換します。{{name}} は新しいデータベースユーザー名、{{password}} は生成されたパスワード、{{expiration}} はアカウントの有効期限になります。これらを保持し、自分で固定したユーザー名やパスワードに置き換えないでください。
CREATE ROLE ... WITH LOGIN は PostgreSQL ログインを作成します。VALID UNTIL は、そのパスワードで新しい接続を認証できる期間を制限します。すでに開かれたすべての接続を終了するものではありません。明示的な失効と自然なリース期限切れは、次のガイド付き実習で学びます。
GRANT は権限を付与します。USAGE ON SCHEMA reporting は、そのスキーマ内のオブジェクトの参照を許可します。SELECT ON reporting.daily_settlement は、その 1 つのビューの読み取りを許可します。現在の public に対する余分な権限は、非公開テーブルの読み取りと注文の変更を可能にします。どちらもこのレポートには不要です。
最後の ALTER ROLE ... SET log_statement = 'all' 行は、新たに作られる各レポートログインのネイティブ SQL 監査を有効にします。この行は保持してください。その ID のクエリは、実行者に帰属させられる必要があります。パスワードを含む管理用アカウント作成文は、このログイン単位の設定では記録されません。準備済みの OpenBao 監査ログも、シークレット値をハッシュ化します。
次の、より限定した文でファイルを置き換えます。cat > role.sql は、渡されたテキストをそのファイルへリダイレクトします。<<'ROLESQL' はヒアドキュメントの開始です。引用した区切りにより、ROLESQL だけの行までのテキストがそのまま保持されます。
cat > role.sql <<'ROLESQL'
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT USAGE ON SCHEMA reporting TO "{{name}}";
GRANT SELECT ON reporting.daily_settlement TO "{{name}}";
ALTER ROLE "{{name}}" SET log_statement = 'all';
ROLESQL
読み返します。
cat role.sql
このロールには、生成されるログイン、期限のあるパスワード、スキーマ参照権限、1 つのビューの読み取り権限があります。元テーブルへのアクセス、書き込み権限、管理者属性はありません。ここでビュー権限が役立つのは、付属のビューが意図的に非公開データを除外しているためです。非公開フィールドを公開するビューでは、同じ境界にはなりません。
終了区切りを省略してターミナルが入力を待つ場合は、ROLESQL だけを 1 行に入力してください。保存したファイルが不完全なら、ブロック全体を繰り返します。これで変わるのはポリシーファイルだけで、稼働中のデータベースはまだ変わりません。
制限された OpenBao オペレーターでポリシーを登録する
このステップでは、シークレットサービスの管理者 ID を使わずにアカウントポリシーを登録します。Terminal を使い続けてください。
準備済み環境ファイルを、読み込む前に確認します。
cat env.sh
source env.sh は、これらの代入を現在のターミナルのシェルで実行します。export は子コマンドでも値を使えるようにします。このファイルは、シェルのコマンド検索一覧である PATH に準備済みの bao ツールを追加し、ローカルの OpenBao と PostgreSQL のアドレスを設定します。$(cat ...) は、非公開ファイルから準備済みオペレータートークンを読みます。トークン自体は表示しません。これらの設定は、このターミナルセッションに属します。新しいターミナルでは再度読み込む必要があります。
source env.sh
OpenBao のオペレータートークンは、シークレットサービスへの要求を認可します。PostgreSQL のユーザー名やパスワードではありません。このトークンでは、settlement-report の管理と資格情報取得、そのロールの対象リースの失効が可能です。データベース管理接続の読み取りや、独立した在庫レポートの管理はできません。
bao write は、指定した OpenBao パスに設定を書き込みます。db_name=market は、準備済みデータベース接続を選びます。creation_statements=@role.sql は、SQL ファイルをそのフィールド値として読みます。@ は OpenBao のファイル入力構文です。default_ttl=10m は新しいリースの既定期間を 10 分にし、max_ttl=15m は寿命設定の上限を 15 分にします。TTL は有効期間を意味します。レポートには、数時間や無期限のアカウントではなく、この短い制限を使います。
bao write database/roles/settlement-report db_name=market creation_statements=@role.sql default_ttl=10m max_ttl=15m
database/roles/settlement-report にデータが書き込まれた確認が表示されるはずです。これで作成ポリシーが変わりますが、データベースログインはまだ発行されていません。ポリシー変更は今後のアカウントに適用されます。すでに発行されたログインから権限を削除するものではありません。 登録済みポリシーを変更したら、新しい資格情報を取得し、それでレポートを作り直してください。完了チェックでは、レポートの ID が最後のポリシー登録後に発行されている必要があります。先に発行した広すぎる権限のレポートログインを有効なまま残さないでください。次のステップの条件付き復旧手順では、資格情報ファイルを置き換える前に、そのリースだけを終了する方法を示します。
ポリシーのセルフチェックは、現在の登録済みポリシーを読み、その SQL を使い捨ての PostgreSQL 検証環境で試します。リースの発行、稼働中のデータベースの変更、実際のレポート実行は行いません。sudo は、この準備済みチェックが保護された設定を確認し、隔離した通常アカウントで実行するために使います。通常の OpenBao とデータベースのコマンドは、制限された ID を使います。
sudo python3 /opt/labex-report/verify.py policy
余分な権限や有効期限の欠落が報告されたら、role.sql を修正して再登録してください。プレースホルダーと監査句は保持します。明示された境界を実現する同等の権限文も受け入れられます。特定のエディターや SQL の綴りを要求するチェックではありません。
一時ログインを取得して実際のレポートを実行する
このステップでは、新しく発行したデータベース ID で精算ファイルを作成します。env.sh を読み込んだ、同じ Terminal セッションから始めてください。
資格情報の応答には、3 つの異なるものが含まれます。OpenBao 内の今回の発行を識別するリース ID、生成された PostgreSQL のユーザー名、そのパスワードです。前のステップのオペレータートークンは発行を認可するもので、これらのデータベース資格情報の 1 つではありません。
資格情報ファイルは非公開に保ちます。umask 077 は、新規作成するファイルを自分のアカウントだけが読み書きできるようにします。既存のファイルは変更しません。
umask 077
bao read はロールの資格情報を要求します。-format=json は構造化された JSON 出力を選びます。> はその出力を lease.json に保存し、同名の既存ファイルを置き換えます。このコマンドは実際に新しい PostgreSQL ログインを作り、リースの時計を開始します。同じリースを見るだけのために繰り返さないでください。
bao read -format=json database/creds/settlement-report > lease.json
jq は JSON のフィールドを選びます。-r は JSON の引用符なしで文字列を表示します。秘密ではない ID、リース ID、リース期間を読み、パスワードは表示しないでください。
jq '.lease_id, .lease_duration, .data.username' lease.json
database/creds/settlement-report/... のリース ID、期間 600 秒、生成されたユーザー名が表示されるはずです。ランダムな接尾辞は毎回異なります。
PostgreSQL クライアント psql は、環境から PGUSER と PGPASSWORD を読みます。引用した $(...) 置換で、今回発行された値を設定します。値はこのターミナルセッションとその子コマンドで使われます。
export PGUSER="$(jq -r .data.username lease.json)"
export PGPASSWORD="$(jq -r .data.password lease.json)"
psql -X は個人の psql 起動設定を無視し、-v ON_ERROR_STOP=1 は SQL エラーで停止し、-c は SQL コマンドを 1 つ指定します。SELECT current_user は、この接続が実際にどの ID を使っているかを PostgreSQL に問い合わせます。
psql -X -v ON_ERROR_STOP=1 -c 'SELECT current_user;'
店舗の書き込みアカウント、デモ用読み取りアカウント、データベース管理者ではなく、lease.json と同じ生成済みユーザー名になるはずです。
準備済みの report.sql は、公開レポートの 4 列を選択し、出店者 ID 順に並べ、ヘッダー付きタブ区切りデータとして標準出力へコピーします。クエリは変更しないでください。-f はそのファイルから SQL を読み、> はレポート出力だけを保存します。
psql -X -v ON_ERROR_STOP=1 -f report.sql > settlement.tsv
実際の結果を読みます。
cat settlement.tsv
出店者 3 行は、先ほどの業務例と一致する必要があります。Birch は 2 / 4700、Elm は 1 / 2000、Maple は 1 / 2700 です。顧客メールアドレスや銀行参照情報は含まれてはいけません。今日の O201 注文は昨日の精算対象外なので、合計は 9400 セントのままです。
後の接続が password authentication failed で失敗したら、読んでいる間に 10 分のリースが切れた可能性があります。これは資格情報の失敗であり、テーブルクエリの拒否ではありません。資格情報発行コマンドを 1 回再実行し、新しいファイルから PGUSER と PGPASSWORD の両方を再読込して、ID とレポートのコマンドを再実行します。新しいユーザー名になるのが正常です。記録されたユーザー名を編集したり、有効期限を手動で延長したりしないでください。
資格情報をすでに発行した後でポリシーを変えた場合は、別の復旧が必要です。前のアカウントには元の権限が残ります。その lease.json を置き換える前に、bao lease revoke "$(jq -r .lease_id lease.json)" を実行してください。引用した置換が渡すのは、その保存されたリース ID そのものです。データベースユーザー名やリースの接頭辞ではありません。在庫レポートやリース接頭辞全体は決して失効させないでください。前の発行分を終了したら、新しい資格情報を要求し、データベース変数を両方再読込して、上記のコマンドでレポートを作り直します。これにより、新しい制限済みログインの横で、古い広すぎる権限のログインが使えるまま残ることを防ぎます。
非公開データと書き込みが利用できないことを確認する
このステップでは、店舗と元のデータを保ちながら、アカウントの境界を試します。発行された PostgreSQL ID を設定した、同じ Terminal を使います。
次の読み取りは失敗するはずです。SELECT * は全列を要求します。このアカウントが元の注文を読む前に、ポリシーで止める必要があります。
psql -X -v ON_ERROR_STOP=1 -c 'SELECT * FROM public.orders;'
非公開スキーマまたはテーブルに対する PostgreSQL の permission denied エラーになるはずです。$? は直前のコマンドの終了ステータスで、echo がそれを表示します。すぐに読みます。
echo $?
SQL クエリが拒否されたことを示す 1 になるはずです。ステータス 2 の接続失敗では、この権限境界を示せません。リースが切れた場合は、前のステップの再発行と再読込の復旧手順を使ってください。
次に、データに害のない書き込みを試します。UPDATE ... WHERE false はどの行にも一致しないので、アカウントに誤って過剰な権限が付いていても注文は変更されません。それでも、権限不足で拒否されなければいけません。
psql -X -v ON_ERROR_STOP=1 -c 'UPDATE public.orders SET amount_cents = 0 WHERE false;'
echo $?
こちらも権限エラーとステータス 1 になるはずです。アカウントは目的のビューを読めますが、元の非公開データを読んだり注文を書き込んだりはできません。
準備済みの店舗クライアントは、レポート資格情報ではなく、店舗サービス独自の書き込み ID を使います。作成した注文がまだあることを確認します。
python3 shop.py list
読み取り専用の完了チェックを実行します。最初に既存の OpenBao のネイティブ発行記録と PostgreSQL のクエリ証拠を読み、次に実際のレポートと保持された業務データを確認します。不足するログインを作成したり、代わりにレポートを生成したりはしません。
sudo python3 /opt/labex-report/verify.py result
これで、実際に動的発行された制限付きデータベース ID による、完全な精算レポートができました。次の独立した実習は、正しい権限ポリシーから始まり、アクセスの終了と新規接続の失敗の観察に焦点を当てます。
まとめ
制限されたアカウント作成ポリシーを登録し、権限を制限した OpenBao オペレーターを通じて新しい PostgreSQL ログインを取得し、その ID で実際の精算レポートを保存しました。ビューは必要な業務集計を公開し、非公開テーブル、書き込み、管理者権限はレポートの範囲外に保たれています。
オペレータートークン、リース ID、データベース資格情報には、それぞれ異なる役割があります。この演習も、準備済みの信頼された管理コンポーネントに依存しています。一時アカウントを使っても、この初期設定への信頼はなくなりません。次の実習では、対象を正確に指定したリース失効、自然な期限切れ、新規発行による復旧を扱います。


