Skip to content

kubewarden/policy-sdk-go

Repository files navigation

GoDoc

Don't forget to take a look at Kubewarden's official documentation. The docs cover step-by-step instructions about how to write policies.

Kubewarden Go Policy SDK

This module provides a SDK that can be used to write Kubewarden Policies using the Go programming language.

Due to current Go compiler limitations, Go policies must be built using TinyGo.

Known limitations of TinyGo

TinyGo doesn't have full support of the Go Standard Library. However, this shouldn't pose a limit to policy authors.

Validation

This SDK provides helper methods to accept and reject validation requests.

A validation policy consists of these steps:

  1. Extract the object to inspect from the incoming payload
  2. (Optional) Extract the settings object from the incoming payload.
  3. Validation code
  4. Communicate the outcome of the validation: accept/reject

The 4th step is done using helper functions provided by this SDK.

As for the 1st step, there are two approaches that can be used.

Perform "jq-like" searches

The policy receives as input a payload parameter of type []byte. This contains the JSON document described here.

Policy authors can leverage the github.com/tidwall/gjson package to search for data inside of this JSON document.

For example, assume you want to validate the labels that are inside of a Kubernetes object. This can be done with this snippet:

data := gjson.GetBytes(
  payload,
  "request.object.metadata.labels")

labels := mapset.NewThreadUnsafeSet()
denied_labels_violations := []string{}
constrained_labels_violations := []string{}

data.ForEach(func(key, value gjson.Result) bool {
  label := key.String()
  labelValue := value.String()
  // do something with label and labelValue

  return true
})

This "jq-like" approach can be pretty handy when the policy has to look deep inside of a Kubernetes object. This is especially helpful when dealing with inner objects that are optional.

Use native Go types

The majority of policies target a specific type of Kubernetes resource, like Pod, Ingress, Service and similar. Because of that, another possible approach is to unmarshal the incoming object into a native Go type.

TinyGo doesn't yet support the full Go Standard Library, plus it has limited support of Go reflection. Because of that, it is not possible to import the official Kubernetes Go library from upstream (e.g.: k8s.io/api/core/v1). Importing these official Kubernetes types will result in a compilation failure.

Moreover, Kubewarden provides TinyGo friendly Go types for all the Kubernetes types inside of the github.com/kubewarden/k8s-objects package.

Using this SDK requires TinyGo 0.28.1 or later.

Warning Using an older version of TinyGo will result in runtime errors due to the limited support for Go reflection.

Example

This snippet shows how to implement a validation function that uses the "native Go types" approach:

// Create a ValidationRequest instance from the incoming payload
validationRequest := kubewarden_protocol.ValidationRequest{}
err := json.Unmarshal(payload, &validationRequest)
if err != nil {
	return kubewarden.RejectRequest(
		kubewarden.Message(err.Error()),
		kubewarden.Code(400))
}

// Access the **raw** JSON that describes the object
ingressJSON := validationRequest.Request.Object

// Try to create an Ingress instance using the RAW JSON we got from the
// ValidationRequest.
// This policy works only against Ingress objects, if the creation fails
// we reject the request and provide a meaningful error.
ingress := &networkingv1.Ingress{}
if err := json.Unmarshal([]byte(ingressJSON), ingress); err != nil {
	return kubewarden.RejectRequest(
		kubewarden.Message(
		fmt.Sprintf("Cannot decode Ingress object: %s", err.Error())),
		kubewarden.Code(400))
}

// the validation logic

Note: the github.com/kubewarden/k8s-objects package is organized in the same way as the official k8s.io one.

Mutating policy

Mutation policies works exactly like the validation ones. The only difference is that, when a request has to be accepted AND mutated, the policy must return the input object with all the required changes applied.

Mutation policies can be done by leveraging the Kubernetes Go types defined inside of the github.com/kubewarden/k8s-objects package and the helper methods provided by this SDK.

The following example defines a mutating policy that always changes the name of Ingress objects:

import (
    "encoding/json"
	"fmt"

	networkingv1 "github.com/kubewarden/k8s-objects/api/networking/v1"
	kubewarden "github.com/kubewarden/policy-sdk-go"

)

