メインコンテンツまでスキップ

ワークフローのマニフェスト

ワークフローは YAML フォーマットで記述された マニフェスト(Manifest) により定義されます:

このマニフェストを記述し、Shisho Cloud 上のワークフローを作成することで、監査・検査やその後の対応をコードとして定義できます。

大構造​

ワークフローのマニフェストは、以下の 5 つから構成されます:

  • version: 定数 (0.1.0)
  • id: ワークフローに対する一意な ID
  • name: ワークフローのわかりやすい表示名
  • triggers: ワークフローの実行タイミングの設定
  • jobs: ワークフロー内で行う監査・監査やその後の対応の設定

つまり、ワークフローのマニフェストは、以下のようなフォーマットに従う YAML ファイルだということです:

version: 0.1.0
id: "example-workflow"
name: "Example"

triggers:
# ...

jobs:
# ....

トリガー​

ワークフローは複数の トリガー を triggers ブロックとして定義します:

version: 0.1.0
id: "example-workflow"

# 複数のトリガーの定義
triggers:
# ...

1 つ 1 つのトリガーは、そのワークフローがいつ実行されるべきかを定義するものです。「一定時間おきにトリガーを実行する」といった時間に基づく定義や、「他サービスからの Webhook メッセージを受け取ったら実行する」というようなイベントに基づく定義が利用できます。

より具体的には、以下のようなトリガーを利用できます:

  • schedule トリガー
  • github トリガー

schedule トリガー​

schedule トリガーは、cron フォーマットを用いてワークフローの実行タイミングを定義するためのトリガーです。 schedule トリガーには cron フォーマットでの実行タイミング設定を 1 つ以上含めることができ、少なくとも 1 つの cron 設定にマッチする時刻 にワークフローが実行されるようになります。

例えば、以下のようなトリガーを持つワークフローは 10 分おきに実行されます:

version: 0.1.0
id: "example-workflow"
triggers:
schedule:
# 10 分おきに実行
- cron: "*/10 * * * *"
# ...

以下は複数の cron 設定を含む schedule トリガーの例です:

version: 0.1.0
id: "example-workflow"
triggers:
schedule:
- cron: "*/10 * * * *"
- cron: "*/30 * * * *"
# ...
note

複数の cron 設定が、同一時刻でのワークフローの実行を指示する場合であっても、ワークフローの実行は 少なくとも 1 回 しか保証されません。

github トリガー​

github トリガーは、GitHub から Shisho Cloud に対して届けられた Webhook メッセージを用いてワークフローの実行タイミングを定義するためのトリガーです。 github トリガーには、Webhook メッセージに基づくワークフローの実行条件を、1 つまたは複数個設定できます。 そのような設定のもと、GitHub から届いた Webhook メッセージが少なくとも 1 つの設定にマッチする場合 にワークフローが実行されるようになります。

例えば、以下のようなトリガーを持つワークフローは、各リポジトリのデフォルトブランチに対する push イベントが発生した際に実行されます:

version: 0.1.0
id: "example-workflow"
triggers:
github:
- push:
branches:
- :default_branch

以下は複数の条件を含む github トリガーの例です:

version: 0.1.0
id: "example-workflow"
triggers:
github:
- push:
branches:
- :default_branch
- push:
branches:
- staging

GitHub が Webhook メッセージを通して配信するイベント情報のうち、Shisho Cloud は現在、以下のようなイベントを処理できます:

push イベントの処理​

