policy: add helper modules loading
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+8
-2
@@ -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
@@ -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
@@ -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
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user