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

概要
このリリースにより、Shisho Cloud 上で自動実行される検査ルールを記述する言語として、TypeScript(及び JavaScript)を利用できるようになりました。
まずは TypeScript で記述された検査ルールの例をご覧ください。これは Google Cloud におけるネットワークのファイアウォールが、任意の IPv4 アドレス(0.0.0.0/0)から 22 番ポートへの SSH 接続を許可してしまっているかどうかを検査するルールです。
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 のみ指定してよい」という許可リスト方式で検査したい場合を考えます。上記の検査ルールを許可リスト方式に変更するには、次のようにコードを書き換えるだけです:
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 によって次のように自動生成されます。
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,
}>,
// ...
この型定義には型そのものだけでなくドキュメンテーションコメントも含まれており、検査ルールがどのようなデータを受け取るのかを簡単に理解できます。
型情報はエディタの支援機能でも活用されます。例えば入力データから検査結果の配列を生成するコードを書く際には、型情報をもとにプロパティ名がエディタ上で補完されます。これによって、検査したいフィールドに迷いなく辿り着けるでしょう。

もちろん、型エラーの早期発見も型システムの大きな恩恵の一つで す。式の型を勘違いしたり、プロパティ名を打ち間違えたりしてもすぐにエディタ上で間違いに気づくことができます。

また、入力データの取りうる構造を網羅的にハンドリングできているかどうかを型検査によって確認することもできます。例えば AWS Network Firewall の Stateful Ruleset では、パケットに対するアクションとして pass, drop, reject, alert のいずれかを指定できますが、次のコードはそのうち 3 つに対する検査結果しか定義していません。しかしこの switch 文の default 節にて網羅性チェックを記述しているため、型検査によって alert の場合の処理が漏れていることが指摘されます。
このようにリソースがとりうる様々な状態を想定できれば、よりカバー範囲の広い検査ルールを定義することができます。そして上記のように、型の情報は全ての状態を考慮する際の強力な支援となります。
ここまで述べたように、バグなく検査ルールを記述する上で型システムは大きな助けとなります。TypeScript を使い慣れている方であれば、Rego と比べて遥かに自信を持って検査ルールを書くことができるでしょう。
もちろんバグの全てが型検査によって見つかるわけではありませんから、テストなどによって検査ルールの挙動を確かめることも有用です。検査ルールのテストは、通常の TypeScript コードのテストと同様に記述できます。
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);
});
$ 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)
