Merge pull request #26 from docker/tuf-config-flags
Read TUF config from flags and add to helm chart
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
56
main.go
@@ -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),
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user