From 0919e1d01d7eef93642e8c6d08e16e119737947f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Tue, 26 Nov 2024 22:35:40 +0100 Subject: [PATCH] Support custom constraint (templates) --- go.mod | 11 ++- go.sum | 22 ++++- internal/commands/constraint_template.tpl | 20 ++++ .../commands/constrainttemplate_template.tpl | 15 +++ internal/commands/create.go | 97 +++++++++++++++---- internal/commands/document.go | 3 +- 6 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 internal/commands/constraint_template.tpl create mode 100644 internal/commands/constrainttemplate_template.tpl diff --git a/go.mod b/go.mod index b0b963fb..e5bcab18 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/plexsystems/konstraint go 1.23.0 require ( + github.com/Masterminds/sprig/v3 v3.3.0 github.com/open-policy-agent/frameworks/constraint v0.0.0-20220218180203-c2a0d8cdf85a github.com/open-policy-agent/opa v0.69.0 github.com/sirupsen/logrus v1.9.3 @@ -15,6 +16,9 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect @@ -38,6 +42,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -45,7 +50,9 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -57,9 +64,10 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -67,6 +75,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect diff --git a/go.sum b/go.sum index b621a3d3..96f385ee 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,11 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -90,6 +98,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -115,8 +125,12 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -160,14 +174,16 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -234,6 +250,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/internal/commands/constraint_template.tpl b/internal/commands/constraint_template.tpl new file mode 100644 index 00000000..e6bf4881 --- /dev/null +++ b/internal/commands/constraint_template.tpl @@ -0,0 +1,20 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: {{ .Kind }} +metadata: + {{- if .Annotations }} + annotations: {{- range $key, $value := .Annotations }} + {{ $key }}: {{ $value }} + {{ end -}} + {{ end -}} + {{- if .Labels }} + labels: {{- range $key, $value := .Labels }} + {{ $key }}: {{ $value }} + {{ end -}} + {{ end -}} + name: {{ .Name }} +spec: + match: + kinds: + {{- if ne .Enforcement "deny" }} + enforcementAction: {{ .Enforcement }} + {{- end -}} diff --git a/internal/commands/constrainttemplate_template.tpl b/internal/commands/constrainttemplate_template.tpl new file mode 100644 index 00000000..e4f7fce6 --- /dev/null +++ b/internal/commands/constrainttemplate_template.tpl @@ -0,0 +1,15 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: {{ .Name }} +spec: + crd: + spec: + names: + kind: {{ .Kind }} + targets: + - libs: {{- range .Dependencies }} + - |- {{- . | nindent 6 -}} + {{ end }} + rego: |- {{- .Source | nindent 6 }} + target: admission.k8s.gatekeeper.sh diff --git a/internal/commands/create.go b/internal/commands/create.go index 334dfb1f..b02ac4af 100644 --- a/internal/commands/create.go +++ b/internal/commands/create.go @@ -1,13 +1,16 @@ package commands import ( + "bytes" "encoding/json" "fmt" "os" "path/filepath" + "text/template" "github.com/plexsystems/konstraint/internal/rego" + "github.com/Masterminds/sprig/v3" v1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1" "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" log "github.com/sirupsen/logrus" @@ -50,10 +53,21 @@ Create constraints with the Gatekeeper enforcement action set to dryrun if err := viper.BindPFlag("constraint-template-version", cmd.PersistentFlags().Lookup("constraint-template-version")); err != nil { return fmt.Errorf("bind constraint-template-version flag: %w", err) } + if err := viper.BindPFlag("constraint-template-custom-template-file", cmd.PersistentFlags().Lookup("constraint-template-custom-template-file")); err != nil { + return fmt.Errorf("bind constraint-template-custom-template-file flag: %w", err) + } + if err := viper.BindPFlag("constraint-custom-template-file", cmd.PersistentFlags().Lookup("constraint-custom-template-file")); err != nil { + return fmt.Errorf("bind constraint-custom-template-file flag: %w", err) + } + if err := viper.BindPFlag("partial-constraints", cmd.PersistentFlags().Lookup("partial-constraints")); err != nil { return fmt.Errorf("bind partial-constraints flag: %w", err) } + if cmd.PersistentFlags().Lookup("constraint-template-custom-template-file").Changed && cmd.PersistentFlags().Lookup("constraint-template-version").Changed { + return fmt.Errorf("need to set either constraint-template-custom-template-file or constraint-template-version") + } + path := "." if len(args) > 0 { path = args[0] @@ -68,6 +82,8 @@ Create constraints with the Gatekeeper enforcement action set to dryrun cmd.PersistentFlags().Bool("skip-constraints", false, "Skip generation of constraints") cmd.PersistentFlags().String("constraint-template-version", "v1beta1", "Set the version of ConstraintTemplates") cmd.PersistentFlags().Bool("partial-constraints", false, "Generate partial Constraints for policies with parameters") + cmd.PersistentFlags().String("constraint-template-custom-template-file", "", "Path to a custom template file to generate constraint templates") + cmd.PersistentFlags().String("constraint-custom-template-file", "", "Path to a custom template file to generate constraints") return &cmd } @@ -107,21 +123,36 @@ func runCreateCommand(path string) error { } constraintTemplateVersion := viper.GetString("constraint-template-version") + constraintTemplateCustomTemplateFile := viper.GetString("constraint-template-custom-template-file") + var constraintTemplate any - switch constraintTemplateVersion { - case "v1": - constraintTemplate = getConstraintTemplatev1(violation, logger) - case "v1beta1": - constraintTemplate = getConstraintTemplatev1beta1(violation, logger) - default: - return fmt.Errorf("unsupported API version for constrainttemplate: %s", constraintTemplateVersion) - } + var constraintTemplateBytes []byte - constraintTemplateBytes, err := yaml.Marshal(constraintTemplate) - if err != nil { - return fmt.Errorf("marshal constrainttemplate: %w", err) - } + if constraintTemplateCustomTemplateFile != "" { + customTemplate, err := os.ReadFile(constraintTemplateCustomTemplateFile) + if err != nil { + return fmt.Errorf("unable to open/read template file: %w", err) + } + constraintTemplateBytes, err = renderTemplate(violation, customTemplate, logger) + if err != nil { + return fmt.Errorf("unable to render custom template: %w", err) + } + } else { + switch constraintTemplateVersion { + case "v1": + constraintTemplate = getConstraintTemplatev1(violation, logger) + case "v1beta1": + constraintTemplate = getConstraintTemplatev1beta1(violation, logger) + default: + return fmt.Errorf("unsupported API version for constrainttemplate: %s", constraintTemplateVersion) + } + constraintTemplateBytes, err = yaml.Marshal(constraintTemplate) + if err != nil { + return fmt.Errorf("marshal constrainttemplate: %w", err) + } + + } if err := os.WriteFile(filepath.Join(outputDir, templateFileName), constraintTemplateBytes, 0644); err != nil { return fmt.Errorf("writing template: %w", err) } @@ -137,16 +168,28 @@ func runCreateCommand(path string) error { continue } - constraint, err := getConstraint(violation, logger) - if err != nil { - return fmt.Errorf("get constraint: %w", err) - } + constraintCustomTemplateFile := viper.GetString("constraint-custom-template-file") + var constraintBytes []byte + if constraintCustomTemplateFile != "" { + customTemplate, err := os.ReadFile(constraintCustomTemplateFile) + if err != nil { + return fmt.Errorf("unable to open/read template file: %w", err) + } + constraintBytes, err = renderTemplate(violation, customTemplate, logger) + if err != nil { + return fmt.Errorf("unable to render custom constraint: %w", err) + } + } else { + constraint, err := getConstraint(violation, logger) + if err != nil { + return fmt.Errorf("get constraint: %w", err) + } - constraintBytes, err := yaml.Marshal(constraint) - if err != nil { - return fmt.Errorf("marshal constraint: %w", err) + constraintBytes, err = yaml.Marshal(constraint) + if err != nil { + return fmt.Errorf("marshal constraint: %w", err) + } } - if err := os.WriteFile(filepath.Join(outputDir, constraintFileName), constraintBytes, 0644); err != nil { return fmt.Errorf("writing constraint: %w", err) } @@ -157,6 +200,20 @@ func runCreateCommand(path string) error { return nil } +func renderTemplate(violation rego.Rego, appliedTemplate []byte, _ *log.Entry) ([]byte, error) { + t, err := template.New("template").Funcs(sprig.FuncMap()).Parse(string(appliedTemplate)) + if err != nil { + return nil, fmt.Errorf("parsing template: %w", err) + } + buf := new(bytes.Buffer) + + if err := t.Execute(buf, violation); err != nil { + return nil, fmt.Errorf("executing template: %w", err) + } + + return buf.Bytes(), nil +} + func getConstraintTemplatev1(violation rego.Rego, logger *log.Entry) *v1.ConstraintTemplate { constraintTemplate := v1.ConstraintTemplate{ TypeMeta: metav1.TypeMeta{ diff --git a/internal/commands/document.go b/internal/commands/document.go index 294c8ecc..96ffe2ba 100644 --- a/internal/commands/document.go +++ b/internal/commands/document.go @@ -11,6 +11,7 @@ import ( "github.com/plexsystems/konstraint/internal/rego" + "github.com/Masterminds/sprig/v3" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -111,7 +112,7 @@ func runDocCommand(path string) error { appliedTemplate = string(b) } - t, err := template.New("docs").Parse(appliedTemplate) + t, err := template.New("docs").Funcs(sprig.FuncMap()).Parse(appliedTemplate) if err != nil { return fmt.Errorf("parsing template: %w", err)