Skip to content

Instantly share code, notes, and snippets.

@theMagicalKarp
Last active December 13, 2020 00:14
Show Gist options
  • Save theMagicalKarp/f23b7fc10528e1738b0785198dbef7db to your computer and use it in GitHub Desktop.
Save theMagicalKarp/f23b7fc10528e1738b0785198dbef7db to your computer and use it in GitHub Desktop.

What

This is a deno typscript script, which pulls all the pods in the cluster, and runs a given, web assembly compiled, opa policiy on each pod.

Requirements

Usage

# convert rego policy to wasm
opa build -t wasm -e main/is_root_user policies.rego
tar -xzvf bundle.tar.gz -C bundle

deno run --allow-read --allow-run \
  https://gist.githubusercontent.com/theMagicalKarp/f23b7fc10528e1738b0785198dbef7db/raw/46180d83fe4734d8578f542d95c0a507782e0cc6/main.ts \
  bundle/policy.wasm
import { loadPolicy } from "./opa_wasm.ts";
import { autoDetectClient } from "https://deno.land/x/kubernetes_client@v0.1.0/mod.ts";
import {
CoreV1Api,
PodFields,
} from "https://deno.land/x/kubernetes_apis@v0.1.0/builtin/core@v1/mod.ts";
if (Deno.args.length !== 1) {
console.log("Must only provide <policy wasm file>!");
Deno.exit(1);
}
const wasmCode = await Deno.readFile(Deno.args[0]);
const wasmModule = new WebAssembly.Module(wasmCode);
const policy = await loadPolicy(wasmModule);
const kubernetes = await autoDetectClient();
const coreApi = new CoreV1Api(kubernetes).namespace("");
const podList = await coreApi.getPodList();
for await (const pod of podList.items) {
const evaluation = policy.evaluate(pod);
if (evaluation.length > 0) {
console.log(
`${pod.metadata?.namespace}/${pod.metadata?.name} is in violation!`,
);
}
}
// ##############################################################
//
// STOLEN FROM: https://github.com/open-policy-agent/npm-opa-wasm
//
// ##############################################################
function stringDecoder(mem: WebAssembly.Memory): (addr: number) => string {
return function (addr: number): string {
const i8 = new Int8Array(mem.buffer);
let s = "";
while (i8[addr] !== 0) {
s += String.fromCharCode(i8[addr++]);
}
return s;
};
}
function _loadJSON(
wasmInstance: WebAssembly.Instance,
memory: WebAssembly.Memory,
value: any,
): number {
if (value === undefined) {
throw "unable to load undefined value into memory";
}
const str = JSON.stringify(value);
// @ts-ignore
const rawAddr = wasmInstance.exports.opa_malloc(str.length);
const buf = new Uint8Array(memory.buffer);
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
// check for strange encodings
if (code > 126) {
code = 63; // ?
}
buf[rawAddr + i] = code;
}
// @ts-ignore
const parsedAddr = wasmInstance.exports.opa_json_parse(rawAddr, str.length);
if (parsedAddr === 0) {
throw "failed to parse json value";
}
return parsedAddr;
}
function _dumpJSON(
wasmInstance: any,
memory: WebAssembly.Memory,
addr: number,
): object {
const rawAddr = wasmInstance.exports.opa_json_dump(addr);
const buf = new Uint8Array(memory.buffer);
let s = "";
let idx = rawAddr;
while (buf[idx] !== 0) {
s += String.fromCharCode(buf[idx++]);
}
return JSON.parse(s);
}
const builtinFuncs: any = {
count: (arr: Array<any>): number => arr.length,
sum: (arr: Array<any>): number => arr.reduce((a, b) => a + b, 0),
product: (arr: Array<any>): number =>
arr.reduce((total, num) => total * num, 1),
max: (arr: Array<any>): number => Math.max(...arr),
min: (arr: Array<any>): number => Math.min(...arr),
sort: (arr: Array<any>): Array<any> => [...arr].sort(),
all: (
arr: Array<any>,
): boolean => (arr.length === 0 ? true : arr.every((v) => v === true)),
any: (
arr: Array<any>,
): boolean => (arr.length === 0 ? false : arr.includes(true)),
"array.concat": (arr1: Array<any>, arr2: Array<any>): Array<any> =>
arr1.concat(arr2),
"array.slice": (
arr: Array<any>,
startIndex: number,
stopIndex: number,
): Array<any> => arr.slice(startIndex, stopIndex),
to_number: (x: string): number => Number(x),
plus: (a: number, b: number): number => a + b,
minus: (a: number, b: number): number => a - b,
mul: (a: number, b: number): number => a * b,
div: (a: number, b: number): number => a / b,
rem: (a: number, b: number): number => a % b,
round: (x: number): number => Math.round(x),
abs: (x: number): number => Math.abs(x),
re_match: (pattern: string, value: string): boolean =>
RegExp(pattern).test(value),
"regex.split": (pattern: string, s: string): Array<string> =>
s.split(RegExp(pattern)),
contains: (s: string, search: string): boolean => s.includes(search),
endswith: (s: string, search: string): boolean => s.endsWith(search),
indexof: (s: string, search: string): number => s.indexOf(search),
lower: (s: string): string => s.toLowerCase(),
replace: (s: string, searchValue: string, newValue: string): string =>
s.replace(searchValue, newValue),
split: (s: string, delimiter: string): Array<string> => s.split(delimiter),
sprintf: (s: string, values: string): string => "oh no",
startswith: (s: string, search: string): boolean => s.startsWith(search),
substring: (s: string, start: number, length: number): string =>
s.substr(start, length),
concat: (delimiter: string, arr: Array<string>) => arr.join(delimiter),
is_number: (x: any): boolean => !isNaN(x),
is_string: (x: any): boolean => typeof x == "string",
is_boolean: (x: any): boolean => typeof x == "boolean",
is_array: (x: any): boolean => Array.isArray(x),
is_set: (x: any): boolean => x instanceof Set,
is_object: (x: any): boolean => typeof x == "object",
is_null: (x: any): boolean => x === null,
type_name: (x: any) => typeof x,
};
function _builtinCall(
wasmInstance: WebAssembly.Instance,
memory: WebAssembly.Memory,
builtins: { [builtinId: string]: string },
builtin_id: string,
_1?: any,
_2?: any,
_3?: any,
_4?: any,
): number {
const builtInName = builtins[builtin_id];
const impl = builtinFuncs[builtInName];
if (impl === undefined) {
throw {
message: "not implemented: built-in function " +
builtin_id +
": " +
builtins[builtin_id],
};
}
const args = [];
for (const x of [_1, _2, _3, _4]) {
if (!x) {
break;
}
args.push(_dumpJSON(wasmInstance, memory, x));
}
const result = impl(...args);
return _loadJSON(wasmInstance, memory, result);
}
async function _loadPolicy(
policy_wasm: WebAssembly.Module,
memory: WebAssembly.Memory,
): Promise<WebAssembly.Instance> {
const addr2string = stringDecoder(memory);
let env: any = {
builtins: {},
};
const wasm = await WebAssembly.instantiate(policy_wasm, {
env: {
memory: memory,
opa_abort: function (addr: number) {
throw addr2string(addr);
},
opa_println: function (addr: number) {
console.log(addr2string(addr));
},
opa_builtin0: function (builtin_id: string, ctx: any): number {
return _builtinCall(env.instance, memory, env.builtins, builtin_id);
},
opa_builtin1: function (builtin_id: string, ctx: any, _1: any): number {
return _builtinCall(env.instance, memory, env.builtins, builtin_id, _1);
},
opa_builtin2: function (
builtin_id: string,
ctx: any,
_1: any,
_2: any,
): number {
return _builtinCall(
env.instance,
memory,
env.builtins,
builtin_id,
_1,
_2,
);
},
opa_builtin3: function (
builtin_id: string,
ctx: any,
_1: any,
_2: any,
_3: any,
): number {
return _builtinCall(
env.instance,
memory,
env.builtins,
builtin_id,
_1,
_2,
_3,
);
},
opa_builtin4: function (
builtin_id: string,
ctx: any,
_1: any,
_2: any,
_3: any,
_4: any,
): number {
return _builtinCall(
env.instance,
memory,
env.builtins,
builtin_id,
_1,
_2,
_3,
_4,
);
},
},
});
env.instance = wasm;
const builtins: any = _dumpJSON(
env.instance,
memory,
env.instance.exports.builtins(),
);
env.builtins = {};
for (var key of Object.keys(builtins)) {
env.builtins[builtins[key]] = key;
}
return wasm;
}
class LoadedPolicy {
mem: WebAssembly.Memory;
wasmInstance: WebAssembly.Instance;
dataAddr: number;
baseHeapPtr: number;
dataHeapPtr: number;
constructor(policy: WebAssembly.Instance, memory: WebAssembly.Memory) {
this.mem = memory;
// Depending on how the wasm was instantiated "policy" might be a
// WebAssembly Instance or be a wrapper around the Module and
// Instance. We only care about the Instance.
this.wasmInstance = policy;
this.dataAddr = _loadJSON(this.wasmInstance, this.mem, {});
// @ts-ignore
this.baseHeapPtr = this.wasmInstance.exports.opa_heap_ptr_get();
this.dataHeapPtr = this.baseHeapPtr;
}
evaluate(input: any): any {
// Reset the heap pointer before each evaluation
// @ts-ignore
this.wasmInstance.exports.opa_heap_ptr_set(this.dataHeapPtr);
// Load the input data
const inputAddr: number = _loadJSON(this.wasmInstance, this.mem, input);
// Setup the evaluation context
// @ts-ignore
const ctxAddr = this.wasmInstance.exports.opa_eval_ctx_new();
// @ts-ignore
this.wasmInstance.exports.opa_eval_ctx_set_input(ctxAddr, inputAddr);
// @ts-ignore
this.wasmInstance.exports.opa_eval_ctx_set_data(ctxAddr, this.dataAddr);
// Actually evaluate the policy
// @ts-ignore
this.wasmInstance.exports.eval(ctxAddr);
// Retrieve the result
// @ts-ignore
const resultAddr = this.wasmInstance.exports.opa_eval_ctx_get_result(
ctxAddr,
);
return _dumpJSON(this.wasmInstance, this.mem, resultAddr);
}
/**
* Loads data for use in subsequent evaluations.
* @param {object} data
*/
setData(data: any) {
// @ts-ignore
this.wasmInstance.exports.opa_heap_ptr_set(this.baseHeapPtr);
this.dataAddr = _loadJSON(this.wasmInstance, this.mem, data);
// @ts-ignore
this.dataHeapPtr = this.wasmInstance.exports.opa_heap_ptr_get();
}
}
export async function loadPolicy(
regoWasm: WebAssembly.Module,
): Promise<LoadedPolicy> {
const memory = new WebAssembly.Memory({ initial: 10 });
const policy = await _loadPolicy(regoWasm, memory);
return new LoadedPolicy(policy, memory);
}
package main
is_root_user {
input.spec.securityContext.runAsUser == 0
}
is_root_user {
input.spec.containers[_].securityContext.runAsUser == 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment