142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"runtime/debug"
|
|
|
|
"github.com/docker/attest-provider/pkg/utils"
|
|
"github.com/docker/attest/pkg/attest"
|
|
"github.com/docker/attest/pkg/config"
|
|
"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"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
type ValidationResult struct {
|
|
Outcome attest.Outcome `json:"outcome"`
|
|
Input *policy.Input `json:"input"`
|
|
VSA *intoto.Statement `json:"vsa"`
|
|
Violations []policy.Violation `json:"violations"`
|
|
}
|
|
|
|
type ValidateHandlerOptions struct {
|
|
TUFRoot string
|
|
TUFOutputPath string
|
|
TUFMetadataURL string
|
|
TUFTargetsURL string
|
|
|
|
PolicyDir string
|
|
PolicyCacheDir string
|
|
|
|
AttestationStyle string
|
|
ReferrersRepo string
|
|
}
|
|
|
|
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 {
|
|
// if this failed, don't return an error, just log it and continue
|
|
// this prevents the server from getting into a crash loop if the TUF repo is down or broken,
|
|
// and we can still recover if the TUF repo comes back up.
|
|
klog.ErrorS(err, "failed to initialize TUF client")
|
|
}
|
|
|
|
klog.Infof("validate handler initialized with %s TUF root", opts.TUFRoot)
|
|
|
|
return handler, nil
|
|
}
|
|
|
|
func (h *validateHandler) createTUFClient() (*tuf.Client, error) {
|
|
root, err := tuf.GetEmbeddedRoot(h.opts.TUFRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return tuf.NewClient(root.Data, h.opts.TUFOutputPath, h.opts.TUFMetadataURL, h.opts.TUFTargetsURL, tuf.NewDefaultVersionChecker())
|
|
}
|
|
|
|
func (h *validateHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
klog.Error(string(debug.Stack()))
|
|
klog.ErrorS(fmt.Errorf("%v", r), "panic occurred")
|
|
}
|
|
}()
|
|
|
|
ctx := req.Context()
|
|
debug := true
|
|
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(debug))
|
|
|
|
// read request body
|
|
requestBody, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
utils.SendResponse(nil, fmt.Sprintf("unable to read request body: %v", err), w)
|
|
return
|
|
}
|
|
|
|
klog.InfoS("received request", "body", requestBody)
|
|
|
|
// parse request body
|
|
var providerRequest externaldata.ProviderRequest
|
|
err = json.Unmarshal(requestBody, &providerRequest)
|
|
if err != nil {
|
|
utils.SendResponse(nil, fmt.Sprintf("unable to unmarshal request body: %v", err), w)
|
|
return
|
|
}
|
|
|
|
tufClient, err := h.createTUFClient()
|
|
if err != nil {
|
|
utils.SendResponse(nil, fmt.Sprintf("unable to create TUF client: %v", err), w)
|
|
return
|
|
}
|
|
|
|
policyOpts := &policy.Options{
|
|
TUFClient: tufClient,
|
|
LocalTargetsDir: h.opts.PolicyCacheDir,
|
|
LocalPolicyDir: h.opts.PolicyDir,
|
|
AttestationStyle: config.AttestationStyle(h.opts.AttestationStyle),
|
|
ReferrersRepo: h.opts.ReferrersRepo,
|
|
}
|
|
|
|
results := make([]externaldata.Item, 0)
|
|
for _, key := range providerRequest.Request.Keys {
|
|
platform := "linux/amd64"
|
|
src, err := oci.ParseImageSpec(key, oci.WithPlatform(platform))
|
|
if err != nil {
|
|
utils.SendResponse(nil, err.Error(), w)
|
|
return
|
|
}
|
|
|
|
result, err := attest.Verify(ctx, src, policyOpts)
|
|
if err != nil {
|
|
utils.SendResponse(nil, err.Error(), w)
|
|
return
|
|
}
|
|
|
|
results = append(results, externaldata.Item{
|
|
Key: key,
|
|
Value: ValidationResult{
|
|
Outcome: result.Outcome,
|
|
Input: result.Input,
|
|
VSA: result.VSA,
|
|
Violations: result.Violations,
|
|
},
|
|
})
|
|
}
|
|
utils.SendResponse(&results, "", w)
|
|
}
|