Merge pull request #26 from docker/tuf-config-flags

Read TUF config from flags and add to helm chart
This commit is contained in:
Jonny Stoten
2024-06-26 12:06:19 +01:00
committed by GitHub
7 changed files with 155 additions and 58 deletions

View File

@@ -25,6 +25,22 @@ spec:
- --client-ca-file={{ .Values.clientCAFile }}
{{- end }}
- --port={{ .Values.port }}
{{- if .Values.tufRoot }}
- --tuf-root={{ .Values.tufRoot }}
{{- end }}
{{- if .Values.tufMetadataSource }}
- --tuf-metadata-source={{ .Values.tufMetadataSource }}
{{- end }}
{{- if .Values.tufTargetsSource }}
- --tuf-targets-source={{ .Values.tufTargetsSource }}
{{- end }}
{{- if .Values.localPolicyDir }}
- --local-policy-dir={{ .Values.localPolicyDir }}
{{- end }}
{{- if .Values.policyCacheDir }}
- --policy-cache-dir={{ .Values.policyCacheDir }}
{{- end }}
ports:
- containerPort: {{ .Values.port }}
protocol: TCP

View File

@@ -1,6 +1,12 @@
certDir: /certs
clientCAFile: /tmp/gatekeeper/ca.crt
port: 8090
# uncomment these lines to use the dev TUF root
# tufRoot: dev
# tufMetadataSource: https://docker.github.io/tuf-dev/metadata
# tufTargetsSource: https://docker.github.io/tuf-dev/targets
provider:
timeout: 30
tls:

View File

