Don't forget to take a look at Kubewarden's official documentation. The docs cover step-by-step instructions about how to write policies.
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.
TinyGo doesn't have full support of the Go Standard Library. However, this shouldn't pose a limit to policy authors.
This SDK provides helper methods to accept and reject validation requests.
A validation policy consists of these steps:
- Extract the object to inspect from the incoming payload
- (Optional) Extract the settings object from the incoming payload.
- Validation code
- 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.
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.
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.
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.
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)
}
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.
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")
})
})
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
.
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")
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")
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.
The kubewarden/policy-sdk-go/testing
module provides some test helpers
that simplify the process of writing unit tests.
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.
We provide a GitHub repository template that can be used to quickly scaffold a new Kubewarden policy writing Go.
This can be found here.