push イベントに基づきワークフローの実行を制御する場合、以下のようなフィルタをご利用いただけます:

  • organization: push 先のリポジトリが所属する GitHub Organization 名が、指定した文字列と一致する
  • name: push 先のリポジトリ名が、指定した文字列と一致する
  • push.branches: 指定された パターン のうち、少なくとも 1 つに push 先のブランチ名が一致する
    • パターンと push 先ブランチ名を比較する際には、パターン中の以下の文字/文字列が特殊な挙動をとる
      • *: / を除く 1 文字以上の文字列にマッチ
      • **: 1 文字以上の文字列にマッチ
      • 3 つ以上 * が続く場合:
        • * が奇数個: 最後が / でない 1 文字以上の文字列
        • * が偶数個: 1 文字以上の文字列にマッチ
    • パターンが以下のどれかに完全一致する場合、一致の検証は特別な挙動をとる
      • :default_branch: パターンと push 先ブランチ名を比較する代わりに、当該リポジトリのデフォルトブランチ名と push 先ブランチ名を比較する
  • push.branches_ignore: 指定された パターン のどれとも push 先のブランチ名が一致しない
    • パターン中では branches 同等の特殊文字・特殊な識別子が利用可能
  • push.tags: 指定された パターン のうち少なくとも 1 つに push されたタグ名が一致する
    • パターンと push されたタグ名を比較する際には、パターン中の以下の文字/文字列が特殊な挙動をとる
      • *: / を除く 1 文字以上の文字列にマッチ
      • **: 1 文字以上の文字列にマッチ
      • 3 つ以上 * が続く場合:
        • * が奇数個: 最後が / でない 1 文字以上の文字列
        • * が偶数個: 1 文字以上の文字列にマッチ
  • push.tags_ignore: 指定された パターン のどれとも push されたタグ名が一致しない
    • パターン中では tags 同等の特殊文字が利用可能

上述のフィルタが複数指定された場合は、それらは AND 条件として扱われます。つまり、全てのフィルタにマッチする場合のみ、ワークフローが実行されるようになります。

以下は上述のフィルタの利用例です:

