# 自社固有の検査を実装する

Shisho Cloud がネイティブで有する検査機能は、多くのクラウドサービスに対して、多くの検査項目をカバーしています。
しかし、自社固有の検査項目を実装したい場合もあるでしょう。そのような場合には、Shisho Cloud が提供する検査機能を拡張できます。

本チュートリアルでは、Shisho Cloud が提供する検査機能を拡張する方法を説明します。
具体的には、以下を実践します:

- Shisho Cloud への検査仕様（**Specification**）の登録
- Shisho Cloud ポリシーの記述・デプロイ

## 準備

ポリシーを記述する言語によって手順が異なります。

<Tabs groupId="policy-lang" queryString>
  <TabItem value="rego" label="Rego" default>

以下のように Shisho Cloud Rego SDK をクローンした後、本チュートリアルで作成するポリシーコードの保管場所として `your-policy` ディレクトリを作成してください:

```bash
git clone https://github.com/flatt-security/shisho-cloud-rego-libraries.git

mkdir ./your-policy
cd ./your-policy
```

この時点では、以下のようなディレクトリ構成になっているはずです:

```bash
.
├── your-policy
└── shisho-cloud-rego-libraries
```

最終的なディレクトリ構成は以下のようになります:

```bash
.
├── your-policy
│   ├── implementation
│   │   ├── decide.rego
│   │   ├── decide_test.rego
│   │   ├── decide.graphql
│   │   └── manifest.yaml
|   ├── specification
│       └── dgspec.yaml
└── shisho-cloud-rego-libraries
```

  </TabItem>
  <TabItem value="typescript" label="TypeScript">

