Seccomp Plugin Support - Introduction

Note

Plugin support for seccomp is currently behind a feature flag and is slated for full release with version v0.12.0.

Support for basic seccomp filtering was introduced with the Jacamar CI configuration but was limited to a simple allow/block list structure. By using Golang’s plugin support it is possible to take advantage of libseccomp-golang in a more dynamic fashion during the startup of jacamar-auth.

This introduction assumes you have a basic understanding of system calls, seccomp, and Go. It also benefits greatly from either using a container runtime (i.e., Podman) or alternatively being able to re-build Jacamar CI from source to ensure the same dependencies have been utilized.

Getting Started

We are going to focus on building a simple plugin using the same container image and source code used for our currently installed version:

$ /opt/jacamar/bin/jacamar --version
Version: 0.10.0
Go Version: go1.17.5-linux/amd64
Built: 2021-12-15T22:24:15+0000

$ git clone https://gitlab.com/ecp-ci/jacamar-ci.git
Cloning into 'jacamar-ci'...

$ cd jacamar-ci && git checkout tags/v0.10.0 -b v0.10.0
Switched to a new branch 'v0.10.0-rc1'

Now you can use the same same image from the build/release process:

$ make run-centos7-builder
docker run \
    --rm -v /opt/jacamar/src/jacamar-ci:/jacamar-ci -w /jacamar-ci  \
    -it registry.gitlab.com/ecp-ci/jacamar-ci/centos7-builder:1.17.5 /bin/bash

[container] $ go version
go version go1.17.5 linux/amd64

Finally, we should finish preparing our environment by downloading the packages used in that release, most importantly libseccomp-golang.

[container] $ go mod download

First Plugin

Our first plugin is going to simply prevent mkdir(2) from being used during the after_script, while still being able to observe all default filters.

Start by creating a plugins/seccomp directory at the root of the project:

[container] $ cd /jacamar-ci && mkdir -p plugins/seccomp

Now add the following to our newly created directory:

  • mkdir.go
    • The source code for our plugin.

    • package main
      
      import (
          "syscall"
      
          libseccomp "github.com/seccomp/libseccomp-golang"
      )
      
      func SeccompExpansion(filter *libseccomp.ScmpFilter, stage string) (err error) {
          if stage == "after_script" {
              callID, _ := libseccomp.GetSyscallFromName("mkdir")
              err = filter.AddRule(callID, libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM)))
          }
      
          return err
      }
      
  • vendor/github.com/seccomp
    • Empty directory, we will copy the build dependencies to avoid conflicts with different versions.

Jacamar CI interacts with the plugin during it’s initialization, prior to any downscoping, by calling a single function: func SeccompExpansion(*libseccomp.ScmpFilter, string) error.

  • filter *libseccomp.ScmpFilter - Filter and all associated rules that will be applied by jacamar-auth, see ScmpFilter docs.

  • stage string - The current stage for the runner; prepare_exec, cleanup_exec, or a sub-stage during run_exec.

Our plugin will add a new rule to this filter, following the documentation of libseccomp-golang. Now we need to account for the package dependencies and build the plugin:

[container] $ cd /jacamar-ci/plugins/seccomp

[container] $ cp -R $GOPATH/pkg/mod/github.com/seccomp/libseccomp-golang\@v0.9.1 vendor/github.com/seccomp/libseccomp-golang

[container] $ CGO_ENABLED=1 go build -trimpath -buildmode=plugin -o mkdir.so mkdir.go

Though this is a very simple example, it is possible to make a range of modifications to the filter, including AddRuleConditional or completely replacing it with your own (though this would bypass any default filters). All that matters is that jacamar-auth can apply the filter after the plugin returns a nil error.

Verifying Functionality

Since the plugin was built in the container’s mounted volume (Git repository you cloned earlier), the mkdir.so file can be relocated on your host system. In our case we are going to copy it to the "/home/gitlab-runner/.gitlab-runner/plugins directory and update our Jacamar CI configuration accordingly:

[auth.seccomp]
validation_plugin = "/home/gitlab-runner/.gitlab-runner/plugins/seccomp.so"

Now, you can run a test job that uses mkdir in the after_script and observe the results:

Running after script...
$ mkdir test
mkdir: cannot create directory ‘test’: Operation not permitted
Error encountered during job: exit status 1

Error Handling

Any error returned by the plugin will result in a system error and a failed CI job.

{
  "level": "error",
  "msg": "unable to establish seccomp filter: plugin error: could not resolve name to syscall: 'mkdirs'",
  "stage": "prepare_exec",
  ...
}

Like most errors generated by jacamar-auth they are obfuscated by default, the above was obtained from the system log.