triggers:
github:
# GitHub からの push イベントに基づきワークフローを実行する。ただし:
# - octcat/Hello-World に対する push イベントのみに反応する
# - staging ブランチ、または feat/ から始まるブランチにのみ反応する
- organization: "octcat"
name: "Hello-World"
push:
branches:
- staging
- feat/**

pull_request イベントの処理​

pull_request イベントに基づきワークフローの実行を制御する場合、以下のようなフィルタをご利用いただけます:

  • organization: push 先のリポジトリが所属する GitHub Organization 名が、指定した文字列と一致する
  • name: push 先のリポジトリ名が、指定した文字列と一致する
  • pull_request.types: pull_request イベントの種別が、指定したリストの中に含まれる
  • pull_request.branches: 指定された パターン のうち、少なくとも 1 つに pull_request のベースブランチ名が一致する
    • パターンと pull_request の ベースブランチ名を比較する際には、パターン中の以下の文字/文字列が特殊な挙動をとる
      • *: / を除く 1 文字以上の文字列にマッチ
      • **: 1 文字以上の文字列にマッチ
    • パターンが以下のどれかに完全一致する場合、一致の検証は特別な挙動をとる
      • :default_branch: パターンと pull_request のベースブランチ名を比較する代わりに、当該リポジトリのデフォルトブランチ名と pull_request のベースブランチ名を比較する
  • PULL_REQUEST.branches_ignore: 指定された パターン のどれとも pull_request のベースブランチ名が一致しない
    • パターン中では branches 同等の特殊文字・特殊な識別子が利用可能

上述のフィルタが複数指定された場合は、それらは AND 条件として扱われます。つまり、全てのフィルタにマッチする場合のみ、ワークフローが実行されるようになります。

以下は上述のフィルタの利用例です:

triggers:
github:
# GitHub からの pull_request イベントに基づきワークフローを実行する。ただし:
# - octcat/Hello-World に対する pull_request イベントのみに反応する
# - synchronize, opened, reopened イベントにのみ反応する
- organization: "octcat"
name: "Hello-World"
pull_request:
types:
- synchronize
- opened
- reopened

ジョブ​

ワークフローは複数の ジョブ(Job) を定義します:

version: 0.1.0
id: "example-workflow"

# 複数のジョブの定義
jobs:
# ....

一つ一つのジョブは以下を含んでいます:

  • id: ジョブのワークフロー内で一意な ID
  • name: ジョブのわかりやすい表示名
  • decide: どんなデータを・どのように検査/監査するか
  • notify (optional): ジョブ実行の各フェーズにおいて、どのように通知するか

decide ブロック​

どんなデータを・どのように検査/監査するかを定義するために、ジョブは必ず一つの decide ブロックを含みます:

version: 0.1.0
id: "example-workflow"

jobs:
- id: "..."
name: "..."

# decide ブロック
decide:
# ...

構造​

「どんなデータを・どのように検査/監査するか」のうち、「どんなデータを」の部分を定義するものとして、decide ブロックには必ず以下が含まれます:

  • input ブロック

「どのように検査/監査するか」の部分を定義するものとしては、を必ず以下の少なくとも一方が含まれます:

  • rego ブロック
  • uses ブロック

図示すると、検査/監査の対象(画像左部分)と、検査/監査の方法(画像中央部分)を規定するのがこれら 2 つの要素です:

input ブロック​

input ブロックは、GraphQL クエリの形で、検査/監査対象のデータを定義するものです。 Shisho Cloud は、このようなクエリの定義に従って各ジョブごとにデータを取得し、その取得したデータをポリシーコードに入力します。

jobs:
- decide:
input:
# GraphQL クエリ
schema: |
query {
...
}

GraphQL クエリの仕様確認や記述のためには、簡易的な Playground が利用できます。

以下に「連携済みの GitHub Organization やリポジトリ群」を取得するための GraphQL クエリの例を示します:

query {
github {
organizations {
login
metadata {
id
}

repositories {
metadata {
id
}
name
}
}
}
}

rego ブロック​

rego ブロックはポリシー言語 Rego を用いて、「どのように検査/監査するか」を規定するためのものです。 ポリシーは、ジョブ中の GraphQL クエリの定義通りに Shisho Cloud により取得されたデータを受け取ります。 そして、複数の Decision と呼ばれるデータを出力することで、Shisho Cloud に対して検査/監査の結果を伝達します。 Shisho Cloud は、この伝達されたデータを元に、セキュリティ状況の可視化や改善をサポートします。

より具体的には、Shisho Cloud と Rego で書かれたポリシーは、以下のような形で値のやり取りします:

  • ポリシーへの入力: Rego 上の変数 input 経由で与えられる
  • ポリシーからの出力: Rego 上の変数 decisions 経由で行う
GraphQL クエリに従うデータのポリシーへの入力​

Shisho Cloud は、ジョブ中の input ブロックで記述された GraphQL クエリを用いてデータを取得し、それをポリシーに入力します。取得されたデータは、input 変数 からアクセスできるようになっています。

例えば GitHub 組織の一覧を取得し、それに対して検査/監査を実施するために、ジョブ中の input ブロックで、以下のようなクエリを記述したとします:

query {
github {
organizations {
login
requiresTwoFactorAuthentication
}
}
}

このとき、Rego ポリシー中からアクセスできる input という変数に、以下のようなオブジェクトが格納されます:

{
"github": {
"organizations": [
{
"login": "octcat",
"requiresTwoFactorAuthentication": true
},
{
"login": "your-org-name",
"requiresTwoFactorAuthentication": false
}
]
}
}

そのため、以下のような記述を Rego ポリシー中で行うことにより、GraphQL クエリを用いて取得した GitHub 組織の一覧が取得できます:

input.github.organizations

もし各 GitHub 組織に関する requiresTwoFactorAuthentication の値(組織中のメンバーに二要素認証の使用を強制しているかを表す bool 値)を検証したい場合は、以下のように記述できます:

org := input.github.organizations[_]
allowed := org.requiresTwoFactorAuthentication == true
info

shishoctl コマンドを利用すると、記述した GraphQL クエリを元にポリシーに対するテスト入力を作成できます。 同コマンドは、Rego ポリシーを Open Policy Agent (OPA) コマンドでテストする場合に利用できます。

以下に jobs[].decide.rego ポリシーに対する入力として利用できる JSON 形式のテストデータを、GraphQL クエリから生成するコマンドの例を示します:

shishoctl testcase generate \
--type decision \
--org (Shisho Cloud 組織の ID) \
--query-path (GraphQL クエリが格納されたファイルへのパス)

ポリシー記述に Rego 以外を利用している場合や、ポリシーの利用箇所が jobs[].notify 以下である場合も、上記のコマンドのオプションを変更することで対応できます。 詳しくは shishoctl testcase generate --help コマンドの出力を確認してください。

ポリシーからの結果の出力​

Rego 中で decisions 変数に値をセットすることで、検査/監査の結果見つかった問題や、あるいは問題が見つからなかったという事実を記録しておくことができます。別の言い方をすると、Shisho Cloud は、Rego ポリシー中の decisions 変数の値を加工の上記録します。

decisions := [
...
]

decisions[n] {
n := ...
}

ここで、ポリシーから decisions 変数に値をセットする際、その値は Decision オブジェクト である必要があります。Decision オブジェクト とは、どのような対象に対する・どのような種類の・どのような結果か を表すデータオブジェクトで、通常 Rego SDK を用いて生成されるものです。以下に例を示します:

decisions[d] {
allowed := org.requiresTwoFactorAuthentication == true

d := shisho.decision.github.org_2fa_status({
# whether the target is allowed by this policy or not
"allowed": allowed,
# the target of the decision (e.g. a GitHub repository, etc.)
"subject": org.metadata.id,
# clues on this decision
"payload": shisho.decision.github.org_2fa_status_payload({"enabled": org.requiresTwoFactorAuthentication}),
})
}

decide ブロックの例​

以下のジョブ定義の例では、検査/監査対象のデータを定義する input ブロックと、その検査/監査方法を定義する rego ブロックを定義しています:

jobs:
- name: check-with-foobar
id: check-with-foobar
decide:
rego: |
import data.shisho

decisions[d] {
org := input.github.organizations[_]
allowed := org.requiresTwoFactorAuthentication == true

d := shisho.decision.github.org_2fa_status({
"allowed": allowed,
"subject": org.metadata.id,
"payload": shisho.decision.github.org_2fa_status_payload({"enabled": org.requiresTwoFactorAuthentication}),
})
}
input:
schema: |
query {
github {
organizations {
requiresTwoFactorAuthentication
metadata {
id
}
}
}
}

with ブロック​

with ブロックは、ポリシーコードへ渡すパラメータなどを定義するためのものです。この機能を用いることで、より柔軟で効率的なポリシーコードを作成することができます。

定義されたパラメータは Rego / TypeScript 両方のポリシーコード内で使用することが可能です。

以下の with ブロックの定義の例では、ポリシーコード内で使用するシンボル名、パラメータタイプ、説明および値を定義しています。

jobs:
- decide:
decide: ...
input: ...
with:
allowName:
type: string
description: skip specific instances
value: "test-sample"

以下に Rego / TypeScript のポリシーコード内でパラメータを使用する例を示します。

# example: Rego policy code
...
allow_specific_instance(instance) {
instance.machineType == "f1-micro"
} else = contains(instance.name, input.params.allowName)
...
// example: TypeScript policy code
...
if (instance.name.includes(params.allowName)) {
return {
...
}
}
...

with ブロックが定義されている場合、Shisho Cloud のワークフローエディタではパラメータ設定 UI が表示されます。

info

パラメータタイプ は以下の4種類があり、それぞれの種類によってパラメータ設定 UI の表示が変わります。

  • string
  • notification_target
  • slack_channel
  • resource_exception

ポリシーコードにパラメータを渡す場合は string を選択してください。 他のタイプに関しては こちらのページ も参照してください。

notify ブロック​

ジョブ実行の各フェーズにおいて、どのように通知するかを定義するために、ジョブは notify ブロックを含むことがあります:

version: 0.1.0
id: "example-workflow"

jobs:
- id: "..."
name: "..."

# notify ブロック
notify:
# 入力の定義
input: # ...

# 通知方法の定義
rego: # ....
uses: # ....

構造​

notify ブロックは 「通知すべきかの判定や、通知内容の構成のために必要なデータ」 を規定する、以下のブロックを必ず含みます:

  • input ブロック

また、通知すべきかの判定方法や、通知内容の構成の方法 を規定する、以下のブロックを必ず含みます:

  • rego ブロック
  • uses ブロック

図示すると、どのような通知をするかの判断に必要な補助データ(画像左部分)と、通知ロジック(画像中央部分)を規定するのがこれら 2 つの要素です:

input ブロック​

notify ブロック中の input ブロックは、decide ブロック中の input ブロックと同様に振る舞います。 詳細は decide ブロック中の input ブロックの説明をご確認ください。

info

shishoctl コマンドを利用すると、記述した GraphQL クエリを元にポリシーに対するテスト入力を作成できます。 同コマンドは、Rego ポリシーを Open Policy Agent (OPA) コマンドでテストする場合に利用できます。

以下に jobs[].notify.rego ポリシーに対する入力として利用できる JSON 形式のテストデータを、GraphQL クエリから生成するコマンドの例を示します:

shishoctl testcase generate \
--type notification \
--org (Shisho Cloud 組織の ID) \
--query-path (GraphQL クエリが格納されたファイルへのパス)

ポリシー記述に Rego 以外を利用している場合や、ポリシーの利用箇所が jobs[].decide 以下である場合も、上記のコマンドのオプションを変更することで対応できます。 詳しくは shishoctl testcase generate --help コマンドの出力を確認してください。

rego ブロック​

rego ブロックはポリシー言語 Rego を用いて、「通知すべきかの判定方法や通知内容の構成の方法」を規定するためのものです。

GraphQL クエリに従うデータのポリシーへの入力​

Shisho Cloud は、ジョブ中の input ブロックで記述された GraphQL クエリを用いてデータを取得し、それをポリシーに入力します。取得されたデータは、input.query 変数 からアクセスできるようになっています。

info

input 変数からは、ジョブの実行状態など、ワークフロー機能に関連する様々な値が利用できます。詳しくは Rego インラインポリシーが従う API についての資料 を参照してください。

ポリシーからの結果の出力​

Rego ポリシー中で notifications 変数に値をセットすることで、Shisho Cloud に通知の送信を指示することが出来ます。

notifications := [
...
]

notifications[n] {
n := ...
}

ここで、ポリシーから notifications 変数に値をセットする際、その値は Notification オブジェクト の配列である必要があります。 ここで、Notification オブジェクト は、どのような対象に対する・どのような種類の・どのような結果か を表すデータオブジェクトで、通常 Rego SDK を用いて生成されるものです。以下に例を示します:

notifications[n] {
target := shisho.notification.to_notification_group("id-of-notification-group")
msg := "test"

n := shisho.notification.new(target, msg)
}

notifications[n] {
target := shisho.notification.to_github_issue(
#...
)
msg := "test"

n := shisho.notification.new(target, msg)
}

notify ブロックの例​

以下のジョブ定義の例では、検査/監査対象のデータを定義する input ブロックと、その検査/監査方法を定義する rego ブロックを定義しています:

jobs:
- name: check-with-foobar
id: check-with-foobar
decide:
# ...
notify:
input:
schema: |
query {
__typename
}
rego: |
package policy.github.config.notify

import data.shisho

notification_groups := []

notifications[n] {
input.running_state == shisho.job.running_state_in_queue

ng := notification_groups[_]
target := shisho.notification.to_notification_group(ng)

msg := concat("", ["Shisho Cloud has started to audit the GitHub configurations! See https://cloud.shisho.dev for further information."])

n := shisho.notification.new(target, msg)
}