検査ルールの実装では JavaScript/TypeScript ランタイムとして [Deno](https://deno.com/) を利用しますので、[Deno 公式ドキュメント](https://docs.deno.com/runtime/manual/getting_started/installation)を参照して Deno をインストールしてください。また、[お使いのエディタに対応した Deno のプラグイン](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment)をインストールしておくとさらに便利です。

Shisho Cloud 向けのポリシーコードを記述する際に利用するライブラリは、[shisho_cloud_policy_helpers](https://deno.land/x/shisho_cloud_policy_helpers) として公開されています。Deno では以下のように import 文でライブラリを直接参照できますので、ライブラリ等のインストールは不要です。

```typescript
import { DecisionPolicy } from "https://deno.land/x/shisho_cloud_policy_helpers/decision/mod.ts";
```

  </TabItem>
</Tabs>

## 検査仕様を定義する

まずは検査仕様を定義しましょう。検査仕様は、Shisho Cloud における検査項目の定義です。検査仕様は、以下の画面例の "A test security check" や "Top-level heading 1" の箇所のような位置で表示され、Shisho Cloud 上に追加したリスクがどのような背景を持つものなのかを説明するために用います:

![](/docs/ja/_md-assets/a07d33cd7e-finding.ja.png)

検査仕様の定義には YAML を用います。
`./your-policy/specification/dgspec.yaml` というファイルを作成し、コメントの指示に従いながら検査の概要を説明してみてください:

<CodeBlock language="yaml" title="./your-policy/specification/dgspec.yaml">
  {DgSpec}
</CodeBlock>

完成したら、以下のコマンドを実行して検査仕様を Shisho Cloud に登録しましょう:

```bash
shishoctl dgspec apply -o $SHISHO_ORG_ID -f "your-policy/specification/dgspec.yaml"
```

登録が完了したら、以下のコマンドを実行して検査仕様が正しく登録されたことを確認してください:

```sh
$ shishoctl dgspec describe "user.decision.api.shisho.dev/v1" "my-security-review" -o $SHISHO_ORG_ID | jq -r
{
  "apiVersion": "user.decision.api.shisho.dev/v1",
  "kind": "my-security-review",
  "title": "A test security check",
  "explanationMarkdown": "This is a test explanation. You can use **Markdown** here.\n",
  "createdAt": "2023-11-13T19:05:41Z",
  "updatedAt": "2023-11-13T19:05:41Z"
}
```

これで、Shisho Cloud における検査仕様の定義が完了しました。
なお `explanation` には Markdown を用いることができ、かつ `dgspec.yaml` には `explanation` をファイルパスで指定することもできます。
以下に定義の例を示します:

<CodeBlock language="yaml" title="./your-policy/specification/dgspec.yaml">
  {DgSpecFile}
</CodeBlock>

```yaml title="./your-policy/specification/explanation.md"
This is a test explanation. You can use **Markdown** here.
```

## 検査対象データを定義する

さて、それでは先程の検査仕様を用いて、実際に検査を実装してみましょう。
ここでは例として、Google Compute Engine のインスタンスのマシンタイプが `f1-micro` である場合に問題を報告する検査を実装します。

以下の URL の `[oid]` を Shisho Cloud の組織 ID に置き換えて、Shisho Cloud の GraphQL Playground にアクセスしてください:

```
https://cloud.shisho.dev/[oid]/playground/query
```

![](/docs/ja/_md-assets/bb70f6ce29-playground.png)

表示された Playground を用いると、上述のような検査を実装するためには、以下のような形で検査対象データを取得できることが分かります:

<CodeBlock
  language="graphql"
  title="./your-policy/implementation/decide.graphql"
>
  {DecideGraphQL}
</CodeBlock>

## 検査を実装する

<Tabs groupId="policy-lang" queryString>
  <TabItem value="rego" label="Rego">

検査対象データが取得できたら、次は検査を実装します。
Google Compute Engine のインスタンスのマシンタイプが `f1-micro` である場合に問題を報告する場合は、例えば以下のような形で実装できます:

<CodeBlock language="rego" title="./your-policy/implementation/decide.rego">
  {DecideRego}
</CodeBlock>

上述の実装を `./your-policy/implementation/decide.rego` とし、今度はテストコードも記述してみましょう。
`./your-policy/implementation/decide_test.rego` というファイルを作成し、以下を参考に、自分が記述したポリシーコードに対するテストコードを記述してみてください:

<CodeBlock
  language="rego"
  title="./your-policy/implementation/decide_test.rego"
>
  {DecideTestRego}
</CodeBlock>

テストコードを実行すると、どうやら実装した検査が正しく動作することが分かります:

```sh
$ opa test ./your-policy ./shisho-cloud-rego-libraries
```

  </TabItem>
  <TabItem value="typescript" label="TypeScript" default>

検査対象データが取得できたら、そのデータの型定義を生成し、それを元に検査ロジックを実装します。

先ほどの GraphQL クエリに対応する TypeScript の型定義は、次のコマンドを用いて生成できます:

```bash
shishoctl codegen typescript-input --query-path=./your-policy/implementation/decide.graphql --output=./your-policy/implementation/input.gen.ts
```

このコマンドを実行すると、以下のように検査対象データを表す型 `Input` が定義されます。このファイルには型そのものだけでなく各フィールドのドキュメンテーションも記載されているので、検査ロジックを実装する際には元の GraphQL クエリよりもこの `input.gen.ts` の方が参照しやすいでしょう。

<CodeBlock
  language="typescript"
  title="./your-policy/implementation/input.gen.ts"
>
  {InputTS}
</CodeBlock>

以上で検査対象データの型定義が得られたので、これを用いて検査ロジックを表す関数 `decide` を実装します。この関数を実装する際のファイル名を、ここでは `./your-policy/implementation/decide.ts` とします。

まずは関数 `decide` の型を、以下のように [`DecisionPolicy<Input>`](https://deno.land/x/shisho_cloud_policy_helpers/decision/mod.ts?s=DecisionPolicy) として明示しておきます。この型は、入力として `Input` を受け取り[^1]検査結果の配列 [`Decision[]`](https://deno.land/x/shisho_cloud_policy_helpers/decision/mod.ts?s=Decision) を返す関数を表します。

[^1]: 追加の引数として、ワークフローのマニフェストから渡される[パラメータ](/docs/ja/g/getting-started/optimize-your-workflows/parameter.md)を受け取ることもできます。

```typescript title="./your-policy/implementation/decide.ts"
import { Input } from "./input.gen.ts";
import { DecisionPolicy } from "https://deno.land/x/shisho_cloud_policy_helpers@v0.0.1/decision/mod.ts";

// Basically the same as `const decide = (input: Input): Decision[] => { ... }`
const decide: DecisionPolicy<Input> = (input) => {
  /* TODO */
};
```

この関数が Shisho Cloud 上で実行されるためには、以下のように `decide` を **[`decision_policy_adapter`](https://deno.land/x/shisho_cloud_policy_helpers/decision/mod.ts?s=decision_policy_adapter) という関数でラップしたもの**を**デフォルトエクスポート**する必要があります。ラッパー関数 `decision_policy_adapter` には、先ほど生成した `input.gen.ts` で定義されている `convert_input` を引数として与える必要があります。

```typescript title="./your-policy/implementation/decide.ts"
// highlight-next-line
import { convert_input, Input } from "./input.gen.ts";
// highlight-next-line
import {
  DecisionPolicy,
  decision_policy_adapter,
} from "https://deno.land/x/shisho_cloud_policy_helpers@v0.0.1/decision/mod.ts";

const decide: DecisionPolicy<Input> = (input) => {
  /* TODO */
};

// highlight-next-line
export default decision_policy_adapter(convert_input)(decide);
```

では、関数 `decide` の中身を実装していきます。Google Compute Engine のインスタンスのマシンタイプが `f1-micro` である場合に問題を報告する場合は、例えば以下のような形で実装できます:

<CodeBlock language="typescript" title="./your-policy/implementation/decide.ts">
  {DecideTS}
</CodeBlock>

以上でポリシーコードの実装が完了したので、今度はテストコードを記述します。
テストの方針としては、検査のロジックを表す `decide` 関数に何らかの入力を与えてみて、出力された検査結果が期待通りであるかを確認するのが良いでしょう。テストコードが `decide` 関数をインポートできるように、先ほどの `decide.ts` において `decide` をエクスポートしておきます。

```diff title="./your-policy/implementation/decide.ts"
-const decide: DecisionPolicy<Input> = (input) =>
+export const decide: DecisionPolicy<Input> = (input) =>
```

では `./your-policy/implementation/decide_test.ts` というファイルを作成し、以下を参考に、自分が記述したポリシーコードに対するテストコードを記述してみてください:

<CodeBlock
  language="typescript"
  title="./your-policy/implementation/decide_test.ts"
>
  {DecideTestTS}
</CodeBlock>

テストコードを実行すると、どうやら実装した検査が正しく動作することが分かります:

```sh
$ deno test ./your-policy/implementation/decide_test.ts
Check file:///path/to/your-policy/implementation/decide_test.ts
running 1 test from ./your-policy/implementation/decide_test.ts
my policy works ... ok (0ms)

ok | 1 passed | 0 failed (1ms)
```

  </TabItem>
</Tabs>

## ワークフローを定義する

最後のステップとして、先程実装した検査ロジックを実行するワークフローを定義します。
ワークフローは、Shisho Cloud における検査をまとめる単位であり、どのタイミングでポリシーを実行するべきかを定義する **トリガー** を含んでいます。

ワークフローの定義には YAML を用います。
`./your-policy/implementation/manifest.yaml` というファイルを作成し、コメントの指示に従いながらワークフローを定義してみてください:

<Tabs groupId="policy-lang" queryString>
  <TabItem value="rego" label="Rego">

<CodeBlock language="yaml" title="./your-policy/implementation/manifest.yaml">
  {ManifestRego}
</CodeBlock>

  </TabItem>
  <TabItem value="typescript" label="TypeScript">

<CodeBlock language="yaml" title="./your-policy/implementation/manifest.yaml">
  {ManifestTS}
</CodeBlock>

このワークフローをデプロイする下準備として、以下のコマンドでポリシーコードが利用するライブラリをダウンロードする必要があります:

```bash
deno vendor $(find . -name '*.[jt]s' -not -path './vendor/*')
```

  </TabItem>
</Tabs>

## ワークフローをデプロイする

さて、最後に、完成したワークフローを Shisho Cloud にデプロイしてみましょう。
以下のコマンドを実行してください:

<Tabs groupId="policy-lang" queryString>

<TabItem value="rego" label="Rego">

```bash
shishoctl workflow apply -o $SHISHO_ORG_ID -f ./your-policy/implementation/manifest.yaml
```

</TabItem>

<TabItem value="typescript" label="TypeScript">

```bash
shishoctl workflow apply -o $SHISHO_ORG_ID -f ./your-policy/implementation/manifest.yaml --js-import-map=./vendor/import_map.json --workspace-root=.
```

</TabItem>

</Tabs>

デプロイが完了したら、以下のようなコマンドで、`schedule` トリガーの発火を待たずにワークフローを実行できます:

```bash
shishoctl workflow run -o $SHISHO_ORG_ID my-review-workflow
```

しばらくするとワークフローの実行が完了します:

![](/docs/ja/_md-assets/ea77d4469e-run.png)

実行結果の詳細や、検査結果を確認すると、たしかに `f1-micro` であるインスタンスが不受理されていることが分かります:

![](/docs/ja/_md-assets/87dd219f82-run-example.ja.png)

![](/docs/ja/_md-assets/a07d33cd7e-finding.ja.png)

このような形で、ついに Shisho Cloud における検査を拡張することができました！

## まとめ

本チュートリアルでは、Shisho Cloud が提供する検査機能を拡張する方法を説明しました。
具体的には、以下を実践しました:

- Shisho Cloud への検査仕様（**Specification**）の登録
- Shisho Cloud ポリシーの記述・デプロイ
