commands: implement policy eval command
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+2
-2
@@ -355,8 +355,8 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
|
|||||||
Log: func(msg string) {
|
Log: func(msg string) {
|
||||||
log.Printf("[policy] %s", msg)
|
log.Printf("[policy] %s", msg)
|
||||||
},
|
},
|
||||||
FS: opt.Inputs.policy.FS,
|
FS: opt.Inputs.policy.FS,
|
||||||
Config: cfg,
|
VerifierProvider: policy.SignatureVerifier(cfg),
|
||||||
})
|
})
|
||||||
cbs = append(cbs, p.CheckPolicy)
|
cbs = append(cbs, p.CheckPolicy)
|
||||||
if popt.Strict {
|
if popt.Strict {
|
||||||
|
|||||||
+361
-16
@@ -1,47 +1,392 @@
|
|||||||
package policy
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/policy"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/moby/buildkit/client/llb/sourceresolver"
|
||||||
"github.com/moby/buildkit/frontend/dockerui"
|
"github.com/moby/buildkit/frontend/dockerui"
|
||||||
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
||||||
|
"github.com/moby/buildkit/sourcepolicy/policysession"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func evalCmd() *cobra.Command {
|
type evalOpts struct {
|
||||||
var filename string
|
filename string
|
||||||
var printOutput bool
|
printOutput bool
|
||||||
|
fields []string
|
||||||
|
builder *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var opts evalOpts
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "eval [source]",
|
Use: "eval source",
|
||||||
Short: "Evaluate policy for a source",
|
Short: "Evaluate policy for a source",
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runEval(args, filename, printOutput)
|
opts.builder = rootOpts.Builder
|
||||||
|
return runEval(cmd.Context(), dockerCli, args[0], opts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&filename, "filename", "", "Policy filename to evaluate")
|
cmd.Flags().StringVar(&opts.filename, "filename", "Dockerfile", "Policy filename to evaluate")
|
||||||
cmd.Flags().BoolVar(&printOutput, "print", false, "Print policy output")
|
cmd.Flags().BoolVar(&opts.printOutput, "print", false, "Print policy output")
|
||||||
|
cmd.Flags().StringSliceVar(&opts.fields, "fields", nil, "Fields to evaluate")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runEval(args []string, filename string, printOutput bool) error {
|
func runEval(ctx context.Context, dockerCli command.Cli, source string, opts evalOpts) error {
|
||||||
if len(args) > 0 {
|
src, err := parseSource(source)
|
||||||
if _, err := parseSource(args[0]); err != nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bopts := []builder.Option{}
|
||||||
|
if opts.builder != nil {
|
||||||
|
bopts = append(bopts, builder.WithName(*opts.builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := builder.New(dockerCli, bopts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := b.LoadNodes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := nodes[0].Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
workers, err := c.ListWorkers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(workers) == 0 {
|
||||||
|
return errors.New("no workers available in the builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPlatform := workers[0].Platforms[0]
|
||||||
|
p := ocispecs.Platform{
|
||||||
|
Architecture: defaultPlatform.Architecture,
|
||||||
|
OS: defaultPlatform.OS,
|
||||||
|
Variant: defaultPlatform.Variant,
|
||||||
|
}
|
||||||
|
openClient, release, err := gatewayClientFactory(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
|
||||||
|
platform := &pb.Platform{
|
||||||
|
Architecture: p.Architecture,
|
||||||
|
OS: p.OS,
|
||||||
|
Variant: p.Variant,
|
||||||
|
}
|
||||||
|
verifier := policy.SignatureVerifier(confutil.NewConfig(dockerCli))
|
||||||
|
|
||||||
|
if opts.printOutput {
|
||||||
|
srcReq := &gwpb.ResolveSourceMetaResponse{
|
||||||
|
Source: src,
|
||||||
|
}
|
||||||
|
maxAttempts := 5
|
||||||
|
var unknowns []string
|
||||||
|
var lastUnknowns []string
|
||||||
|
var trimmedUnknowns []string
|
||||||
|
var input policy.Input
|
||||||
|
var doneInvalidCheck bool
|
||||||
|
var invalidFields []string
|
||||||
|
for {
|
||||||
|
maxAttempts--
|
||||||
|
if maxAttempts <= 0 {
|
||||||
|
return errors.New("maximum attempts reached for resolving source metadata")
|
||||||
|
}
|
||||||
|
input, unknowns, err = policy.SourceToInput(ctx, verifier, srcReq, &p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
trimmedUnknowns = trimInputPrefixSlice(unknowns)
|
||||||
|
if lastUnknowns != nil && slices.Equal(trimmedUnknowns, lastUnknowns) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastUnknowns = slices.Clone(trimmedUnknowns)
|
||||||
|
toReload := []string{}
|
||||||
|
for _, f := range opts.fields {
|
||||||
|
if slices.Contains(trimmedUnknowns, f) {
|
||||||
|
toReload = append(toReload, f)
|
||||||
|
} else if !doneInvalidCheck {
|
||||||
|
invalidFields = append(invalidFields, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doneInvalidCheck = true
|
||||||
|
if len(toReload) > 0 {
|
||||||
|
req := &gwpb.ResolveSourceMetaRequest{}
|
||||||
|
if err := policy.AddUnknowns(req, toReload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gwClient, err := openClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := sourceResolverOpt(req, &p)
|
||||||
|
resp, err := gwClient.ResolveSourceMetadata(ctx, src, opt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srcReq = buildSourceMetaResponse(resp, req)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(invalidFields) > 0 {
|
||||||
|
logrus.Warnf("invalid fields: %v", strings.Join(invalidFields, ", "))
|
||||||
|
}
|
||||||
|
if len(trimmedUnknowns) > 0 {
|
||||||
|
logrus.Infof("unresolved fields: %v", strings.Join(trimmedUnknowns, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := json.MarshalIndent(input, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to marshal policy input")
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(os.Stdout, string(dt))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.filename == "" {
|
||||||
|
return errors.New("filename is required")
|
||||||
|
}
|
||||||
|
policyName := opts.filename
|
||||||
|
policyFile := policyName + ".rego"
|
||||||
|
policyData, err := os.ReadFile(policyFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read policy file %s", policyFile)
|
||||||
|
}
|
||||||
|
fsProvider := func() (fs.StatFS, func() error, error) {
|
||||||
|
root, err := os.OpenRoot(".")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrapf(err, "failed to open root for policy file %s", policyFile)
|
||||||
|
}
|
||||||
|
baseFS := root.FS()
|
||||||
|
statFS, ok := baseFS.(fs.StatFS)
|
||||||
|
if !ok {
|
||||||
|
_ = root.Close()
|
||||||
|
return nil, nil, errors.Errorf("invalid root FS type %T", baseFS)
|
||||||
|
}
|
||||||
|
return statFS, root.Close, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
env := policy.Env{
|
||||||
|
Filename: filepath.Base(policyName),
|
||||||
|
}
|
||||||
|
|
||||||
|
policyEval := policy.NewPolicy(policy.Opt{
|
||||||
|
Files: []policy.File{
|
||||||
|
{
|
||||||
|
Filename: filepath.Base(policyFile),
|
||||||
|
Data: policyData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Env: env,
|
||||||
|
FS: fsProvider,
|
||||||
|
VerifierProvider: verifier,
|
||||||
|
})
|
||||||
|
|
||||||
|
srcReq := &gwpb.ResolveSourceMetaResponse{
|
||||||
|
Source: src,
|
||||||
|
}
|
||||||
|
maxAttempts := 5
|
||||||
|
for {
|
||||||
|
maxAttempts--
|
||||||
|
if maxAttempts <= 0 {
|
||||||
|
return errors.New("maximum attempts reached for resolving policy metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
decision, next, err := policyEval.CheckPolicy(ctx, &policysession.CheckPolicyRequest{
|
||||||
|
Platform: platform,
|
||||||
|
Source: srcReq,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if next == nil {
|
||||||
|
return evalDecisionError(decision)
|
||||||
|
}
|
||||||
|
|
||||||
|
gwClient, err := openClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opt := sourceResolverOpt(next, &p)
|
||||||
|
resp, err := gwClient.ResolveSourceMetadata(ctx, src, opt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srcReq = buildSourceMetaResponse(resp, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGatewayDescriptor(desc ocispecs.Descriptor) *gwpb.Descriptor {
|
||||||
|
return &gwpb.Descriptor{
|
||||||
|
MediaType: desc.MediaType,
|
||||||
|
Digest: desc.Digest.String(),
|
||||||
|
Size: desc.Size,
|
||||||
|
Annotations: desc.Annotations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGatewayAttestationChain(chain *sourceresolver.AttestationChain) *gwpb.AttestationChain {
|
||||||
|
if chain == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
signatures := make([]string, 0, len(chain.SignatureManifests))
|
||||||
|
for _, dgst := range chain.SignatureManifests {
|
||||||
|
signatures = append(signatures, dgst.String())
|
||||||
|
}
|
||||||
|
blobs := make(map[string]*gwpb.Blob, len(chain.Blobs))
|
||||||
|
for dgst, blob := range chain.Blobs {
|
||||||
|
blobs[dgst.String()] = &gwpb.Blob{
|
||||||
|
Descriptor_: toGatewayDescriptor(blob.Descriptor),
|
||||||
|
Data: blob.Data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &gwpb.AttestationChain{
|
||||||
|
Root: chain.Root.String(),
|
||||||
|
ImageManifest: chain.ImageManifest.String(),
|
||||||
|
AttestationManifest: chain.AttestationManifest.String(),
|
||||||
|
SignatureManifests: signatures,
|
||||||
|
Blobs: blobs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sourceResolverOpt(req *gwpb.ResolveSourceMetaRequest, platform *ocispecs.Platform) sourceresolver.Opt {
|
||||||
|
opt := sourceresolver.Opt{
|
||||||
|
LogName: req.LogName,
|
||||||
|
SourcePolicies: req.SourcePolicies,
|
||||||
|
}
|
||||||
|
if req.Image != nil {
|
||||||
|
opt.ImageOpt = &sourceresolver.ResolveImageOpt{
|
||||||
|
NoConfig: req.Image.NoConfig,
|
||||||
|
AttestationChain: req.Image.AttestationChain,
|
||||||
|
Platform: platform,
|
||||||
|
ResolveMode: req.ResolveMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Git != nil {
|
||||||
|
opt.GitOpt = &sourceresolver.ResolveGitOpt{
|
||||||
|
ReturnObject: req.Git.ReturnObject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSourceMetaResponse(resp *sourceresolver.MetaResponse, req *gwpb.ResolveSourceMetaRequest) *gwpb.ResolveSourceMetaResponse {
|
||||||
|
out := &gwpb.ResolveSourceMetaResponse{
|
||||||
|
Source: resp.Op,
|
||||||
|
}
|
||||||
|
if resp.Image != nil {
|
||||||
|
chain := toGatewayAttestationChain(resp.Image.AttestationChain)
|
||||||
|
if chain == nil && req != nil && req.Image != nil && req.Image.AttestationChain {
|
||||||
|
chain = &gwpb.AttestationChain{}
|
||||||
|
}
|
||||||
|
out.Image = &gwpb.ResolveSourceImageResponse{
|
||||||
|
Digest: resp.Image.Digest.String(),
|
||||||
|
Config: resp.Image.Config,
|
||||||
|
AttestationChain: chain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if resp.Git != nil {
|
||||||
|
out.Git = &gwpb.ResolveSourceGitResponse{
|
||||||
|
Checksum: resp.Git.Checksum,
|
||||||
|
Ref: resp.Git.Ref,
|
||||||
|
CommitChecksum: resp.Git.CommitChecksum,
|
||||||
|
CommitObject: resp.Git.CommitObject,
|
||||||
|
TagObject: resp.Git.TagObject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if resp.HTTP != nil {
|
||||||
|
var lastModified *timestamppb.Timestamp
|
||||||
|
if resp.HTTP.LastModified != nil {
|
||||||
|
lastModified = timestamppb.New(*resp.HTTP.LastModified)
|
||||||
|
}
|
||||||
|
out.HTTP = &gwpb.ResolveSourceHTTPResponse{
|
||||||
|
Checksum: resp.HTTP.Digest.String(),
|
||||||
|
Filename: resp.HTTP.Filename,
|
||||||
|
LastModified: lastModified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimInputPrefixSlice(fields []string) []string {
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
out := make([]string, 0, len(fields))
|
||||||
|
for _, field := range fields {
|
||||||
|
out = append(out, strings.TrimPrefix(field, "input."))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDecisionError(decision *policysession.DecisionResponse) error {
|
||||||
|
if decision == nil {
|
||||||
|
return errors.New("policy returned no decision")
|
||||||
|
}
|
||||||
|
switch decision.Action {
|
||||||
|
case spb.PolicyAction_ALLOW, spb.PolicyAction_CONVERT:
|
||||||
|
return nil
|
||||||
|
case spb.PolicyAction_DENY:
|
||||||
|
if len(decision.DenyMessages) == 0 {
|
||||||
|
return errors.New("policy denied")
|
||||||
|
}
|
||||||
|
msgs := make([]string, 0, len(decision.DenyMessages))
|
||||||
|
for _, msg := range decision.DenyMessages {
|
||||||
|
if msg != nil && msg.Message != "" {
|
||||||
|
msgs = append(msgs, msg.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
return errors.New("policy denied")
|
||||||
|
}
|
||||||
|
return errors.Errorf("policy denied: %s", strings.Join(msgs, "; "))
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unknown policy action %s", decision.Action)
|
||||||
}
|
}
|
||||||
_ = filename
|
|
||||||
_ = printOutput
|
|
||||||
return errors.New("not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSource(input string) (*pb.SourceOp, error) {
|
func parseSource(input string) (*pb.SourceOp, error) {
|
||||||
if strings.HasPrefix(input, "docker-image://") {
|
if strings.HasPrefix(input, "docker-image://") {
|
||||||
return &pb.SourceOp{Identifier: input}, nil
|
refstr := strings.TrimPrefix(input, "docker-image://")
|
||||||
|
ref, err := reference.ParseNormalizedNamed(refstr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to parse image source reference")
|
||||||
|
}
|
||||||
|
return &pb.SourceOp{Identifier: "docker-image://" + ref.String()}, nil
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(input, "git://") {
|
if strings.HasPrefix(input, "git://") {
|
||||||
_, ok, err := dockerui.DetectGitContext(input, nil)
|
_, ok, err := dockerui.DetectGitContext(input, nil)
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gatewayClientOpener func(context.Context) (gwclient.Client, error)
|
||||||
|
|
||||||
|
func gatewayClientFactory(c *client.Client) (gatewayClientOpener, func() error, error) {
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
releaseOnce sync.Once
|
||||||
|
started atomic.Bool
|
||||||
|
ready = make(chan gwclient.Client, 1)
|
||||||
|
done = make(chan error, 1)
|
||||||
|
openErr error
|
||||||
|
releaseErr error
|
||||||
|
gwClient gwclient.Client
|
||||||
|
cancel context.CancelCauseFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
open := func(ctx context.Context) (gwclient.Client, error) {
|
||||||
|
once.Do(func() {
|
||||||
|
started.Store(true)
|
||||||
|
buildCtx, cancelFn := context.WithCancelCause(ctx)
|
||||||
|
cancel = cancelFn
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := c.Build(buildCtx, client.SolveOpt{Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||||
|
ready <- c
|
||||||
|
<-buildCtx.Done()
|
||||||
|
return nil, context.Cause(buildCtx)
|
||||||
|
}, nil)
|
||||||
|
done <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case gwClient = <-ready:
|
||||||
|
case err := <-done:
|
||||||
|
if err == nil {
|
||||||
|
err = errors.New("gateway build finished without a client")
|
||||||
|
}
|
||||||
|
openErr = err
|
||||||
|
case <-ctx.Done():
|
||||||
|
openErr = context.Cause(ctx)
|
||||||
|
cancelFn(openErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if openErr != nil {
|
||||||
|
return nil, openErr
|
||||||
|
}
|
||||||
|
return gwClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
release := func() error {
|
||||||
|
releaseOnce.Do(func() {
|
||||||
|
if !started.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cancel != nil {
|
||||||
|
cancel(context.Canceled)
|
||||||
|
}
|
||||||
|
err := <-done
|
||||||
|
if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
releaseErr = err
|
||||||
|
})
|
||||||
|
return releaseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return open, release, nil
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
package policy
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RootOptions struct {
|
||||||
|
Builder *string
|
||||||
|
}
|
||||||
|
|
||||||
// RootCmd creates the policy command tree.
|
// RootCmd creates the policy command tree.
|
||||||
func RootCmd(rootcmd *cobra.Command) *cobra.Command {
|
func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "policy",
|
Use: "policy",
|
||||||
Short: "Commands for working with build policies",
|
Short: "Commands for working with build policies",
|
||||||
@@ -13,7 +18,7 @@ func RootCmd(rootcmd *cobra.Command) *cobra.Command {
|
|||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
jsonSchemaCmd(),
|
jsonSchemaCmd(),
|
||||||
evalCmd(),
|
evalCmd(dockerCli, rootOpts),
|
||||||
testCmd(),
|
testCmd(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -121,7 +121,7 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) {
|
|||||||
installCmd(dockerCli),
|
installCmd(dockerCli),
|
||||||
uninstallCmd(dockerCli),
|
uninstallCmd(dockerCli),
|
||||||
versionCmd(dockerCli),
|
versionCmd(dockerCli),
|
||||||
policycmd.RootCmd(cmd),
|
policycmd.RootCmd(cmd, dockerCli, policycmd.RootOptions{Builder: &opts.builder}),
|
||||||
pruneCmd(dockerCli, opts),
|
pruneCmd(dockerCli, opts),
|
||||||
duCmd(dockerCli, opts),
|
duCmd(dockerCli, opts),
|
||||||
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Extended build capabilities with BuildKit
|
|||||||
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||||
| [`ls`](buildx_ls.md) | List builder instances |
|
| [`ls`](buildx_ls.md) | List builder instances |
|
||||||
|
| [`policy`](buildx_policy.md) | Commands for working with build policies |
|
||||||
| [`prune`](buildx_prune.md) | Remove build cache |
|
| [`prune`](buildx_prune.md) | Remove build cache |
|
||||||
| [`rm`](buildx_rm.md) | Remove one or more builder instances |
|
| [`rm`](buildx_rm.md) | Remove one or more builder instances |
|
||||||
| [`stop`](buildx_stop.md) | Stop builder instance |
|
| [`stop`](buildx_stop.md) | Stop builder instance |
|
||||||
|
|||||||
@@ -13,43 +13,44 @@ Start a build
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------------------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------------------------|
|
|:----------------------------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [`--add-host`](#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
| [`--add-host`](#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||||
| [`--allow`](#allow) | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`, `device`) |
|
| [`--allow`](#allow) | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`, `device`) |
|
||||||
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
||||||
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||||
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||||
| [`--call`](#call) | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
| [`--call`](#call) | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
||||||
| [`--cgroup-parent`](#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| [`--cgroup-parent`](#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| [`-f`](#file), [`--file`](#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| [`-f`](#file), [`--file`](#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
| [`--load`](#load) | `bool` | | Shorthand for `--output=type=docker` |
|
| [`--load`](#load) | `bool` | | Shorthand for `--output=type=docker` |
|
||||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to a file |
|
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to a file |
|
||||||
| [`--network`](#network) | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
| [`--network`](#network) | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||||
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
||||||
| [`--no-cache-filter`](#no-cache-filter) | `stringArray` | | Do not cache specified stages |
|
| [`--no-cache-filter`](#no-cache-filter) | `stringArray` | | Do not cache specified stages |
|
||||||
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `quiet`, `rawjson`, `tty`). Use plain to show container output |
|
| `--policy` | `stringArray` | | Policy configuration (format: `filename=path[,filename=path][,reset=true\|false][,disabled=true\|false][,strict=true\|false][,log-level=level]`) |
|
||||||
| [`--provenance`](#provenance) | `string` | | Shorthand for `--attest=type=provenance` |
|
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `quiet`, `rawjson`, `tty`). Use plain to show container output |
|
||||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
| [`--provenance`](#provenance) | `string` | | Shorthand for `--attest=type=provenance` |
|
||||||
| [`--push`](#push) | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
| [`--push`](#push) | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
||||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Image identifier (format: `[registry/]repository[:tag]`) |
|
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
| [`--target`](#target) | `string` | | Set the target build stage to build |
|
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Image identifier (format: `[registry/]repository[:tag]`) |
|
||||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
| [`--target`](#target) | `string` | | Set the target build stage to build |
|
||||||
|
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|||||||
@@ -5,43 +5,44 @@ Start a build
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:--------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------------------------|
|
|:--------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `--add-host` | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
| `--add-host` | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||||
| `--allow` | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`, `device`) |
|
| `--allow` | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`, `device`) |
|
||||||
| `--annotation` | `stringArray` | | Add annotation to the image |
|
| `--annotation` | `stringArray` | | Add annotation to the image |
|
||||||
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||||
| `--build-arg` | `stringArray` | | Set build-time variables |
|
| `--build-arg` | `stringArray` | | Set build-time variables |
|
||||||
| `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) |
|
| `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||||
| `--builder` | `string` | | Override the configured builder instance |
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||||
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||||
| `--call` | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
| `--call` | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
||||||
| `--cgroup-parent` | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| `--cgroup-parent` | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| `--check` | `bool` | | Shorthand for `--call=check` |
|
| `--check` | `bool` | | Shorthand for `--call=check` |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| `-f`, `--file` | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| `-f`, `--file` | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
| `--load` | `bool` | | Shorthand for `--output=type=docker` |
|
| `--load` | `bool` | | Shorthand for `--output=type=docker` |
|
||||||
| `--metadata-file` | `string` | | Write build result metadata to a file |
|
| `--metadata-file` | `string` | | Write build result metadata to a file |
|
||||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||||
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
||||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||||
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||||
| `--platform` | `stringArray` | | Set target platform for build |
|
| `--platform` | `stringArray` | | Set target platform for build |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `quiet`, `rawjson`, `tty`). Use plain to show container output |
|
| `--policy` | `stringArray` | | Policy configuration (format: `filename=path[,filename=path][,reset=true\|false][,disabled=true\|false][,strict=true\|false][,log-level=level]`) |
|
||||||
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `quiet`, `rawjson`, `tty`). Use plain to show container output |
|
||||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
||||||
| `--push` | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
| `--push` | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
||||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| `-t`, `--tag` | `stringArray` | | Image identifier (format: `[registry/]repository[:tag]`) |
|
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
| `--target` | `string` | | Set the target build stage to build |
|
| `-t`, `--tag` | `stringArray` | | Image identifier (format: `[registry/]repository[:tag]`) |
|
||||||
| `--ulimit` | `ulimit` | | Ulimit options |
|
| `--target` | `string` | | Set the target build stage to build |
|
||||||
|
| `--ulimit` | `ulimit` | | Ulimit options |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|||||||
@@ -9,43 +9,44 @@ Start a build
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:--------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------------------------|
|
|:--------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `--add-host` | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
| `--add-host` | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||||
| `--allow` | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`, `device`) |
|
| `--allow` | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`, `device`) |
|
||||||
| `--annotation` | `stringArray` | | Add annotation to the image |
|
| `--annotation` | `stringArray` | | Add annotation to the image |
|
||||||
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||||
| `--build-arg` | `stringArray` | | Set build-time variables |
|
| `--build-arg` | `stringArray` | | Set build-time variables |
|
||||||
| `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) |
|
| `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||||
| `--builder` | `string` | | Override the configured builder instance |
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||||
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||||
| `--call` | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
| `--call` | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
||||||
| `--cgroup-parent` | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| `--cgroup-parent` | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| `--check` | `bool` | | Shorthand for `--call=check` |
|
| `--check` | `bool` | | Shorthand for `--call=check` |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| `-f`, `--file` | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| `-f`, `--file` | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
| `--load` | `bool` | | Shorthand for `--output=type=docker` |
|
| `--load` | `bool` | | Shorthand for `--output=type=docker` |
|
||||||
| `--metadata-file` | `string` | | Write build result metadata to a file |
|
| `--metadata-file` | `string` | | Write build result metadata to a file |
|
||||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||||
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
||||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||||
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||||
| `--platform` | `stringArray` | | Set target platform for build |
|
| `--platform` | `stringArray` | | Set target platform for build |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `quiet`, `rawjson`, `tty`). Use plain to show container output |
|
| `--policy` | `stringArray` | | Policy configuration (format: `filename=path[,filename=path][,reset=true\|false][,disabled=true\|false][,strict=true\|false][,log-level=level]`) |
|
||||||
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `quiet`, `rawjson`, `tty`). Use plain to show container output |
|
||||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
||||||
| `--push` | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
| `--push` | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
||||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| `-t`, `--tag` | `stringArray` | | Image identifier (format: `[registry/]repository[:tag]`) |
|
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
| `--target` | `string` | | Set the target build stage to build |
|
| `-t`, `--tag` | `stringArray` | | Image identifier (format: `[registry/]repository[:tag]`) |
|
||||||
| `--ulimit` | `ulimit` | | Ulimit options |
|
| `--target` | `string` | | Set the target build stage to build |
|
||||||
|
| `--ulimit` | `ulimit` | | Ulimit options |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# docker buildx policy
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Commands for working with build policies
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|:----------------------------------------------|:-----------------------------|
|
||||||
|
| [`eval`](buildx_policy_eval.md) | Evaluate policy for a source |
|
||||||
|
| [`json-schema`](buildx_policy_json-schema.md) | Print policy JSON schema |
|
||||||
|
| [`test`](buildx_policy_test.md) | Run policy tests |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# docker buildx policy eval
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Evaluate policy for a source
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:--------------|:-------------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| `--fields` | `stringSlice` | | Fields to evaluate |
|
||||||
|
| `--filename` | `string` | `Dockerfile` | Policy filename to evaluate |
|
||||||
|
| `--print` | `bool` | | Print policy output |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# docker buildx policy json-schema
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Print policy JSON schema
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# docker buildx policy test
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Run policy tests
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
|
solverpb "github.com/moby/buildkit/solver/pb"
|
||||||
moby_buildkit_v1_sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
|
moby_buildkit_v1_sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
|
||||||
"github.com/moby/buildkit/sourcepolicy/policysession"
|
"github.com/moby/buildkit/sourcepolicy/policysession"
|
||||||
solverpb "github.com/moby/buildkit/solver/pb"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+44
-2
@@ -4,18 +4,57 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd/v2/core/content"
|
"github.com/containerd/containerd/v2/core/content"
|
||||||
"github.com/containerd/containerd/v2/core/remotes"
|
"github.com/containerd/containerd/v2/core/remotes"
|
||||||
cerrderfs "github.com/containerd/errdefs"
|
cerrderfs "github.com/containerd/errdefs"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
|
policyverifier "github.com/moby/policy-helpers"
|
||||||
policyimage "github.com/moby/policy-helpers/image"
|
policyimage "github.com/moby/policy-helpers/image"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Policy) parseSignatures(ctx context.Context, ac *gwpb.AttestationChain, platform *ocispecs.Platform) ([]AttestationSignature, error) {
|
type PolicyVerifierProvider func() (*policyverifier.Verifier, error)
|
||||||
|
|
||||||
|
func SignatureVerifier(cfg *confutil.Config) PolicyVerifierProvider {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
v *policyverifier.Verifier
|
||||||
|
)
|
||||||
|
return func() (*policyverifier.Verifier, error) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if v != nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
root := cfg.Dir()
|
||||||
|
confDir := filepath.Join(root, "policy")
|
||||||
|
if err := cfg.MkdirAll("policy/tuf", 0o755); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to create policy verifier config dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
nv, err := policyverifier.NewVerifier(policyverifier.Config{
|
||||||
|
StateDir: confDir,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to create policy verifier")
|
||||||
|
}
|
||||||
|
v = nv
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSignatures(ctx context.Context, getVerifier PolicyVerifierProvider, ac *gwpb.AttestationChain, platform *ocispecs.Platform) ([]AttestationSignature, error) {
|
||||||
if ac.Root == "" || ac.AttestationManifest == "" || len(ac.SignatureManifests) == 0 {
|
if ac.Root == "" || ac.AttestationManifest == "" || len(ac.SignatureManifests) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -63,7 +102,10 @@ func (p *Policy) parseSignatures(ctx context.Context, ac *gwpb.AttestationChain,
|
|||||||
return nil, errors.Errorf("attestation manifest digest mismatch: expected %s, got %s", att, sc.AttestationManifest.Digest)
|
return nil, errors.Errorf("attestation manifest digest mismatch: expected %s, got %s", att, sc.AttestationManifest.Digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := p.getVerifier()
|
if getVerifier == nil {
|
||||||
|
return nil, errors.New("policy verifier is not configured")
|
||||||
|
}
|
||||||
|
v, err := getVerifier()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "getting policy verifier")
|
return nil, errors.Wrapf(err, "getting policy verifier")
|
||||||
}
|
}
|
||||||
|
|||||||
+320
-322
@@ -9,7 +9,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -18,14 +17,12 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/platforms"
|
"github.com/containerd/platforms"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/buildx/util/confutil"
|
|
||||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
moby_buildkit_v1_sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
|
moby_buildkit_v1_sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
|
||||||
"github.com/moby/buildkit/sourcepolicy/policysession"
|
"github.com/moby/buildkit/sourcepolicy/policysession"
|
||||||
"github.com/moby/buildkit/util/gitutil"
|
"github.com/moby/buildkit/util/gitutil"
|
||||||
"github.com/moby/buildkit/util/gitutil/gitobject"
|
"github.com/moby/buildkit/util/gitutil/gitobject"
|
||||||
policyverifier "github.com/moby/policy-helpers"
|
|
||||||
"github.com/open-policy-agent/opa/v1/ast"
|
"github.com/open-policy-agent/opa/v1/ast"
|
||||||
"github.com/open-policy-agent/opa/v1/rego"
|
"github.com/open-policy-agent/opa/v1/rego"
|
||||||
"github.com/open-policy-agent/opa/v1/topdown/print"
|
"github.com/open-policy-agent/opa/v1/topdown/print"
|
||||||
@@ -52,9 +49,6 @@ func debugf(format string, v ...any) {
|
|||||||
type Policy struct {
|
type Policy struct {
|
||||||
opt Opt
|
opt Opt
|
||||||
funcs []fun
|
funcs []fun
|
||||||
|
|
||||||
verifierMu sync.Mutex
|
|
||||||
verifier *policyverifier.Verifier
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
@@ -77,11 +71,11 @@ type fun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Opt struct {
|
type Opt struct {
|
||||||
Files []File
|
Files []File
|
||||||
Env Env
|
Env Env
|
||||||
Log func(string)
|
Log func(string)
|
||||||
FS func() (fs.StatFS, func() error, error)
|
FS func() (fs.StatFS, func() error, error)
|
||||||
Config *confutil.Config
|
VerifierProvider PolicyVerifierProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ policysession.PolicyCallback = (&Policy{}).CheckPolicy
|
var _ policysession.PolicyCallback = (&Policy{}).CheckPolicy
|
||||||
@@ -99,227 +93,13 @@ func NewPolicy(opt Opt) *Policy {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) getVerifier() (*policyverifier.Verifier, error) {
|
|
||||||
p.verifierMu.Lock()
|
|
||||||
defer p.verifierMu.Unlock()
|
|
||||||
|
|
||||||
if p.verifier != nil {
|
|
||||||
return p.verifier, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
root := p.opt.Config.Dir()
|
|
||||||
|
|
||||||
confDir := filepath.Join(root, "policy")
|
|
||||||
if err := p.opt.Config.MkdirAll("policy/tuf", 0o755); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to create policy verifier config dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := policyverifier.NewVerifier(policyverifier.Config{
|
|
||||||
StateDir: confDir,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to create policy verifier")
|
|
||||||
}
|
|
||||||
p.verifier = v
|
|
||||||
return p.verifier, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
|
func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
|
||||||
var inp Input
|
|
||||||
var unknowns []string
|
|
||||||
inp.Env = p.opt.Env
|
|
||||||
|
|
||||||
if req.Source == nil || req.Source.Source == nil {
|
if req.Source == nil || req.Source.Source == nil {
|
||||||
return nil, nil, errors.Errorf("no source info in request")
|
return nil, nil, errors.Errorf("no source info in request")
|
||||||
}
|
}
|
||||||
src := req.Source
|
src := req.Source
|
||||||
|
var platform *ocispecs.Platform
|
||||||
scheme, refstr, ok := strings.Cut(src.Source.Identifier, "://")
|
if req.Platform != nil {
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.Errorf("invalid source identifier: %s", src.Source.Identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch scheme {
|
|
||||||
case "http", "https":
|
|
||||||
u, err := url.Parse(src.Source.Identifier)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrapf(err, "failed to parse http source url")
|
|
||||||
}
|
|
||||||
inp.HTTP = &HTTP{
|
|
||||||
URL: src.Source.Identifier,
|
|
||||||
Schema: scheme,
|
|
||||||
Host: u.Host,
|
|
||||||
Path: u.Path,
|
|
||||||
Query: u.Query(),
|
|
||||||
}
|
|
||||||
if _, ok := src.Source.Attrs[pb.AttrHTTPAuthHeaderSecret]; ok {
|
|
||||||
inp.HTTP.HasAuth = true
|
|
||||||
}
|
|
||||||
if req.Source.Image == nil {
|
|
||||||
unknowns = append(unknowns, "input.http.checksum")
|
|
||||||
} else {
|
|
||||||
inp.HTTP.Checksum = req.Source.Image.Digest
|
|
||||||
}
|
|
||||||
case "git":
|
|
||||||
if !gitutil.IsGitTransport(refstr) {
|
|
||||||
refstr = "https://" + refstr
|
|
||||||
}
|
|
||||||
u, err := gitutil.ParseURL(refstr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
g := &Git{
|
|
||||||
Schema: u.Scheme,
|
|
||||||
Remote: u.Remote,
|
|
||||||
Host: u.Host,
|
|
||||||
}
|
|
||||||
var ref string
|
|
||||||
var isFullRef bool
|
|
||||||
if u.Opts != nil {
|
|
||||||
ref = u.Opts.Ref
|
|
||||||
g.Subdir = u.Opts.Subdir
|
|
||||||
if sd := path.Clean(g.Subdir); sd == "/" || sd == "." {
|
|
||||||
g.Subdir = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v, ok := src.Source.Attrs[pb.AttrFullRemoteURL]; !ok {
|
|
||||||
if !gitutil.IsGitTransport(v) {
|
|
||||||
v = "https://" + v
|
|
||||||
}
|
|
||||||
u, err := gitutil.ParseURL(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
g.Schema = u.Scheme
|
|
||||||
g.Remote = u.Remote
|
|
||||||
g.Host = u.Host
|
|
||||||
g.FullURL = v
|
|
||||||
}
|
|
||||||
if tag, ok := strings.CutPrefix(g.Ref, "refs/tags/"); ok {
|
|
||||||
g.TagName = tag
|
|
||||||
isFullRef = true
|
|
||||||
}
|
|
||||||
if branch, ok := strings.CutPrefix(g.Ref, "refs/heads/"); ok {
|
|
||||||
g.Branch = branch
|
|
||||||
isFullRef = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if gitutil.IsCommitSHA(ref) {
|
|
||||||
g.IsCommitRef = true
|
|
||||||
g.Checksum = ref
|
|
||||||
g.CommitChecksum = ref
|
|
||||||
isFullRef = true
|
|
||||||
}
|
|
||||||
|
|
||||||
unk := []string{}
|
|
||||||
|
|
||||||
if src.Git == nil {
|
|
||||||
if !isFullRef {
|
|
||||||
unk = append(unk, "tagName", "branch", "ref")
|
|
||||||
} else {
|
|
||||||
g.Ref = ref
|
|
||||||
}
|
|
||||||
if g.Checksum == "" {
|
|
||||||
unk = append(unk, "checksum", "isAnnotatedTag", "commitChecksum", "isSHA256")
|
|
||||||
}
|
|
||||||
unk = append(unk, "tag", "commit")
|
|
||||||
} else {
|
|
||||||
g.Ref = src.Git.Ref
|
|
||||||
if tag, ok := strings.CutPrefix(g.Ref, "refs/tags/"); ok {
|
|
||||||
g.TagName = tag
|
|
||||||
}
|
|
||||||
if branch, ok := strings.CutPrefix(g.Ref, "refs/heads/"); ok {
|
|
||||||
g.Branch = branch
|
|
||||||
}
|
|
||||||
g.Checksum = src.Git.Checksum
|
|
||||||
g.CommitChecksum = src.Git.CommitChecksum
|
|
||||||
if g.CommitChecksum == "" {
|
|
||||||
g.CommitChecksum = g.Checksum
|
|
||||||
}
|
|
||||||
if g.Checksum != g.CommitChecksum {
|
|
||||||
g.IsAnnotatedTag = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(src.Git.CommitObject) == 0 {
|
|
||||||
unk = append(unk, "commit", "tag")
|
|
||||||
} else {
|
|
||||||
obj, err := gitobject.Parse(src.Git.CommitObject)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := obj.VerifyChecksum(g.CommitChecksum); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
c, err := obj.ToCommit()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
g.Commit = &Commit{
|
|
||||||
Tree: c.Tree,
|
|
||||||
Message: c.Message,
|
|
||||||
Parents: c.Parents,
|
|
||||||
Author: Actor(c.Author),
|
|
||||||
Committer: Actor(c.Committer),
|
|
||||||
obj: obj,
|
|
||||||
}
|
|
||||||
s := parseGitSignature(obj)
|
|
||||||
g.Commit.PGPSignature = s.PGPSignature
|
|
||||||
g.Commit.SSHSignature = s.SSHSignature
|
|
||||||
|
|
||||||
if dt := src.Git.TagObject; len(dt) > 0 {
|
|
||||||
obj, err := gitobject.Parse(src.Git.TagObject)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := obj.VerifyChecksum(g.Checksum); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t, err := obj.ToTag()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
g.Tag = &Tag{
|
|
||||||
Object: t.Object,
|
|
||||||
Message: t.Message,
|
|
||||||
Type: t.Type,
|
|
||||||
Tag: t.Tag,
|
|
||||||
Tagger: Actor(t.Tagger),
|
|
||||||
obj: obj,
|
|
||||||
}
|
|
||||||
s := parseGitSignature(obj)
|
|
||||||
g.Tag.PGPSignature = s.PGPSignature
|
|
||||||
g.Tag.SSHSignature = s.SSHSignature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(g.Checksum) == 64 {
|
|
||||||
g.IsSHA256 = true
|
|
||||||
}
|
|
||||||
|
|
||||||
unknowns = append(unknowns, withPrefix(unk, "input.git.")...)
|
|
||||||
inp.Git = g
|
|
||||||
case "docker-image":
|
|
||||||
ref, err := reference.ParseNormalizedNamed(refstr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrapf(err, "failed to parse image source reference")
|
|
||||||
}
|
|
||||||
inp.Image = &Image{
|
|
||||||
Ref: ref.String(),
|
|
||||||
Host: reference.Domain(ref),
|
|
||||||
Repo: reference.FamiliarName(ref),
|
|
||||||
FullRepo: ref.Name(),
|
|
||||||
}
|
|
||||||
if digested, ok := ref.(reference.Canonical); ok {
|
|
||||||
inp.Image.Checksum = digested.Digest().String()
|
|
||||||
inp.Image.IsCanonical = true
|
|
||||||
}
|
|
||||||
if tagged, ok := ref.(reference.Tagged); ok {
|
|
||||||
inp.Image.Tag = tagged.Tag()
|
|
||||||
}
|
|
||||||
if req.Platform == nil {
|
|
||||||
return nil, nil, errors.Errorf("platform required for image source")
|
|
||||||
}
|
|
||||||
platformStr := req.Platform.OS + "/" + req.Platform.Architecture
|
platformStr := req.Platform.OS + "/" + req.Platform.Architecture
|
||||||
if req.Platform.Variant != "" {
|
if req.Platform.Variant != "" {
|
||||||
platformStr += "/" + req.Platform.Variant
|
platformStr += "/" + req.Platform.Variant
|
||||||
@@ -329,62 +109,15 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
|
|||||||
return nil, nil, errors.Wrapf(err, "failed to parse platform")
|
return nil, nil, errors.Wrapf(err, "failed to parse platform")
|
||||||
}
|
}
|
||||||
pl = platforms.Normalize(pl)
|
pl = platforms.Normalize(pl)
|
||||||
inp.Image.Platform = platforms.Format(pl)
|
platform = &pl
|
||||||
inp.Image.OS = pl.OS
|
|
||||||
inp.Image.Architecture = pl.Architecture
|
|
||||||
inp.Image.Variant = pl.Variant
|
|
||||||
|
|
||||||
configFields := []string{
|
|
||||||
"labels", "user", "volumes", "workingDir", "env",
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Source.Image == nil {
|
|
||||||
if !inp.Image.IsCanonical {
|
|
||||||
unknowns = append(unknowns, "input.image.checksum")
|
|
||||||
}
|
|
||||||
unknowns = append(unknowns, withPrefix(configFields, "input.image.")...)
|
|
||||||
unknowns = append(unknowns, "input.image.hasProvenance", "input.image.signatures")
|
|
||||||
} else {
|
|
||||||
inp.Image.Checksum = req.Source.Image.Digest
|
|
||||||
if cfg := req.Source.Image.Config; cfg != nil {
|
|
||||||
var img ocispecs.Image
|
|
||||||
if err := json.Unmarshal(cfg, &img); err != nil {
|
|
||||||
return nil, nil, errors.Wrapf(err, "failed to unmarshal image config")
|
|
||||||
}
|
|
||||||
inp.Image.CreatedTime = img.Created.Format(time.RFC3339)
|
|
||||||
inp.Image.Labels = img.Config.Labels
|
|
||||||
inp.Image.Env = img.Config.Env
|
|
||||||
inp.Image.User = img.Config.User
|
|
||||||
inp.Image.Volumes = make([]string, 0, len(img.Config.Volumes))
|
|
||||||
for v := range img.Config.Volumes {
|
|
||||||
inp.Image.Volumes = append(inp.Image.Volumes, v)
|
|
||||||
}
|
|
||||||
inp.Image.WorkingDir = img.Config.WorkingDir
|
|
||||||
} else {
|
|
||||||
unknowns = append(unknowns, withPrefix(configFields, "input.image.")...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ac := req.Source.Image.AttestationChain; ac != nil {
|
|
||||||
inp.Image.HasProvenance = ac.AttestationManifest != ""
|
|
||||||
signatures, err := p.parseSignatures(ctx, ac, &pl)
|
|
||||||
if err != nil {
|
|
||||||
debugf("failed to parse image signatures: %v", err)
|
|
||||||
} else {
|
|
||||||
inp.Image.Signatures = signatures
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unknowns = append(unknowns, "input.image.hasProvenance", "input.image.signatures")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "local":
|
|
||||||
inp.Local = &Local{
|
|
||||||
Name: refstr,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// oci-layout not supported yet
|
|
||||||
return nil, nil, errors.Errorf("unsupported source scheme: %s", scheme)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inp, unknowns, err := SourceToInput(ctx, p.opt.VerifierProvider, src, platform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrapf(err, "failed to convert source to policy input")
|
||||||
|
}
|
||||||
|
inp.Env = p.opt.Env
|
||||||
|
|
||||||
caps := &ast.Capabilities{
|
caps := &ast.Capabilities{
|
||||||
Builtins: builtins(),
|
Builtins: builtins(),
|
||||||
Features: slices.Clone(ast.Features),
|
Features: slices.Clone(ast.Features),
|
||||||
@@ -509,48 +242,10 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
|
|||||||
Source: req.Source.Source,
|
Source: req.Source.Source,
|
||||||
Platform: req.Platform,
|
Platform: req.Platform,
|
||||||
}
|
}
|
||||||
unk2 := make([]string, 0, len(unk))
|
if err := AddUnknowns(next, unk); err != nil {
|
||||||
for _, u := range unk {
|
return nil, nil, err
|
||||||
k := strings.TrimPrefix(u, "input.")
|
|
||||||
k = trimKey(k)
|
|
||||||
switch k {
|
|
||||||
case "image", "git", "http", "local":
|
|
||||||
// parents are returned as unknowns for some reason, ignore
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
unk2 = append(unk2, k)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(unk2) > 0 {
|
if next.Image != nil || next.Git != nil {
|
||||||
debugf("collected unknowns: %+v", unk2)
|
|
||||||
for _, u := range unk2 {
|
|
||||||
switch u {
|
|
||||||
case "image.labels", "image.user", "image.volumes", "image.workingDir", "image.env":
|
|
||||||
if next.Image == nil {
|
|
||||||
next.Image = &gwpb.ResolveSourceImageRequest{}
|
|
||||||
}
|
|
||||||
next.Image.NoConfig = false
|
|
||||||
case "image.hasProvenance", "image.signatures":
|
|
||||||
if next.Image == nil {
|
|
||||||
next.Image = &gwpb.ResolveSourceImageRequest{
|
|
||||||
NoConfig: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next.Image.AttestationChain = true
|
|
||||||
case "image.checksum":
|
|
||||||
|
|
||||||
case "git.ref", "git.checksum", "git.commitChecksum", "git.isAnnotatedTag", "git.isSHA256", "git.tagName", "git.branch":
|
|
||||||
|
|
||||||
case "git.commit", "git.tag":
|
|
||||||
if next.Git == nil {
|
|
||||||
next.Git = &gwpb.ResolveSourceGitRequest{}
|
|
||||||
}
|
|
||||||
next.Git.ReturnObject = true
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, nil, errors.Errorf("unhandled unknown property %s", u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debugf("next resolve meta request: %+v", next)
|
debugf("next resolve meta request: %+v", next)
|
||||||
return nil, next, nil
|
return nil, next, nil
|
||||||
}
|
}
|
||||||
@@ -629,6 +324,261 @@ func (p *Policy) Print(ctx print.Context, msg string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src *gwpb.ResolveSourceMetaResponse, platform *ocispecs.Platform) (Input, []string, error) {
|
||||||
|
var inp Input
|
||||||
|
var unknowns []string
|
||||||
|
|
||||||
|
if src == nil || src.Source == nil {
|
||||||
|
return inp, nil, errors.Errorf("no source info in request")
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme, refstr, ok := strings.Cut(src.Source.Identifier, "://")
|
||||||
|
if !ok {
|
||||||
|
return inp, nil, errors.Errorf("invalid source identifier: %s", src.Source.Identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch scheme {
|
||||||
|
case "http", "https":
|
||||||
|
u, err := url.Parse(src.Source.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, errors.Wrapf(err, "failed to parse http source url")
|
||||||
|
}
|
||||||
|
inp.HTTP = &HTTP{
|
||||||
|
URL: src.Source.Identifier,
|
||||||
|
Schema: scheme,
|
||||||
|
Host: u.Host,
|
||||||
|
Path: u.Path,
|
||||||
|
Query: u.Query(),
|
||||||
|
}
|
||||||
|
if _, ok := src.Source.Attrs[pb.AttrHTTPAuthHeaderSecret]; ok {
|
||||||
|
inp.HTTP.HasAuth = true
|
||||||
|
}
|
||||||
|
if src.Image == nil {
|
||||||
|
unknowns = append(unknowns, "input.http.checksum")
|
||||||
|
} else {
|
||||||
|
inp.HTTP.Checksum = src.Image.Digest
|
||||||
|
}
|
||||||
|
case "git":
|
||||||
|
if !gitutil.IsGitTransport(refstr) {
|
||||||
|
refstr = "https://" + refstr
|
||||||
|
}
|
||||||
|
u, err := gitutil.ParseURL(refstr)
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
g := &Git{
|
||||||
|
Schema: u.Scheme,
|
||||||
|
Remote: u.Remote,
|
||||||
|
Host: u.Host,
|
||||||
|
}
|
||||||
|
var ref string
|
||||||
|
var isFullRef bool
|
||||||
|
if u.Opts != nil {
|
||||||
|
ref = u.Opts.Ref
|
||||||
|
g.Subdir = u.Opts.Subdir
|
||||||
|
if sd := path.Clean(g.Subdir); sd == "/" || sd == "." {
|
||||||
|
g.Subdir = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := src.Source.Attrs[pb.AttrFullRemoteURL]; !ok {
|
||||||
|
if !gitutil.IsGitTransport(v) {
|
||||||
|
v = "https://" + v
|
||||||
|
}
|
||||||
|
u, err := gitutil.ParseURL(v)
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
g.Schema = u.Scheme
|
||||||
|
g.Remote = u.Remote
|
||||||
|
g.Host = u.Host
|
||||||
|
g.FullURL = v
|
||||||
|
}
|
||||||
|
if tag, ok := strings.CutPrefix(g.Ref, "refs/tags/"); ok {
|
||||||
|
g.TagName = tag
|
||||||
|
isFullRef = true
|
||||||
|
}
|
||||||
|
if branch, ok := strings.CutPrefix(g.Ref, "refs/heads/"); ok {
|
||||||
|
g.Branch = branch
|
||||||
|
isFullRef = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if gitutil.IsCommitSHA(ref) {
|
||||||
|
g.IsCommitRef = true
|
||||||
|
g.Checksum = ref
|
||||||
|
g.CommitChecksum = ref
|
||||||
|
isFullRef = true
|
||||||
|
}
|
||||||
|
|
||||||
|
unk := []string{}
|
||||||
|
|
||||||
|
if src.Git == nil {
|
||||||
|
if !isFullRef {
|
||||||
|
unk = append(unk, "tagName", "branch", "ref")
|
||||||
|
} else {
|
||||||
|
g.Ref = ref
|
||||||
|
}
|
||||||
|
if g.Checksum == "" {
|
||||||
|
unk = append(unk, "checksum", "isAnnotatedTag", "commitChecksum", "isSHA256")
|
||||||
|
}
|
||||||
|
unk = append(unk, "tag", "commit")
|
||||||
|
} else {
|
||||||
|
g.Ref = src.Git.Ref
|
||||||
|
if tag, ok := strings.CutPrefix(g.Ref, "refs/tags/"); ok {
|
||||||
|
g.TagName = tag
|
||||||
|
}
|
||||||
|
if branch, ok := strings.CutPrefix(g.Ref, "refs/heads/"); ok {
|
||||||
|
g.Branch = branch
|
||||||
|
}
|
||||||
|
g.Checksum = src.Git.Checksum
|
||||||
|
g.CommitChecksum = src.Git.CommitChecksum
|
||||||
|
if g.CommitChecksum == "" {
|
||||||
|
g.CommitChecksum = g.Checksum
|
||||||
|
}
|
||||||
|
if g.Checksum != g.CommitChecksum {
|
||||||
|
g.IsAnnotatedTag = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(src.Git.CommitObject) == 0 {
|
||||||
|
unk = append(unk, "commit", "tag")
|
||||||
|
} else {
|
||||||
|
obj, err := gitobject.Parse(src.Git.CommitObject)
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
if err := obj.VerifyChecksum(g.CommitChecksum); err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
c, err := obj.ToCommit()
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
g.Commit = &Commit{
|
||||||
|
Tree: c.Tree,
|
||||||
|
Message: c.Message,
|
||||||
|
Parents: c.Parents,
|
||||||
|
Author: Actor(c.Author),
|
||||||
|
Committer: Actor(c.Committer),
|
||||||
|
obj: obj,
|
||||||
|
}
|
||||||
|
s := parseGitSignature(obj)
|
||||||
|
g.Commit.PGPSignature = s.PGPSignature
|
||||||
|
g.Commit.SSHSignature = s.SSHSignature
|
||||||
|
|
||||||
|
if dt := src.Git.TagObject; len(dt) > 0 {
|
||||||
|
obj, err := gitobject.Parse(src.Git.TagObject)
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
if err := obj.VerifyChecksum(g.Checksum); err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
t, err := obj.ToTag()
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, err
|
||||||
|
}
|
||||||
|
g.Tag = &Tag{
|
||||||
|
Object: t.Object,
|
||||||
|
Message: t.Message,
|
||||||
|
Type: t.Type,
|
||||||
|
Tag: t.Tag,
|
||||||
|
Tagger: Actor(t.Tagger),
|
||||||
|
obj: obj,
|
||||||
|
}
|
||||||
|
s := parseGitSignature(obj)
|
||||||
|
g.Tag.PGPSignature = s.PGPSignature
|
||||||
|
g.Tag.SSHSignature = s.SSHSignature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Checksum) == 64 {
|
||||||
|
g.IsSHA256 = true
|
||||||
|
}
|
||||||
|
|
||||||
|
unknowns = append(unknowns, withPrefix(unk, "input.git.")...)
|
||||||
|
inp.Git = g
|
||||||
|
case "docker-image":
|
||||||
|
ref, err := reference.ParseNormalizedNamed(refstr)
|
||||||
|
if err != nil {
|
||||||
|
return inp, nil, errors.Wrapf(err, "failed to parse image source reference")
|
||||||
|
}
|
||||||
|
inp.Image = &Image{
|
||||||
|
Ref: ref.String(),
|
||||||
|
Host: reference.Domain(ref),
|
||||||
|
Repo: reference.FamiliarName(ref),
|
||||||
|
FullRepo: ref.Name(),
|
||||||
|
}
|
||||||
|
if digested, ok := ref.(reference.Canonical); ok {
|
||||||
|
inp.Image.Checksum = digested.Digest().String()
|
||||||
|
inp.Image.IsCanonical = true
|
||||||
|
}
|
||||||
|
if tagged, ok := ref.(reference.Tagged); ok {
|
||||||
|
inp.Image.Tag = tagged.Tag()
|
||||||
|
}
|
||||||
|
if platform == nil {
|
||||||
|
return inp, nil, errors.Errorf("platform required for image source")
|
||||||
|
}
|
||||||
|
inp.Image.Platform = platforms.Format(*platform)
|
||||||
|
inp.Image.OS = platform.OS
|
||||||
|
inp.Image.Architecture = platform.Architecture
|
||||||
|
inp.Image.Variant = platform.Variant
|
||||||
|
|
||||||
|
configFields := []string{
|
||||||
|
"labels", "user", "volumes", "workingDir", "env",
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Image == nil {
|
||||||
|
if !inp.Image.IsCanonical {
|
||||||
|
unknowns = append(unknowns, "input.image.checksum")
|
||||||
|
}
|
||||||
|
unknowns = append(unknowns, withPrefix(configFields, "input.image.")...)
|
||||||
|
unknowns = append(unknowns, "input.image.hasProvenance", "input.image.signatures")
|
||||||
|
} else {
|
||||||
|
inp.Image.Checksum = src.Image.Digest
|
||||||
|
if cfg := src.Image.Config; cfg != nil {
|
||||||
|
var img ocispecs.Image
|
||||||
|
if err := json.Unmarshal(cfg, &img); err != nil {
|
||||||
|
return inp, nil, errors.Wrapf(err, "failed to unmarshal image config")
|
||||||
|
}
|
||||||
|
inp.Image.CreatedTime = img.Created.Format(time.RFC3339)
|
||||||
|
inp.Image.Labels = img.Config.Labels
|
||||||
|
inp.Image.Env = img.Config.Env
|
||||||
|
inp.Image.User = img.Config.User
|
||||||
|
inp.Image.Volumes = make([]string, 0, len(img.Config.Volumes))
|
||||||
|
for v := range img.Config.Volumes {
|
||||||
|
inp.Image.Volumes = append(inp.Image.Volumes, v)
|
||||||
|
}
|
||||||
|
inp.Image.WorkingDir = img.Config.WorkingDir
|
||||||
|
} else {
|
||||||
|
unknowns = append(unknowns, withPrefix(configFields, "input.image.")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac := src.Image.AttestationChain; ac != nil {
|
||||||
|
inp.Image.HasProvenance = ac.AttestationManifest != ""
|
||||||
|
if getVerifier != nil {
|
||||||
|
signatures, err := parseSignatures(ctx, getVerifier, ac, platform)
|
||||||
|
if err != nil {
|
||||||
|
debugf("failed to parse image signatures: %v", err)
|
||||||
|
} else {
|
||||||
|
inp.Image.Signatures = signatures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unknowns = append(unknowns, "input.image.hasProvenance", "input.image.signatures")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "local":
|
||||||
|
inp.Local = &Local{
|
||||||
|
Name: refstr,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// oci-layout not supported yet
|
||||||
|
return inp, nil, errors.Errorf("unsupported source scheme: %s", scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
return inp, unknowns, nil
|
||||||
|
}
|
||||||
|
|
||||||
func withPrefix(arr []string, prefix string) []string {
|
func withPrefix(arr []string, prefix string) []string {
|
||||||
out := make([]string, len(arr))
|
out := make([]string, len(arr))
|
||||||
for i, s := range arr {
|
for i, s := range arr {
|
||||||
@@ -637,6 +587,54 @@ func withPrefix(arr []string, prefix string) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddUnknowns(req *gwpb.ResolveSourceMetaRequest, unk []string) error {
|
||||||
|
unk2 := make([]string, 0, len(unk))
|
||||||
|
for _, u := range unk {
|
||||||
|
k := strings.TrimPrefix(u, "input.")
|
||||||
|
k = trimKey(k)
|
||||||
|
switch k {
|
||||||
|
case "image", "git", "http", "local":
|
||||||
|
// parents are returned as unknowns for some reason, ignore
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
unk2 = append(unk2, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(unk2) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
debugf("collected unknowns: %+v", unk2)
|
||||||
|
for _, u := range unk2 {
|
||||||
|
switch u {
|
||||||
|
case "image.checksum", "image.labels", "image.user", "image.volumes", "image.workingDir", "image.env":
|
||||||
|
if req.Image == nil {
|
||||||
|
req.Image = &gwpb.ResolveSourceImageRequest{}
|
||||||
|
}
|
||||||
|
req.Image.NoConfig = false
|
||||||
|
case "image.hasProvenance", "image.signatures":
|
||||||
|
if req.Image == nil {
|
||||||
|
req.Image = &gwpb.ResolveSourceImageRequest{
|
||||||
|
NoConfig: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.Image.AttestationChain = true
|
||||||
|
|
||||||
|
case "git.ref", "git.checksum", "git.commitChecksum", "git.isAnnotatedTag", "git.isSHA256", "git.tagName", "git.branch":
|
||||||
|
|
||||||
|
case "git.commit", "git.tag":
|
||||||
|
if req.Git == nil {
|
||||||
|
req.Git = &gwpb.ResolveSourceGitRequest{}
|
||||||
|
}
|
||||||
|
req.Git.ReturnObject = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unhandled unknown property %s", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectUnknowns(mods []*ast.Module) []string {
|
func collectUnknowns(mods []*ast.Module) []string {
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
var out []string
|
var out []string
|
||||||
|
|||||||
Reference in New Issue
Block a user