@@ -1,42 +1,42 @@
{
"signatures": [
{
"keyid": "b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09",
"sig": "3064023037bbb03c3472b140572a7d5a2895bd80e74435bbcb7053949731f81b104c6d05a0876590cd6a2e94d7ed619426a2f6fa02303adc8c9006fa5506fdd7ea87d2960074a537ad8bf2459f2863e806b47682cbb2f9b01b7502eaf5437a1a68fdaaeac114"
"keyid": "76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221",
"sig": "3065023000f7d0a866576e94eaabc173b9233d4c8fcfa495527088f9022dff5a553f7a457da1015a6d0fc714f84848ec627387360231009fa70b2eebbe15241a2ec9b96a094ebd28661e30b8c3d1eab8d694df2b340bda511c489393630c9a9dacde42c99e9fa1"
}
],
"signed": {
"_type": "root",
"consistent_snapshot": true,
"expires": "2034-04-02T17:00:22Z",
"expires": "2034-05-29T20:14:11Z",
"keys": {
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp256",
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"
},
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09": {
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp384",
"x-tuf-on-ci-keyowner": "@mrjoelkamp"
},
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp256",
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"
}
},
"roles": {
"root": {
"keyids": [
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09"
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"
],
"threshold": 1
},
"snapshot": {
"keyids": [
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"
],
"threshold": 1,
"x-tuf-on-ci-expiry-period": 3650,
@@ -44,13 +44,13 @@
},
"targets": {
"keyids": [
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09"
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"
],
"threshold": 1
},
"timestamp": {
"keyids": [
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"
],
"threshold": 1,
"x-tuf-on-ci-expiry-period": 3650,

56
main.go
View File

@@ -32,6 +32,24 @@ var (
certDir string
clientCAFile string
port int
tufRoot string
tufoutputPath string
metadataURL string
targetsURL string
policyDir string
policyCacheDir string
)
const (
defaultMetadataURL = "registry-1.docker.io/docker/tuf-metadata:latest"
defaultTargetsURL = "registry-1.docker.io/docker/tuf-targets"
)
var (
defaultTUFOutputPath = filepath.Join("/tuf_temp", ".docker", "tuf")
defaultPolicyCacheDir = filepath.Join("/tuf_temp", ".docker", "policy")
)
var timeoutError = string(utils.GatekeeperError("operation timed out"))
@@ -41,13 +59,47 @@ func init() {
flag.StringVar(&certDir, "cert-dir", "", "path to directory containing TLS certificates")
flag.StringVar(&clientCAFile, "client-ca-file", "", "path to client CA certificate")
flag.IntVar(&port, "port", defaultPort, "Port for the server to listen on")
flag.StringVar(&tufRoot, "tuf-root", "staging", "specify embedded tuf root [dev, staging], default [staging]")
if tufRoot != "dev" && tufRoot != "staging" {
klog.Errorf("invalid tuf root: %s", tufRoot)
os.Exit(1)
}
flag.StringVar(&metadataURL, "tuf-metadata-source", defaultMetadataURL, "source (URL or repo) for TUF metadata")
flag.StringVar(&targetsURL, "tuf-targets-source", defaultTargetsURL, "source (URL or repo) for TUF targets")
flag.StringVar(&tufoutputPath, "tuf-output-path", defaultTUFOutputPath, "local dir to store TUF repo metadata")
flag.StringVar(&policyDir, "local-policy-dir", "", "path to local policy directory (overrides TUF policy)")
flag.StringVar(&policyCacheDir, "policy-cache-dir", defaultPolicyCacheDir, "path to store policy downloaded from TUF")
flag.Parse()
}
func main() {
mux := http.NewServeMux()
mux.Handle("POST /validate", http.TimeoutHandler(handler.Validate(), handlerTimeout, timeoutError))
mux.Handle("POST /mutate", http.TimeoutHandler(handler.Mutate(), handlerTimeout, timeoutError))
validateHandler, err := handler.NewValidateHandler(&handler.ValidateHandlerOptions{
TUFRoot: tufRoot,
TUFOutputPath: tufoutputPath,
TUFMetadataURL: metadataURL,
TUFTargetsURL: targetsURL,
PolicyDir: policyDir,
PolicyCacheDir: policyCacheDir,
})
if err != nil {
klog.ErrorS(err, "unable to create validate handler")
os.Exit(1)
}
mutateHandler, err := handler.NewMutateHandler()
if err != nil {
klog.ErrorS(err, "unable to create validate handler")
os.Exit(1)
}
mux.Handle("POST /validate", http.TimeoutHandler(validateHandler, handlerTimeout, timeoutError))
mux.Handle("POST /mutate", http.TimeoutHandler(mutateHandler, handlerTimeout, timeoutError))
server := &http.Server{
Addr: fmt.Sprintf(":%d", port),

View File

@@ -15,11 +15,13 @@ import (
"k8s.io/klog/v2"
)
func Mutate() http.Handler {
return http.HandlerFunc(mutate)
type mutateHandler struct{}
func NewMutateHandler() (http.Handler, error) {
return &mutateHandler{}, nil
}
func mutate(w http.ResponseWriter, req *http.Request) {
func (h *mutateHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
defer func() {
if r := recover(); r != nil {
klog.Error(string(debug.Stack()))

View File

@@ -1,17 +0,0 @@
package handler
import (
"github.com/docker/attest/pkg/tuf"
"github.com/open-policy-agent/gatekeeper-external-data-provider/internal/embed"
)
func createTufClient(outputPath string) (*tuf.TufClient, error) {
// using oci tuf metadata and targets
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
targetsURI := "registry-1.docker.io/docker/tuf-targets"
// example using http tuf metadata and targets
// metadataURI := "https://docker.github.io/tuf-staging/metadata"
// targetsURI := "https://docker.github.io/tuf-staging/targets"
return tuf.NewTufClient(embed.StagingRoot, outputPath, metadataURI, targetsURI, tuf.NewVersionChecker())
}

View File

@@ -5,14 +5,15 @@ import (
"fmt"
"io"
"net/http"
"path/filepath"
"runtime/debug"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
"github.com/docker/attest/pkg/tuf"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/open-policy-agent/frameworks/constraint/pkg/externaldata"
"github.com/open-policy-agent/gatekeeper-external-data-provider/internal/embed"
"github.com/open-policy-agent/gatekeeper-external-data-provider/pkg/utils"
"k8s.io/klog/v2"
)
@@ -24,11 +25,50 @@ type ValidationResult struct {
Violations []policy.Violation `json:"violations"`
}
func Validate() http.Handler {
return http.HandlerFunc(validate)
type ValidateHandlerOptions struct {
TUFRoot string
TUFOutputPath string
TUFMetadataURL string
TUFTargetsURL string
PolicyDir string
PolicyCacheDir string
}
func validate(w http.ResponseWriter, req *http.Request) {
type validateHandler struct {
opts *ValidateHandlerOptions
}
func NewValidateHandler(opts *ValidateHandlerOptions) (http.Handler, error) {
handler := &validateHandler{opts: opts}
// a TUF client can only be used once, so we need to create a new one for each request.
// we create this one up front to ensure that the TUF root is valid and to pre-load the metadata.
// TODO: this pre-loading works for the root, targets, snapshot, and timestamp roles, but not for delegated roles.
_, err := handler.createTUFClient()
if err != nil {
return nil, err
}
klog.Infof("validate handler initialized with %s TUF root", opts.TUFRoot)
return handler, nil
}
func (h *validateHandler) createTUFClient() (*tuf.TufClient, error) {
var rootBytes []byte
switch h.opts.TUFRoot {
case "dev":
rootBytes = embed.DevRoot
case "staging":
rootBytes = embed.StagingRoot
default:
return nil, fmt.Errorf("invalid tuf root: %s", h.opts.TUFRoot)
}
return tuf.NewTufClient(rootBytes, h.opts.TUFOutputPath, h.opts.TUFMetadataURL, h.opts.TUFTargetsURL, tuf.NewVersionChecker())
}
func (h *validateHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
defer func() {
if r := recover(); r != nil {
klog.Error(string(debug.Stack()))
@@ -36,6 +76,10 @@ func validate(w http.ResponseWriter, req *http.Request) {
}
}()
ctx := req.Context()
debug := true
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(debug))
// read request body
requestBody, err := io.ReadAll(req.Body)
if err != nil {
@@ -53,16 +97,19 @@ func validate(w http.ResponseWriter, req *http.Request) {
return
}
results := make([]externaldata.Item, 0)
// create a tuf client
tufOutputPath := filepath.Join("/tuf_temp", ".docker", "tuf")
tufClient, err := createTufClient(tufOutputPath)
tufClient, err := h.createTUFClient()
if err != nil {
utils.SendResponse(nil, err.Error(), w)
utils.SendResponse(nil, fmt.Sprintf("unable to create TUF client: %v", err), w)
return
}
policyOpts := &policy.PolicyOptions{
TufClient: tufClient,
LocalTargetsDir: h.opts.PolicyCacheDir,
LocalPolicyDir: h.opts.PolicyDir,
}
results := make([]externaldata.Item, 0)
for _, key := range providerRequest.Request.Keys {
platform := "linux/amd64"
src, err := oci.ParseImageSpec(key, oci.WithPlatform(platform))
@@ -71,16 +118,7 @@ func validate(w http.ResponseWriter, req *http.Request) {
return
}
opts := &policy.PolicyOptions{
TufClient: tufClient,
LocalTargetsDir: filepath.Join("/tuf_temp", ".docker", "policy"), // location to store policy files downloaded from TUF
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
}
ctx := req.Context()
debug := true
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(debug))
result, err := attest.Verify(ctx, src, opts)
result, err := attest.Verify(ctx, src, policyOpts)
if err != nil {
utils.SendResponse(nil, err.Error(), w)
return