# 利用可能なポリシー記述言語に TypeScript を追加

[Shisho Cloud](https://shisho.dev/ja) における検査ルールを記述するための言語として、従来の Rego に加えて TypeScript が利用できるようになりました。
これにより、組織のポリシーに合わせた独自の検査ルールを作成する際に、慣れ親しんだ言語を用いてルールを記述していただけます。
なお Flatt Security により標準提供される検査項目（マネージド検査項目）のルールは現在 Rego 版のみを提供しておりますが、TypeScript 版の提供については順次進めてまいります。

![eyecatch](/docs/ja/_md-assets/e0302502bb-eyecatch.png)

## 概要

このリリースにより、Shisho Cloud 上で自動実行される検査ルールを記述する言語として、TypeScript（及び JavaScript）を利用できるようになりました。

まずは TypeScript で記述された検査ルールの例をご覧ください。これは Google Cloud におけるネットワークのファイアウォールが、任意の IPv4 アドレス（`0.0.0.0/0`）から 22 番ポートへの SSH 接続を許可してしまっているかどうかを検査するルールです。

```typescript title="decide.ts"
function decide(input: Input): Decision[] {
  return input.googleCloud.projects
    .map((project) =>
      project.network.vpcNetworks.map((network) => {
        const insecure_rules = network.firewallRules.filter(
          allows_ssh_public_access,
        );
        const ok = insecure_rules.length === 0;
        const decision: Decision = {
          header: {
            api_version: "decision.api.shisho.dev/v1beta",
            kind: "network_ssh_access",
            subject: network.metadata.id,
            type: ok ? TYPE_ALLOW : TYPE_DENY,
            severity: ok ? SEVERITY_INFO : SEVERITY_HIGH,
          },
        };
        return decision;
      }),
    )
    .flat();
}

function allows_ssh_public_access(rule: FirewallRule): boolean {
  const SSH_PORT = 22;
  return (
    rule.direction === "INGRESS" &&
    rule.sourceRanges.some((range) => range === "0.0.0.0/0") &&
    rule.allowed.some(
      ({ ipProtocol, ports }) =>
        (ipProtocol === "all" ||
          ipProtocol === "tcp" ||
          ipProtocol === "sctp") &&
        ports.some(({ from_, to }) => from_ <= SSH_PORT && SSH_PORT <= to),
    )
  );
}
```

TypeScript に慣れ親しんだ方であれば、この検査ルールのロジックを理解することは難しくないでしょう。この検査ルールは `allows_ssh_public_access` を条件として、問題のあるファイアウォールルール `insecure_rules` を列挙します。そしてそれをもとに、検査結果を表すオブジェクト `decision` を生成します。ファイアウォールルールに問題があるかどうかを判定する関数 `allows_ssh_public_access` は、ソース IP 範囲に `0.0.0.0/0` が含まれているか、ポート範囲が 22 を含むかどうかなどを確認しています。

検査ルールのカスタマイズも簡単です。例として、「ソース IP 範囲に `0.0.0.0/0` を指定してはいけない」という拒否リスト方式の代わりに、「ソース IP 範囲は `192.0.2.0/24` のみ指定してよい」という許可リスト方式で検査したい場合を考えます。上記の検査ルールを許可リスト方式に変更するには、次のようにコードを書き換えるだけです:

```diff
   return rule.direction === "INGRESS" &&
-    rule.sourceRanges.some(range => range === "0.0.0.0/0") &&
+    rule.sourceRanges.some(range => range !== "192.0.2.0/24") &&
     rule.allowed.some(({ ipProtocol, ports }) =>
```

カスタマイズできることはこれだけではありません。例えば 22 番以外のポート番号を検査対象に追加することも、簡単に実装できるでしょう。検査ルールのロジックが TypeScript のコードで表現されているからこそ、ロジックのあらゆる部分を自由にカスタマイズできます。

そして TypeScript の主要な特徴は、その名の通り型システムでしょう。通常のソフトウェア開発において型システムは様々な恩恵をもたらしますが、その多くは Shisho Cloud における検査ルールの実装でも享受できます。

型はしばしば、「入出力がどのような構造のデータであるか」に関するドキュメントの役割を果たします。Shisho Cloud における検査ルールとは、大まかには入力データ（`Input`）を受け取り検査結果の配列（`Decision[]`）を返す関数です。この入力データの型定義は、Shisho Cloud が提供する CLI によって次のように自動生成されます。

```typescript title="input.gen.ts"
export type Input = {
  /** All data from Google Cloud integration */
  googleCloud: {
    /** Projects in Google Cloud */
    projects: Array<{
      /** The unique, user-assigned ID of the project. It must be 6 to 30 lowercase letters, digits, or hyphens. */
      id: string,
      /** The network configuration of the project */
      network: {
        /** All VPC networks */
        vpcNetworks: Array<{
          /** The metadata to identify this resource in Shisho Cloud */
          metadata: {
            /** The ID to identify the report in Shisho Cloud */
            id: ResourceID,
          },
          /** All firewall rules */
          firewallRules: Array<{
            /** The list of ALLOW rules specified by this firewall */
            allowed: Array<{
              /** The IP protocol allows */
              ipProtocol: string,
              /** The list of ports to allow; if empty, all ports are allowed */
              ports: Array<{
                /** The first port in the range */
                from_: Int,
                /** The last port in the range */
                to: Int,
              }>,
            // ...
```

この型定義には型そのものだけでなくドキュメンテーションコメントも含まれており、検査ルールがどのようなデータを受け取るのかを簡単に理解できます。

型情報はエディタの支援機能でも活用されます。例えば入力データから検査結果の配列を生成するコードを書く際には、型情報をもとにプロパティ名がエディタ上で補完されます。これによって、検査したいフィールドに迷いなく辿り着けるでしょう。  
![エディタ支援機能によるプロパティ名の補完](/docs/ja/_md-assets/2c54641ce8-completion.png)

もちろん、型エラーの早期発見も型システムの大きな恩恵の一つです。式の型を勘違いしたり、プロパティ名を打ち間違えたりしてもすぐにエディタ上で間違いに気づくことができます。  
![型を取り違えると、すぐにエディタ上で型エラーが表示される](/docs/ja/_md-assets/596df48cc3-type-error-mismatch.png)

また、入力データの取りうる構造を網羅的にハンドリングできているかどうかを型検査によって確認することもできます。例えば AWS Network Firewall の Stateful Ruleset では、パケットに対するアクションとして pass, drop, reject, alert のいずれかを指定できますが、次のコードはそのうち 3 つに対する検査結果しか定義していません。しかしこの `switch` 文の `default` 節にて[網羅性チェック](https://typescript-eslint.io/rules/switch-exhaustiveness-check/)を記述しているため、型検査によって alert の場合の処理が漏れていることが指摘されます。  
![](/docs/ja/_md-assets/41014d7f71-exhaustiveness-check.png)
このようにリソースがとりうる様々な状態を想定できれば、よりカバー範囲の広い検査ルールを定義することができます。そして上記のように、型の情報は全ての状態を考慮する際の強力な支援となります。

ここまで述べたように、バグなく検査ルールを記述する上で型システムは大きな助けとなります。TypeScript を使い慣れている方であれば、Rego と比べて遥かに自信を持って検査ルールを書くことができるでしょう。

もちろんバグの全てが型検査によって見つかるわけではありませんから、テストなどによって検査ルールの挙動を確かめることも有用です。検査ルールのテストは、通常の TypeScript コードのテストと同様に記述できます。

```typescript title="decide.test.ts"
import { assertEquals } from "https://deno.land/std@0.202.0/assert/assert_equals.ts";
import { TYPE_DENY } from "https://deno.land/x/shisho_cloud_policy_helpers@v0.0.1/decision/mod.ts";
import { decide } from "./decide.ts";
import { Input } from "./input.gen.ts";

Deno.test("Denies networks that allow public access to port 22", () => {
  const input: Input = {
    googleCloud: {
      projects: [
        {
          id: "project-foo",
          network: {
            vpcNetworks: [
              {
                metadata: {
                  id: "googlecloud-nw-vpc-network|00000000|00000000",
                },
                firewallRules: [
                  {
                    allowed: [
                      {
                        ipProtocol: "tcp",
                        ports: [{ from_: 0, to: 65535 }],
                      },
                    ],
                    direction: "INGRESS",
                    sourceRanges: ["0.0.0.0/0"],
                  },
                ],
              },
            ],
          },
        },
      ],
    },
  };

  const decisions = decide(input);

  assertEquals(decisions.length, 1);
  assertEquals(decisions[0].header.type, TYPE_DENY);
});
```

```bash
$ deno test decide.ts
Check file:///path/to/decide_test.ts
running 1 test from ./decide_test.ts
Denies networks that allow public access to port 22 ... ok (0ms)

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

## 利用開始方法

### Shisho Cloud を新しく利用したい場合

ご関心をお寄せ頂きありがとうございます！[Shisho Cloud 公式ページ](https://shisho.dev/ja) からお問い合わせください。
直ちに Shisho Cloud の無料トライアルの開始手順をご案内いたします。

### 既存の Shisho Cloud 組織での利用

自組織のポリシーに合わせた独自の検査ルールを TypeScript で記述したい場合は、[チュートリアル](/g/getting-started/automate-your-workflows?policy-lang=typescript)をご覧ください。

なおマネージド検査項目のルールは現在 Rego 版のみを提供しておりますが、TypeScript 版の提供については順次進めてまいります。

:::info
従前のマネージド検査項目に含まれる GraphQL クエリの一部は、将来的な TypeScript 版のルール提供を見据えて少々変更されていますが、Rego で記述された検査ルールの動作への影響は基本的にありません。

TypeScript による独自の検査項目を実装する際に、マネージド検査項目が用いる GraphQL クエリを基にする場合は、従前の GraphQL クエリを利用するとコード生成に失敗する場合があります。その場合は、[マネージド検査項目のリポジトリ](https://github.com/flatt-security/shisho-cloud-managed-workflows)より最新の GraphQL クエリをご利用ください。
:::
