policy: add helper modules loading

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-01-14 09:03:42 -08:00
parent 7247c1e38e
commit 70da643c58
4 changed files with 102 additions and 13 deletions
+8 -2
View File
@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/fs"
"maps"
"os"
"slices"
@@ -115,7 +116,12 @@ type Inputs struct {
DockerfileMappingSrc string
DockerfileMappingDst string
policy []policy.File
policy *policyOpt
}
type policyOpt struct {
Files []policy.File
FS func() (fs.StatFS, func() error, error)
}
type NamedContext struct {
@@ -929,7 +935,7 @@ func detectSharedMounts(ctx context.Context, reqs map[string][]*reqForNode) (_ m
}
fsMap := m[nodeName]
for name, m := range req.so.LocalMounts {
fs, ok := m.(*fs)
fs, ok := m.(*fsMount)
if !ok {
continue
}
+1 -1
View File
@@ -135,7 +135,7 @@ func getGitAttributes(ctx context.Context, contextPath, dockerfilePath string) (
}
for key, mount := range so.LocalMounts {
fs, ok := mount.(*fs)
fs, ok := mount.(*fsMount)
if !ok {
continue
}
+34 -9
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"io"
"io/fs"
"log"
"maps"
"os"
@@ -326,7 +327,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
}
defers = append(defers, releaseLoad)
if len(opt.Inputs.policy) > 0 {
if opt.Inputs.policy != nil {
env := policy.Env{}
for k, v := range opt.BuildArgs {
if env.Args == nil {
@@ -338,11 +339,12 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
env.Target = opt.Target
env.Labels = opt.Labels
p := policy.NewPolicy(policy.Opt{
Files: opt.Inputs.policy,
Files: opt.Inputs.policy.Files,
Env: env,
Log: func(msg string) {
log.Printf("[policy] %s", msg)
},
FS: opt.Inputs.policy.FS,
})
so.SourcePolicyProvider = policysession.NewPolicyProvider(p.CheckPolicy)
}
@@ -438,6 +440,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
var (
err error
dockerfileReader io.ReadCloser
contextDir string
dockerfileDir string
dockerfileName = inp.DockerfilePath
dockerfileSrcName = inp.DockerfilePath
@@ -479,12 +482,14 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
if err := setLocalMount("context", inp.ContextPath, target); err != nil {
return nil, err
}
contextDir = inp.ContextPath
}
}
case osutil.IsLocalDir(inp.ContextPath):
if err := setLocalMount("context", inp.ContextPath, target); err != nil {
return nil, err
}
contextDir = inp.ContextPath
sharedKey := inp.ContextPath
if p, err := filepath.Abs(sharedKey); err == nil {
sharedKey = filepath.Base(p)
@@ -567,10 +572,30 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
if err != nil {
return nil, errors.Wrapf(err, "failed to read policy file %s.rego", dockerfileName)
}
inp.policy = append(inp.policy, policy.File{
Filename: dockerfileName + ".rego",
Data: dt,
})
inp.policy = &policyOpt{
Files: []policy.File{
{
Filename: dockerfileName + ".rego",
Data: dt,
},
},
FS: func() (fs.StatFS, func() error, error) {
if contextDir == "" {
return nil, nil, errors.Errorf("unimplemented, cannot use policy file without a local build context")
}
root, err := os.OpenRoot(contextDir)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to open root for policy file %s.rego", dockerfileName)
}
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
},
}
}
}
}
@@ -687,7 +712,7 @@ func setLocalMount(name, dir string, so *client.SolveOpt) error {
if so.LocalMounts == nil {
so.LocalMounts = map[string]fsutil.FS{}
}
so.LocalMounts[name] = &fs{FS: lm, dir: dir}
so.LocalMounts[name] = &fsMount{FS: lm, dir: dir}
return nil
}
@@ -799,12 +824,12 @@ func handleLowercaseDockerfile(dir, p string) string {
return p
}
type fs struct {
type fsMount struct {
fsutil.FS
dir string
}
var _ fsutil.FS = &fs{}
var _ fsutil.FS = &fsMount{}
func CreateSSH(ssh []*buildflags.SSH) (session.Attachable, error) {
configs := make([]sshprovider.AgentConfig, 0, len(ssh))
+59 -1
View File
@@ -3,6 +3,7 @@ package policy
import (
"context"
"encoding/json"
"io/fs"
"log"
"net/url"
"os"
@@ -51,6 +52,7 @@ type Opt struct {
Files []File
Env Env
Log func(string)
FS func() (fs.StatFS, func() error, error)
}
var _ policysession.PolicyCallback = (&Policy{}).CheckPolicy
@@ -320,11 +322,67 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
Features: slices.Clone(ast.Features),
}
comp := ast.NewCompiler().WithCapabilities(caps)
comp := ast.NewCompiler().WithCapabilities(caps).WithKeepModules(true)
if p.opt.Log != nil {
comp = comp.WithEnablePrintStatements(true)
}
var root fs.StatFS
var closeFS func() error
defer func() {
if closeFS != nil {
closeFS()
}
}()
comp = comp.WithModuleLoader(func(resolved map[string]*ast.Module) (parsed map[string]*ast.Module, err error) {
out := make(map[string]*ast.Module)
for k, v := range resolved {
for _, imp := range v.Imports {
pv := imp.Path.Value.String()
pkgPath, ok := strings.CutPrefix(pv, "data.")
if !ok {
continue
}
fn := strings.ReplaceAll(pkgPath, ".", "/") + ".rego"
if _, ok := resolved[fn]; !ok {
if root == nil {
if p.opt.FS == nil {
return nil, errors.Errorf("no policy FS defined for import %s", pv)
}
f, cf, err := p.opt.FS()
if err != nil {
return nil, errors.Wrapf(err, "failed to get policy FS for import %s", pv)
}
root = f
closeFS = cf
}
if _, err := root.Stat(fn); err != nil {
return nil, errors.Wrapf(err, "import %s not found for module %s", pv, k)
}
dt, err := fs.ReadFile(root, fn)
if err != nil {
return nil, errors.Wrapf(err, "failed to read imported policy file %s for module %s", fn, k)
}
mod, err := ast.ParseModule(fn, string(dt))
if err != nil {
return nil, errors.Wrapf(err, "failed to parse imported policy file %s for module %s", fn, k)
}
// rewrite package to be less strict
pkgParts := strings.Split(pkgPath, ".")
ref := ast.Ref{mod.Package.Path[0]}
for _, p := range pkgParts {
ref = append(ref, ast.StringTerm(p))
}
mod.Package = &ast.Package{Path: ref}
out[fn] = mod
}
}
}
return out, nil
})
opts := []func(*rego.Rego){
rego.SetRegoVersion(ast.RegoV1),
rego.Query("data.docker.decision"),