-
Notifications
You must be signed in to change notification settings - Fork 30
feat(typescript): add tutorial for writing policies with typescript/javascript #663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 51 commits
75e6d6e
e747c53
f3b103e
90a41db
4a4f373
a2da140
f1460b2
8d37595
f702460
14e756b
65b6beb
e3be4b3
410402a
aa9ef5c
bb47b23
91ab0ff
5a2763c
895352c
bf2f19a
2337e5b
936e53e
5bc5556
f943284
cc0902a
8fe78fa
0add942
bf64702
317fb1d
66716e8
b2bd1a6
7cf07ca
222a851
a0588cc
9d71d20
f34bdf3
42fcbda
e506224
828663f
b54aabb
ce7080e
1ce0b99
d36046f
b10c58b
6449f20
f111b6b
4cc82b0
3770251
4998535
b39696c
6fc3ede
0c183ea
ffecbf1
1ffb025
bb8a6af
b142aa1
9f8c2b1
f58fd1a
155314d
fb9a33f
d0059f7
b80e3a8
44db024
1c7b70d
117c4f1
38e002d
5fee132
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| --- | ||
| sidebar_label: Writing policies in TypeScript/JavaScript | ||
| sidebar_position: 010 | ||
| title: Writing policies in TypeScript/JavaScript | ||
| description: A tutorial introduction to writing policies in TypeScript/JavaScript. | ||
| keywords: [kubewarden, kubernetes, writing policies in TypeScript, writing policies in JavaScript] | ||
| doc-type: [tutorial] | ||
| doc-topic: [kubewarden, writing-policies, typescript, javascript, introduction] | ||
| doc-persona: [kubewarden-policy-developer] | ||
| --- | ||
|
|
||
| <head> | ||
| <link rel="canonical" href="https://docs.kubewarden.io/tutorials/writing-policies/intro-typescript"/> | ||
| </head> | ||
|
|
||
| :::note | ||
| TypeScript/JavaScript support for WebAssembly is rapidly evolving. | ||
| This page was last revised in September 2025. | ||
| ::: | ||
|
|
||
| As stated on the [official website](https://www.typescriptlang.org/): | ||
|
|
||
| > TypeScript extends JavaScript by adding types. | ||
| > | ||
| > By understanding JavaScript, TypeScript saves you time catching errors and | ||
| > providing fixes before you run code. | ||
|
|
||
| Kubewarden uses [Javy](https://github.com/bytecodealliance/javy) (a Bytecode Alliance project) to build WebAssembly binaries from JavaScript and TypeScript. | ||
|
|
||
| > Javy takes your JavaScript code and executes it in a WebAssembly context. | ||
| > | ||
| > It features an embedded QuickJS engine compiled to WebAssembly that can execute JavaScript. | ||
| > | ||
| > The project provides both a CLI and a set of APIs for embedding and customizing the behavior when running JavaScript in WebAssembly. | ||
|
|
||
| The Kubewarden project currently uses Javy for these reasons: | ||
|
|
||
| - Mature JavaScript engine (QuickJS) compiled to WebAssembly | ||
| - Support for [WASI interface](../wasi/01-intro-wasi.md) through custom host functions | ||
| - Smaller binary sizes compared to other JavaScript-to-WebAssembly solutions | ||
| - Active development and maintenance by the Bytecode Alliance | ||
|
|
||
| ## Javy limitations | ||
|
|
||
| Javy runs JavaScript in a sandboxed WebAssembly environment with certain constraints: | ||
|
|
||
| - **WASI environment only**: Access limited to stdin/stdout/stderr and explicitly provided host capabilities | ||
| - **No Node.js APIs**: Standard Node.js modules like `fs`, `http`, or `crypto` aren't available | ||
| - **Limited standard library**: Only core JavaScript features and explicitly enabled APIs are accessible | ||
| - **Single-threaded execution**: No support for Web Workers or multi-threading | ||
| - **STDOUT restrictions**: Writing to STDOUT will break your policy - use STDERR for logging instead | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Despite these limitations, Javy provides sufficient capabilities for writing effective Kubewarden validation policies through the host capabilities system. | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Tooling | ||
|
|
||
| Writing Kubewarden policies requires: | ||
|
|
||
| - **Node.js**: Version 18 or higher | ||
| - **npm**: For dependency management | ||
| - **TypeScript**: Recommended for type safety (optional) | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| :::warning | ||
| Ensure you're using Node.js 18 or higher. Older versions may not be compatible with the compilation toolchain. | ||
| ::: | ||
|
|
||
| These TypeScript/JavaScript libraries are useful when writing a Kubewarden policy: | ||
|
|
||
| - [Kubewarden JavaScript SDK](https://github.com/kubewarden/policy-sdk-js): Provides structures and functions reducing the amount of code necessary. It also provides test helpers and access to all host capabilities. | ||
| - [Kubernetes TypeScript types](https://github.com/silverlyra/kubernetes-types): Provides TypeScript definitions for all Kubernetes resources, enabling type-safe policy development. | ||
|
|
||
| The Kubewarden project provides a [template JavaScript/TypeScript policy project](https://github.com/kubewarden/js-policy-template) you can use to create Kubewarden policies. | ||
|
|
||
| ## Getting the toolchain | ||
|
|
||
| The easiest way to get the complete toolchain is by using the Kubewarden JavaScript SDK, which includes the Javy compilation plugin: | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```bash | ||
| npm install kubewarden-policy-sdk | ||
| ``` | ||
|
|
||
| The Javy plugin binary is automatically included and you can find it at: | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ``` | ||
| node_modules/kubewarden-policy-sdk/plugin/javy-plugin-kubewarden | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| ## Tutorial prerequisites | ||
|
|
||
| During this tutorial you need these tools on your development machine: | ||
|
|
||
| - **Node.js**: Version 18 or higher with npm for dependency management | ||
| - [**`bats`**](https://github.com/bats-core/bats-core): Used to write the tests and automate their execution | ||
| - [**`kwctl`**](https://github.com/kubewarden/kwctl/releases): CLI tool provided by Kubewarden to run its policies outside of Kubernetes, among other actions. It's covered in [the testing policies section](../../testing-policies/index.md) of the documentation. | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reminder: here we will have to tell the user they have to use kwctl 1.30 or later |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| --- | ||
| sidebar_label: New validation policy | ||
| sidebar_position: 020 | ||
| title: Creating a new validation policy | ||
| description: Creating a new validation policy for Kubewarden using TypeScript. | ||
| keywords: [kubewarden, kubernetes, writing policies in TypeScript, new validation policy] | ||
| doc-type: [tutorial] | ||
| doc-topic: [kubewarden, writing-policies, typescript, creating a new validation policy] | ||
| doc-persona: [kubewarden-policy-developer] | ||
| --- | ||
|
|
||
| <head> | ||
| <link rel="canonical" href="https://docs.kubewarden.io/tutorials/writing-policies/typescript/scaffold"/> | ||
| </head> | ||
|
|
||
| This tutorial covers creating a policy that validates the hostnames of Pod objects. | ||
|
|
||
| The policy is to reject all Pods that use one or more hostnames on the deny list. | ||
| You provide policy configuration using runtime settings. | ||
|
|
||
| To summarize, the policy settings should look like this: | ||
|
|
||
| ```yaml | ||
| denied_hostnames: | ||
| - bad-host | ||
| - forbidden-host | ||
| ``` | ||
|
|
||
| The policy rejects the creation of this Pod: | ||
|
|
||
| ```yaml | ||
| apiVersion: v1 | ||
| kind: Pod | ||
| metadata: | ||
| name: nginx | ||
| spec: | ||
| hostname: bad-host | ||
| containers: | ||
| - name: nginx | ||
| image: nginx:latest | ||
| ``` | ||
|
|
||
| But, it accepts the creation of this Pod: | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```yaml | ||
| apiVersion: v1 | ||
| kind: Pod | ||
| metadata: | ||
| name: nginx | ||
| spec: | ||
| hostname: allowed-host | ||
| containers: | ||
| - name: nginx | ||
| image: nginx:latest | ||
| ``` | ||
|
|
||
| ## Scaffolding a new policy project | ||
|
|
||
| You can create a new policy project using the [template repository](https://github.com/kubewarden/js-policy-template). Select the "Use this template" green button near the top of the page and follow GitHub's wizard. | ||
|
|
||
| Clone the repository locally and update the `package.json` file to reflect your policy details: | ||
|
|
||
| ```json | ||
| { | ||
| "name": "your-policy-name", | ||
| "version": "1.0.0", | ||
| "description": "Your policy description", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/your-username/your-policy-name" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Make sure to use a repository path that matches your actual GitHub repository. | ||
|
|
||
| ## Testing | ||
|
|
||
| Provided the necessary tools are in place, the `make all` command builds the `annotated-policy.wasm` target. The command `make e2e` runs tests using `bats` with `kwctl`. | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When trying Using I get: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @esosaoh can you please check and list the needed changes for this to work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Javy team has provided documentation with a steps to migrate to v2.0.0 of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everything is going to be sorted out once:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @flavio we need to tag a new version of the sdk when |
||
| <details> | ||
| <summary>Output from the `make` commands</summary> | ||
|
|
||
| ```console | ||
| make all | ||
| npx webpack --config webpack.config.cjs | ||
| asset bundled.js 5.52 KiB [compared for emit] [minimized] (name: main) | ||
| asset types.d.ts 430 bytes [compared for emit] | ||
| asset index.d.ts 11 bytes [compared for emit] | ||
| ./src/index.ts 3.84 KiB [built] [code generated] | ||
| ./node_modules/kubewarden-policy-sdk/dist/bundle.js 3.85 KiB [built] [code generated] | ||
| webpack 5.101.3 compiled successfully in 2280 ms | ||
| npm install | ||
|
|
||
| up to date, audited 400 packages in 2s | ||
|
|
||
| 58 packages are looking for funding | ||
| run `npm fund` for details | ||
|
|
||
| found 0 vulnerabilities | ||
| ``` | ||
esosaoh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```console | ||
| make e2e | ||
| npx webpack --config webpack.config.cjs | ||
| asset bundled.js 5.52 KiB [compared for emit] [minimized] (name: main) | ||
| asset types.d.ts 430 bytes [compared for emit] | ||
| asset index.d.ts 11 bytes [compared for emit] | ||
| ./src/index.ts 3.84 KiB [built] [code generated] | ||
| ./node_modules/kubewarden-policy-sdk/dist/bundle.js 3.85 KiB [built] [code generated] | ||
| webpack 5.101.3 compiled successfully in 1909 ms | ||
| bats e2e.bats | ||
| e2e.bats | ||
| ✓ reject because hostname is on the deny list | ||
| ✓ accept because hostname is not on the deny list | ||
| ✓ accept because the deny list is empty | ||
| ✓ accept because pod has no hostname set | ||
| ✓ accept non-pod resources | ||
|
|
||
| 5 tests, 0 failures | ||
| ``` | ||
|
|
||
| </details> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| --- | ||
| sidebar_label: Defining policy settings | ||
| sidebar_position: 030 | ||
| title: Defining policy settings | ||
| description: Defining policy settings for a Kubewarden policy written in TypeScript. | ||
| keywords: [kubewarden, kubernetes, defining policy settings, TypeScript] | ||
| doc-type: [tutorial] | ||
| doc-topic: [kubewarden, writing-policies, typescript, defining-policy-settings] | ||
| doc-persona: [kubewarden-policy-developer] | ||
| --- | ||
|
|
||
| <head> | ||
| <link rel="canonical" href="https://docs.kubewarden.io/tutorials/writing-policies/typescript/policy-settings"/> | ||
| </head> | ||
|
|
||
| :::danger Critical: Don't write logging information to STDOUT | ||
|
|
||
| Writing to STDOUT breaks your policy. Instead, use STDERR for logging or the logging facility provided by the Kubewarden SDK. The policy's output to STDOUT must only contain the validation response. | ||
|
|
||
| ::: | ||
esosaoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| First, define the structure that holds the policy settings in `src/types.ts`. | ||
|
|
||
| ```ts | ||
| import type { PodSpec } from 'kubernetes-types/core/v1'; | ||
| import type { ObjectMeta } from 'kubernetes-types/meta/v1'; | ||
|
|
||
| /** | ||
| * Interface representing policy settings structure. | ||
| */ | ||
| export interface PolicySettings { | ||
| // List of hostnames that are denied by the policy. | ||
| denied_hostnames?: string[]; | ||
| } | ||
|
|
||
| /** | ||
| * Generic Kubernetes resource interface | ||
| */ | ||
| export interface KubernetesResource { | ||
| apiVersion: string; | ||
| kind: string; | ||
| metadata: ObjectMeta; | ||
| spec?: PodSpec | any; | ||
| } | ||
| ``` | ||
|
|
||
| ## Building Settings instances | ||
|
|
||
| Kubewarden policies use two functions that handle settings: | ||
|
|
||
| - `validate`: Called during object validation. | ||
| - `validateSettings`: Called at policy load time. | ||
|
|
||
| In `src/index.ts`, the `validate` function looks like: | ||
|
|
||
| ```ts | ||
| function validate(): void { | ||
| try { | ||
| const validationRequest = Validation.Validation.readValidationRequest(); | ||
| const settings: PolicySettings = validationRequest.settings || {}; | ||
|
|
||
| // Policy logic... | ||
| } catch (err) { | ||
| console.error('Validation error:', err); | ||
| writeOutput(Validation.Validation.rejectRequest(`Validation failed: ${err}`)); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Implementing Settings validation | ||
|
|
||
| ```ts | ||
| function validateSettings(): void { | ||
| try { | ||
| const settingsInput = Validation.Validation.readValidationRequest(); | ||
| const settings: PolicySettings = settingsInput as PolicySettings; | ||
|
|
||
| if (settings.denied_hostnames && !Array.isArray(settings.denied_hostnames)) { | ||
| const errorResponse = new Validation.Validation.SettingsValidationResponse( | ||
| false, | ||
| 'denied_hostnames must be an array of strings', | ||
| ); | ||
| writeOutput(errorResponse); | ||
| return; | ||
| } | ||
|
|
||
| for (const hostname of settings.denied_hostnames || []) { | ||
| if (typeof hostname !== 'string') { | ||
| const errorResponse = new Validation.Validation.SettingsValidationResponse( | ||
| false, | ||
| 'All hostnames in denied_hostnames must be strings', | ||
| ); | ||
| writeOutput(errorResponse); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| const response = new Validation.Validation.SettingsValidationResponse(true); | ||
| writeOutput(response); | ||
| } catch (err) { | ||
| console.error('Settings validation error:', err); | ||
| const errorResponse = new Validation.Validation.SettingsValidationResponse( | ||
| false, | ||
| `Settings validation failed: ${err}`, | ||
| ); | ||
| writeOutput(errorResponse); | ||
| } | ||
| } | ||
| ``` | ||
Uh oh!
There was an error while loading. Please reload this page.