func validate(payload []byte) ([]byte, error) {
  // Create a ValidationRequest instance from the incoming payload
  validationRequest := kubewarden_protocol.ValidationRequest{}
  err := json.Unmarshal(payload, &validationRequest)
  if err != nil {
    return kubewarden.RejectRequest(
      kubewarden.Message(err.Error()),
      kubewarden.Code(400))
  }

  // Access the **raw** JSON that describes the object
  ingressJSON := validationRequest.Request.Object

  // Try to create a Ingress instance using the RAW JSON we got from the
  // ValidationRequest.
  // This policy works only against Ingress objects, if the creation fails
  // we reject the request and provide a meaningful error.
  ingress := &networkingv1.Ingress{}
  if err := json.Unmarshal([]byte(ingressJSON), ingress); err != nil {
    return kubewarden.RejectRequest(
      kubewarden.Message(
      fmt.Sprintf("Cannot decode Ingress object: %s", err.Error())),
      kubewarden.Code(400))
  }

  ingress.Metadata.Name = fmt.Sprintf("%s-changed", ingress.Metadata.Name)

  return kubewarden.MutateRequest(ingress)
}

Logging

Policies can generate log messages that are then propagated to the host environment (eg: kwctl, policy-server).

This Go module provides logging capabilities that integrate with the onelog project.

This logging solution has been chosen because:

  • It works also with WebAssembly binaries. Other popular logging solutions cannot even be built to WebAssembly.
  • It provides good performance
  • It supports structured logging.

Usage

The instructions provided by the official onelog project apply also to Kubewarden policies.

The onelog.Logger instance must be configured to use a KubewardenLogWriter object.

	kl := kubewarden.KubewardenLogWriter{}
	logger := onelog.New(
		&kl,
		onelog.ALL, // shortcut for onelog.DEBUG|onelog.INFO|onelog.WARN|onelog.ERROR|onelog.FATAL,
	)
	logger.Info("info message from tinygo")
	logger.DebugWithFields("i'm not sure what's going on", func(e onelog.Entry) {
		e.String("string", "foobar")
		e.Int("int", 12345)
		e.Int64("int64", 12345)
		e.Float("float64", 0.15)
		e.Bool("bool", true)
		e.Err("err", errors.New("someError"))
		e.ObjectFunc("user", func(e onelog.Entry) {
			e.String("name", "somename")
		})
	})

Host capabilities

The policy executor exposes additional capabilities that can be leveraged by the guest.

These capabilities are exposed to the Go policies via this SDK, through the Host type defined inside of github.com/kubewarden/policy-sdk-go/capabilities.

Get OCI manifest digest

The policy can request the digest of an OCI manifest. This can be used to get the immutable reference of a container Image or anything that is stored inside of a container registry (e.g. Kubewarden Policies, Helm charts,...).

host := capabilities.NewHost()
digest, err := host.GetOCIManifestDigest("busybox:latest")

Hostname DNS lookup

The policy can lookup the addresses for a given hostname by using the DNS resolvers of the host that is evaluating the policy.

host := capabilities.NewHost()
ips, err := host.LookupHost("kubewarden.io")

Sigstore verification

The policy can ask the host to perform verification operations against objects stored inside of container registries (e.g. container image, kubewarden policy, helm chart,...) leveraging the Sigstore primitives.

Currently this SDK exposes helper function that can perform verification using public keys and using the Sigstore keyless mechanism.

Testing

GoDoc

The kubewarden/policy-sdk-go/testing module provides some test helpers that simplify the process of writing unit tests.

Host capabilities

The Go unit tests of a policy are not run inside of a WebAssembly environment, they are instead built into a native executable using the official Go compiler.

Because of that, at test time the host capabilities have to be mocked. This is also useful to write ad-hoc tests that can handle different kind of responses coming from the host.

The Host type described above relies on an internal waPC client that interacts with the host. At test time, the client is an instance of MockWapcClient.

Developers can create MockWapcClient instances using these two helper methods:

  • NewSuccessfulMockWapcClient: the client never fails, and always return the response payload provided by the user.
  • NewFailingMockWapcClient: the client always fails with the error provided by the user.

Project template

We provide a GitHub repository template that can be used to quickly scaffold a new Kubewarden policy writing Go.

This can be found here.