ガード
ガードは、実行で許可する操作を制限します。--read-onlyは書き込みを拒否し、--allow-hostは指定したホスト以外への接続を拒否します。ガードはProbeを実行する人がコマンドラインか環境変数で指定するもので、ワークフローの側からは緩められません。コーディングエージェントが書いたワークフローのように、変更してはいけないシステムに対して実行するワークフローに使います。
probe --read-only --allow-host api.staging.example.com workflow.ymlガードを守るのはアクションです。これから何を送るかを知っているのはアクション自身なので、何を許可するかは各アクションが判定します。Probeは、アクションがガードを守ると申告している場合に限り、そのアクションをガードの下で実行します。
役割の分担
アクションが判定します。アクションには実行のガードが伝えられ、アクションは何かを送る前に、ガードが許可しない操作を理由とともに拒否します。たとえばhttpアクションは、--read-onlyの下ではPOSTを拒否し、--allow-hostが指定していないホストへのリダイレクトも拒否します。dbアクションは、書き込みうる文を拒否します。書き込みと読み取りをどう見分けるかはアクションごとに異なり、各アクションのページで説明しています。
Probeは、アクションが送る内容を見ません。Probeが行うのは次のことです。
- フラグと環境変数からガードを組み立て、ステップが実行する各アクションに伝える
- 各アクションがどの種類のガードを守ると申告しているかを読む。実行にかかっているガードの種類をすべて申告していないアクションのステップは、アクションを実行する前に拒否する。ただし
--allow-actionで指定したアクションは拒否しない - 拒否したステップを種類
refusedで失敗させる。終了ステータスは2で、再試行はしない embeddedアクションのジョブを同じガードの下で実行する。そのジョブのステップが拒否されれば、埋め込んだ側のステップも拒否する
ガードの申告
アクションは、守るガードの種類を申告します。
| 種類 | フラグ | アクションが拒否するもの |
|---|---|---|
read-only | --read-only | 書き込みうる操作 |
allow-host | --allow-host | 実行が許可していないホストへの接続 |
ステップがガードの下で実行されるのは、そのアクションが、実行にかかっているガードの種類をすべて申告している場合だけです。read-onlyだけを申告したアクションは、--read-onlyの下では実行され、--allow-hostの下では拒否されます。Probeが知らない種類は無視します。そのため、知らない種類だけを書いたアクションは、書いていない種類のガードの下では拒否されます。
組み込みアクションは、守る種類をそれぞれのパッケージで申告します。外部アクションは、action.ymlのguardで申告します。
name: greet
description: Say hello over HTTP
guard: [read-only, allow-host]
runs:
using: binary
url: https://github.com/<owner>/probe-greet/releases/download/v0.1.0/probe-greet_{os}_{arch}
checksums:
linux_amd64: <probe-greet_linux_amd64のSHA-256>Probeは最初のジョブを始める前に、ワークフローが使うすべての外部アクションのaction.ymlを読みます。実行ファイルをダウンロードするのは、そのアクションがガードの下で実行される場合だけです。拒否されるステップのために実行ファイルを取得することはありません。
申告の信頼
Probeは、アクションの申告をそのまま信じます。あるガードを守ると申告したアクションが実際に守るかどうかは、Probeには確かめられません。そのため、ガードが働くのは、ワークフローが使うアクションが申告どおりに動く範囲までです。守らないガードを申告した外部アクションを、ワークフローが使うこともありえます。ガードの下でワークフローを実行する前に、ワークフローが使う外部アクションを確認してください。リモートのアクションはコミットで固定されるので、実行されるのは確認したものと同じアクションです。パスで指定するローカルのアクションは固定されず、そのパスにあるファイルを実行します。そのファイルと、それを変更できる人を確認してください。
ガードはサンドボックスではありません。ガードは、各アクションがこれから行う操作を判定できる範囲で、ワークフローが意図しないものへ書き込んだり接続したりするのを防ぎます。WITH x AS (DELETE ...) SELECT ...のように、データベース自身が拒否した書き込みは、refusedではなくデータベースが報告するとおりに失敗します。アクションが何をするかにかかわらず実行の接続先を限りたい場合は、ネットワークポリシーを適用したコンテナの中など、ネットワークがその接続先だけを許可する場所でProbeを実行してください。
組み込みアクション
| アクション | --read-only | --allow-host |
|---|---|---|
http | GET、HEAD、OPTIONSだけを送る | URLのホストと、各リダイレクト先のホスト。ポートのないURLは、スキームの既定のポートとして扱う |
db | SELECT、SHOW、DESCRIBE、DESC、EXPLAIN、WITHのいずれかで始まり、末尾以外にセミコロンを含まない1文だけを、接続時に文を実行させないDSNで、読み取り専用トランザクション(SQLiteでは問い合わせしかできない接続)で実行する。文が書き込みを隠していても、データベース自身が拒否する | ドライバが接続しうる各サーバー。ドライバ自身によるDSNの解釈を使う。MySQLでは、go-sql-driverが接続するアドレス。PostgreSQLでは、lib/pqがDSNを解決した結果を使う。パラメータ、サービスファイル、PGHOST、PGHOSTADDR、PGPORTなども考慮し、ホストのリストはすべて確かめ、hostaddrがあればそのアドレスを確かめる。ポートがなければドライバの既定のポートとして扱う。SQLiteのファイルはホストを持たない |
embedded | ジョブをガードの下で実行する | ジョブをガードの下で実行する |
grpc | 手元にあるメソッドの定義(サーバーのリフレクションとprotoの.protoファイル)がすべてidempotency_level = NO_SIDE_EFFECTSと宣言しているメソッドだけを呼び出す。protoのないConnectの呼び出しには定義がないため拒否する | addrのホストとポート。ポートがなければ443として扱う。dns://server/のDNSサーバーも確かめ、ポートがなければ53として扱う。protocol: connectではURLのホストを、ポートがなければスキームの既定のポートとして扱う。Unixソケットのようにホストのない宛先は拒否する |
hello | 拒否するものがない | 接続するものがない |
組み込みのshell、ssh、smtp、imapは、コマンドやスクリプトが何をするかを判定できないため、ガードを申告していません。ページが何をするかを判定できない外部のbrowserアクションも、同じく申告していません。これらを使うステップは、--allow-actionで指定しない限り、ガードの下では拒否されます。指定した場合は、ガードがないときと同じように実行します。
probe --read-only --allow-action shell workflow.yml外部アクションでガードを守る
外部アクションにも、組み込みアクションと同じくCall.Guardで実行のガードが伝えられます。外部アクションは、何かを送る前に、ガードが許可しない操作に対してactionrpc.Refuse(...)を返すことでガードを守ります。
func (a *Action) RunStep(call actionrpc.Call) (map[string]any, map[string]any, error) {
req, err := parse(call.With)
if err != nil {
return nil, nil, err
}
if call.Guard.ReadOnly && req.writes() {
return nil, nil, actionrpc.Refuse("%s may write, and the run is read-only", req.Name)
}
if err := call.Guard.CheckHost(req.Host); err != nil {
return nil, nil, err // すでに拒否のエラー
}
// ...
}何が許可されているかはGuard.ReadOnly、Guard.AllowsHost、Guard.CheckHostで分かります。リダイレクト先も含め、アクションが接続するすべてのホストを確かめてください。そのうえで、アクションが守る種類だけをaction.ymlで申告します。