diff --git a/go.mod b/go.mod index df4d810bc9..2e8ebcdb7f 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/minio/minio v0.0.0-20200114012931-30922148fbb5 github.com/onsi/ginkgo v1.11.0 github.com/onsi/gomega v1.8.1 + github.com/ory/go-acc v0.2.6 // indirect github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.6.0 // indirect github.com/spf13/cobra v1.0.0 diff --git a/go.sum b/go.sum index 835f1b4a2f..af7c05702f 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/siphash v1.1.0 h1:1Rs9eTUlZLPBEvV+2sTaM8O0NWn0ppbgqS7p11aWawI= github.com/dchest/siphash v1.1.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -428,6 +432,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -484,6 +490,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/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= @@ -530,12 +538,18 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/ory/go-acc v0.2.6 h1:YfI+L9dxI7QCtWn2RbawqO0vXhiThdXu/RgizJBbaq0= +github.com/ory/go-acc v0.2.6/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= +github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= +github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -608,6 +622,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= @@ -616,11 +631,15 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -641,6 +660,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE= github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0= github.com/tidwall/gjson v1.3.5/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= @@ -712,6 +733,7 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -792,6 +814,8 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -880,6 +904,9 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go index a579fd278d..2edddf3177 100644 --- a/pkg/engine/mutation_test.go +++ b/pkg/engine/mutation_test.go @@ -160,7 +160,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) { Context: ctx, NewResource: *resourceUnstructured} er := Mutate(policyContext) - expectedErrorStr := "variable request.object.metadata.name1 not found (path: /spec/name)" + expectedErrorStr := "variable request.object.metadata.name1 not resolved at path /spec/name" t.Log(er.PolicyResponse.Rules[0].Message) assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr) } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 4d2894a980..bf79244e3f 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -3,6 +3,7 @@ package engine import ( "fmt" "reflect" + "strings" "time" "github.com/go-logr/logr" @@ -246,8 +247,8 @@ func isSameRules(oldRules []response.RuleResponse, newRules []response.RuleRespo // validatePatterns validate pattern and anyPattern func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstructured.Unstructured, rule kyverno.Rule) (resp response.RuleResponse) { startTime := time.Now() - logger := log.WithValues("rule", rule.Name) - logger.V(4).Info("start processing rule", "startTime", startTime) + logger := log.WithValues("rule", rule.Name, "name", resource.GetName(), "kind", resource.GetKind()) + logger.V(5).Info("start processing rule", "startTime", startTime) resp.Name = rule.Name resp.Type = utils.Validation.String() defer func() { @@ -255,34 +256,26 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr logger.V(4).Info("finished processing rule", "processingTime", resp.RuleStats.ProcessingTime.String()) }() - // work on a copy of validation rule validationRule := rule.Validation.DeepCopy() - - // either pattern or anyPattern can be specified in Validation rule if validationRule.Pattern != nil { - // substitute variables in the pattern pattern := validationRule.Pattern var err error if pattern, err = variables.SubstituteVars(logger, ctx, pattern); err != nil { - // variable substitution failed resp.Success = false - resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed. '%s'", - rule.Validation.Message, rule.Name, err) + resp.Message = fmt.Sprintf("variable substitution failed for rule %s: %s", rule.Name, err.Error()) return resp } if path, err := validate.ValidateResourceWithPattern(logger, resource.Object, pattern); err != nil { - // validation failed - logger.V(5).Info(err.Error()) + logger.V(3).Info("validation failed", "path", path, "error", err.Error()) resp.Success = false - resp.Message = fmt.Sprintf("Validation error: %s; Validation rule %s failed at path %s", - rule.Validation.Message, rule.Name, path) + resp.Message = buildErrorMessage(rule, path) return resp } logger.V(4).Info("successfully processed rule") resp.Success = true - resp.Message = fmt.Sprintf("Validation rule '%s' succeeded.", rule.Name) + resp.Message = fmt.Sprintf("validation rule '%s' passed.", rule.Name) return resp } @@ -294,31 +287,32 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr anyPatterns, err := rule.Validation.DeserializeAnyPattern() if err != nil { resp.Success = false - resp.Message = fmt.Sprintf("Failed to deserialize anyPattern, expect type array: %v", err) + resp.Message = fmt.Sprintf("failed to deserialize anyPattern, expected type array: %v", err) return resp } for idx, pattern := range anyPatterns { if pattern, err = variables.SubstituteVars(logger, ctx, pattern); err != nil { - // variable substitution failed failedSubstitutionsErrors = append(failedSubstitutionsErrors, err) continue } - _, err := validate.ValidateResourceWithPattern(logger, resource.Object, pattern) + + path, err := validate.ValidateResourceWithPattern(logger, resource.Object, pattern) if err == nil { resp.Success = true - resp.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, idx) + resp.Message = fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", rule.Name, idx) return resp } - logger.V(4).Info(fmt.Sprintf("validation rule failed for anyPattern[%d]", idx), "message", rule.Validation.Message) - patternErr := fmt.Errorf("anyPattern[%d] failed; %s", idx, err) + + logger.V(4).Info("validation rule failed", "anyPattern[%d]", idx, "path", path) + patternErr := fmt.Errorf("Rule %s[%d] failed at path %s.", rule.Name, idx, path) failedAnyPatternsErrors = append(failedAnyPatternsErrors, patternErr) } // Substitution failures if len(failedSubstitutionsErrors) > 0 { resp.Success = false - resp.Message = fmt.Sprintf("Substitutions failed: %v", failedSubstitutionsErrors) + resp.Message = fmt.Sprintf("failed to substitute variables: %v", failedSubstitutionsErrors) return resp } @@ -328,15 +322,38 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr for _, err := range failedAnyPatternsErrors { errorStr = append(errorStr, err.Error()) } - resp.Success = false + log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", rule.Name, errorStr)) - if rule.Validation.Message == "" { - resp.Message = fmt.Sprintf("Validation rule '%s' has failed", rule.Name) - } else { - resp.Message = rule.Validation.Message - } + + resp.Success = false + resp.Message = buildAnyPatternErrorMessage(rule, errorStr) return resp } } return response.RuleResponse{} } + +func buildErrorMessage(rule kyverno.Rule, path string) string { + if rule.Validation.Message == "" { + return fmt.Sprintf("validation error: rule %s failed at path %s", rule.Name, path) + } + + if strings.HasSuffix(rule.Validation.Message, ".") { + return fmt.Sprintf("validation error: %s Rule %s failed at path %s", rule.Validation.Message, rule.Name, path) + } + + return fmt.Sprintf("validation error: %s. Rule %s failed at path %s", rule.Validation.Message, rule.Name, path) +} + +func buildAnyPatternErrorMessage(rule kyverno.Rule, errors []string) string { + errStr := strings.Join(errors, " ") + if rule.Validation.Message == "" { + return fmt.Sprintf("validation error: %s", errStr) + } + + if strings.HasSuffix(rule.Validation.Message, ".") { + return fmt.Sprintf("validation error: %s %s", rule.Validation.Message, errStr) + } + + return fmt.Sprintf("validation error: %s. %s", rule.Validation.Message, errStr) +} diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index f99d58affe..76bdb21a79 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -124,8 +124,8 @@ func TestValidate_image_tag_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) msgs := []string{ - "Validation rule 'validate-tag' succeeded.", - "Validation error: imagePullPolicy 'Always' required with tag 'latest'; Validation rule validate-latest failed at path /spec/containers/0/imagePullPolicy/", + "validation rule 'validate-tag' passed.", + "validation error: imagePullPolicy 'Always' required with tag 'latest'. Rule validate-latest failed at path /spec/containers/0/imagePullPolicy/", } er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) for index, r := range er.PolicyResponse.Rules { @@ -223,8 +223,8 @@ func TestValidate_image_tag_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) msgs := []string{ - "Validation rule 'validate-tag' succeeded.", - "Validation rule 'validate-latest' succeeded.", + "validation rule 'validate-tag' passed.", + "validation rule 'validate-latest' passed.", } er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) for index, r := range er.PolicyResponse.Rules { @@ -301,7 +301,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"A namespace is required"} + msgs := []string{"validation error: A namespace is required. Rule check-default-namespace[0] failed at path /metadata/namespace/. Rule check-default-namespace[1] failed at path /metadata/namespace/."} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -383,7 +383,7 @@ func TestValidate_host_network_port(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: Host network and port are not allowed; Validation rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"} + msgs := []string{"validation error: Host network and port are not allowed. Rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -473,7 +473,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation rule 'validate-host-path' succeeded."} + msgs := []string{"validation rule 'validate-host-path' passed."} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -561,7 +561,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: Host path '/var/lib/' is not allowed; Validation rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"} + msgs := []string{"validation error: Host path '/var/lib/' is not allowed. Rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -631,7 +631,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation rule 'pod rule 2' succeeded."} + msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -704,7 +704,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation rule 'pod rule 2' succeeded."} + msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -777,7 +777,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: pod: validate run as non root user; Validation rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"} + msgs := []string{"validation error: pod: validate run as non root user. Rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -852,7 +852,7 @@ func TestValidate_AnchorList_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation rule 'pod image rule' succeeded."} + msgs := []string{"validation rule 'pod image rule' passed."} for index, r := range er.PolicyResponse.Rules { t.Log(r.Message) @@ -927,11 +927,6 @@ func TestValidate_AnchorList_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - // msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/1/name/' for resource Pod//myapp-pod."} - // for index, r := range er.PolicyResponse.Rules { - // // t.Log(r.Message) - // assert.Equal(t, r.Message, msgs[index]) - // } assert.Assert(t, !er.IsSuccessful()) } @@ -1002,12 +997,6 @@ func TestValidate_existenceAnchor_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - // msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/' for resource Pod//myapp-pod."} - - // for index, r := range er.PolicyResponse.Rules { - // t.Log(r.Message) - // assert.Equal(t, r.Message, msgs[index]) - // } assert.Assert(t, !er.IsSuccessful()) } @@ -1078,7 +1067,7 @@ func TestValidate_existenceAnchor_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation rule 'pod image rule' succeeded."} + msgs := []string{"validation rule 'pod image rule' passed."} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -1166,7 +1155,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: Host path is not allowed; Validation rule validate-host-path failed at path /spec/volumes/0/hostPath/"} + msgs := []string{"validation error: Host path is not allowed. Rule validate-host-path failed at path /spec/volumes/0/hostPath/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -1253,7 +1242,7 @@ func TestValidate_negationAnchor_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation rule 'validate-host-path' succeeded."} + msgs := []string{"validation rule 'validate-host-path' passed."} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -1330,7 +1319,8 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) { NewResource: *resourceUnstructured} er := Validate(policyContext) assert.Assert(t, !er.PolicyResponse.Rules[0].Success) - assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation error: ; Validation rule 'test-path-not-exist' failed. 'variable request.object.metadata.name1 not found (path: /spec/containers/0/name)'") + assert.Equal(t, er.PolicyResponse.Rules[0].Message, + "variable substitution failed for rule test-path-not-exist: variable request.object.metadata.name1 not resolved at path /spec/containers/0/name") } func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *testing.T) { @@ -1421,7 +1411,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *t NewResource: *resourceUnstructured} er := Validate(policyContext) assert.Assert(t, er.PolicyResponse.Rules[0].Success) - assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' anyPattern[1] succeeded.") + assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation rule 'test-path-not-exist' anyPattern[1] passed.") } func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) { @@ -1512,7 +1502,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test NewResource: *resourceUnstructured} er := Validate(policyContext) assert.Assert(t, !er.PolicyResponse.Rules[0].Success) - assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Substitutions failed: [variable request.object.metadata.name1 not found (path: /spec/template/spec/containers/0/name) variable request.object.metadata.name2 not found (path: /spec/template/spec/containers/0/name)]") + assert.Equal(t, er.PolicyResponse.Rules[0].Message, "failed to substitute variables: [variable request.object.metadata.name1 not resolved at path /spec/template/spec/containers/0/name variable request.object.metadata.name2 not resolved at path /spec/template/spec/containers/0/name]") } func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) { @@ -1603,9 +1593,9 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter NewResource: *resourceUnstructured} er := Validate(policyContext) - // expectedMsg := "Validation error: ; Validation rule test-path-not-exist anyPattern[0] failed at path /spec/template/spec/containers/0/name/. Validation rule test-path-not-exist anyPattern[1] failed at path /spec/template/spec/containers/0/name/." assert.Assert(t, !er.PolicyResponse.Rules[0].Success) - assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' has failed") + assert.Equal(t, er.PolicyResponse.Rules[0].Message, + "validation error: Rule test-path-not-exist[0] failed at path /spec/template/spec/containers/0/name/. Rule test-path-not-exist[1] failed at path /spec/template/spec/containers/0/name/.") } func Test_denyFeatureIssue744(t *testing.T) { diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go index aef0ac4811..c035df839b 100644 --- a/pkg/engine/variables/vars.go +++ b/pkg/engine/variables/vars.go @@ -82,7 +82,7 @@ type NotFoundVariableErr struct { } func (n NotFoundVariableErr) Error() string { - return fmt.Sprintf("variable %v not found (path: %v)", n.variable, n.path) + return fmt.Sprintf("variable %s not resolved at path %s", n.variable, n.path) } // subValR resolves the variables if defined diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go index 761dce72f2..22127e9662 100644 --- a/pkg/kyverno/apply/command.go +++ b/pkg/kyverno/apply/command.go @@ -18,7 +18,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/kyverno/common" - sanitizedError "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError" + "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError" "github.com/kyverno/kyverno/pkg/openapi" policy2 "github.com/kyverno/kyverno/pkg/policy" "github.com/kyverno/kyverno/pkg/utils" @@ -59,18 +59,7 @@ type SkippedPolicy struct { Variable string `json:"variable"` } -func Command() *cobra.Command { - var cmd *cobra.Command - var resourcePaths []string - var cluster, policyReport bool - var mutateLogPath, variablesString, valuesFile, namespace string - - kubernetesConfig := genericclioptions.NewConfigFlags(true) - - cmd = &cobra.Command{ - Use: "apply", - Short: "applies policies on resources", - Example: fmt.Sprintf(` +var applyHelp = ` To apply on a resource: kyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --resource=/path/to/resource1 --resource=/path/to/resource2 @@ -112,12 +101,25 @@ To apply policy with variables: : : - More info: https://kyverno.io/docs/kyverno-cli/ - `), +More info: https://kyverno.io/docs/kyverno-cli/ +` + +func Command() *cobra.Command { + var cmd *cobra.Command + var resourcePaths []string + var cluster, policyReport bool + var mutateLogPath, variablesString, valuesFile, namespace string + + kubernetesConfig := genericclioptions.NewConfigFlags(true) + + cmd = &cobra.Command{ + Use: "apply", + Short: "applies policies on resources", + Example: applyHelp, RunE: func(cmd *cobra.Command, policyPaths []string) (err error) { defer func() { if err != nil { - if !sanitizedError.IsErrorSanitized(err) { + if !sanitizederror.IsErrorSanitized(err) { log.Log.Error(err, "failed to sanitize") err = fmt.Errorf("internal error") } @@ -125,20 +127,20 @@ To apply policy with variables: }() if valuesFile != "" && variablesString != "" { - return sanitizedError.NewWithError("pass the values either using set flag or values_file flag", err) + return sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err) } variables, valuesMap, err := getVariable(variablesString, valuesFile) if err != nil { - if !sanitizedError.IsErrorSanitized(err) { - return sanitizedError.NewWithError("failed to decode yaml", err) + if !sanitizederror.IsErrorSanitized(err) { + return sanitizederror.NewWithError("failed to decode yaml", err) } return err } openAPIController, err := openapi.NewOpenAPIController() if err != nil { - return sanitizedError.NewWithError("failed to initialize openAPIController", err) + return sanitizederror.NewWithError("failed to initialize openAPIController", err) } var dClient *client.Client @@ -154,33 +156,41 @@ To apply policy with variables: } if len(policyPaths) == 0 { - return sanitizedError.NewWithError(fmt.Sprintf("require policy"), err) + return sanitizederror.NewWithError(fmt.Sprintf("require policy"), err) } - policies, err := common.ValidateAndGetPolicies(policyPaths) - if err != nil { - if !sanitizedError.IsErrorSanitized(err) { - return sanitizedError.NewWithError("failed to mutate policies.", err) + policies, errors := common.GetPolicies(policyPaths) + if len(policies) == 0 { + if len(errors) > 0 { + return sanitizederror.NewWithErrors("failed to read policies", errors) + } + + return sanitizederror.New(fmt.Sprintf("no policies found in paths %v", policyPaths)) + } + + if len(errors) > 0 && log.Log.V(1).Enabled() { + fmt.Printf("ignoring errors: \n") + for _, e := range errors { + fmt.Printf(" %v \n", e.Error()) } - return err } if len(resourcePaths) == 0 && !cluster { - return sanitizedError.NewWithError(fmt.Sprintf("resource file(s) or cluster required"), err) + return sanitizederror.NewWithError(fmt.Sprintf("resource file(s) or cluster required"), err) } mutateLogPathIsDir, err := checkMutateLogPath(mutateLogPath) if err != nil { - if !sanitizedError.IsErrorSanitized(err) { - return sanitizedError.NewWithError("failed to create file/folder", err) + if !sanitizederror.IsErrorSanitized(err) { + return sanitizederror.NewWithError("failed to create file/folder", err) } return err } mutatedPolicies, err := mutatePolices(policies) if err != nil { - if !sanitizedError.IsErrorSanitized(err) { - return sanitizedError.NewWithError("failed to mutate policy", err) + if !sanitizederror.IsErrorSanitized(err) { + return sanitizederror.NewWithError("failed to mutate policy", err) } } @@ -244,12 +254,12 @@ To apply policy with variables: } if len(common.PolicyHasVariables(*policy)) > 0 && len(thisPolicyResourceValues) == 0 { - return sanitizedError.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err) + return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err) } ers, validateErs, err := applyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, rc, policyReport) if err != nil { - return sanitizedError.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err) + return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err) } engineResponses = append(engineResponses, ers...) validateEngineResponses = append(validateEngineResponses, validateErs) @@ -285,17 +295,17 @@ func getVariable(variablesString, valuesFile string) (variables map[string]strin if valuesFile != "" { yamlFile, err := ioutil.ReadFile(valuesFile) if err != nil { - return variables, valuesMap, sanitizedError.NewWithError("unable to read yaml", err) + return variables, valuesMap, sanitizederror.NewWithError("unable to read yaml", err) } valuesBytes, err := yaml.ToJSON(yamlFile) if err != nil { - return variables, valuesMap, sanitizedError.NewWithError("failed to convert json", err) + return variables, valuesMap, sanitizederror.NewWithError("failed to convert json", err) } values := &Values{} if err := json.Unmarshal(valuesBytes, values); err != nil { - return variables, valuesMap, sanitizedError.NewWithError("failed to decode yaml", err) + return variables, valuesMap, sanitizederror.NewWithError("failed to decode yaml", err) } for _, p := range values.Policies { @@ -323,8 +333,8 @@ func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err erro err := createFileOrFolder(mutateLogPath, mutateLogPathIsDir) if err != nil { - if !sanitizedError.IsErrorSanitized(err) { - return mutateLogPathIsDir, sanitizedError.NewWithError("failed to create file/folder.", err) + if !sanitizederror.IsErrorSanitized(err) { + return mutateLogPathIsDir, sanitizederror.NewWithError("failed to create file/folder.", err) } return mutateLogPathIsDir, err } @@ -345,7 +355,7 @@ func getResourceAccordingToResourcePath(resourcePaths []string, cluster bool, po yamlBytes := []byte(resourceStr) resources, err = common.GetResource(yamlBytes) if err != nil { - return resources, sanitizedError.NewWithError("failed to extract the resources", err) + return resources, sanitizederror.NewWithError("failed to extract the resources", err) } } } else if (len(resourcePaths) > 0 && resourcePaths[0] != "-") || len(resourcePaths) < 0 || cluster { @@ -434,7 +444,7 @@ func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst } else { err := printMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated") if err != nil { - return engineResponses, response.EngineResponse{}, sanitizedError.NewWithError("failed to print mutated result", err) + return engineResponses, response.EngineResponse{}, sanitizederror.NewWithError("failed to print mutated result", err) } fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.") } @@ -503,8 +513,8 @@ func mutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) { for _, policy := range policies { p, err := common.MutatePolicy(policy, logger) if err != nil { - if !sanitizedError.IsErrorSanitized(err) { - return nil, sanitizedError.NewWithError("failed to mutate policy.", err) + if !sanitizederror.IsErrorSanitized(err) { + return nil, sanitizederror.NewWithError("failed to mutate policy.", err) } return nil, err } @@ -557,30 +567,30 @@ func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error { if os.IsNotExist(err) { errDir := os.MkdirAll(folderPath, 0755) if errDir != nil { - return sanitizedError.NewWithError(fmt.Sprintf("failed to create directory"), err) + return sanitizederror.NewWithError(fmt.Sprintf("failed to create directory"), err) } } } file, err := os.OpenFile(mutateLogPath, os.O_RDONLY|os.O_CREATE, 0644) if err != nil { - return sanitizedError.NewWithError(fmt.Sprintf("failed to create file"), err) + return sanitizederror.NewWithError(fmt.Sprintf("failed to create file"), err) } err = file.Close() if err != nil { - return sanitizedError.NewWithError(fmt.Sprintf("failed to close file"), err) + return sanitizederror.NewWithError(fmt.Sprintf("failed to close file"), err) } } else { errDir := os.MkdirAll(mutateLogPath, 0755) if errDir != nil { - return sanitizedError.NewWithError(fmt.Sprintf("failed to create directory"), err) + return sanitizederror.NewWithError(fmt.Sprintf("failed to create directory"), err) } } } else { - return sanitizedError.NewWithError(fmt.Sprintf("failed to describe file"), err) + return sanitizederror.NewWithError(fmt.Sprintf("failed to describe file"), err) } } diff --git a/pkg/kyverno/common/common.go b/pkg/kyverno/common/common.go index 890015e2bd..751c722f39 100644 --- a/pkg/kyverno/common/common.go +++ b/pkg/kyverno/common/common.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "os" "path/filepath" + "sigs.k8s.io/controller-runtime/pkg/log" jsonpatch "github.com/evanphx/json-patch" "github.com/go-logr/logr" @@ -22,62 +23,56 @@ import ( ) // GetPolicies - Extracting the policies from multiple YAML -func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, error error) { +func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) { for _, path := range paths { + log.Log.V(5).Info("reading policies", "path", path) + path = filepath.Clean(path) fileDesc, err := os.Stat(path) if err != nil { - return nil, err + errors = append(errors, err) + continue } + if fileDesc.IsDir() { files, err := ioutil.ReadDir(path) if err != nil { - return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to parse %v", path), err) + errors = append(errors, fmt.Errorf("failed to read %v: %v", path, err.Error())) + continue } + listOfFiles := make([]string, 0) for _, file := range files { - listOfFiles = append(listOfFiles, filepath.Join(path, file.Name())) - } - policiesFromDir, err := GetPolicies(listOfFiles) - if err != nil { - return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to extract policies from %v", listOfFiles), err) - } - - policies = append(policies, policiesFromDir...) - } else { - file, err := ioutil.ReadFile(path) - if err != nil { - return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to load file %v", path), err) - } - getPolicies, getErrors := utils.GetPolicy(file) - var errString string - for _, err := range getErrors { - if err != nil { - errString += err.Error() + "\n" + ext := filepath.Ext(file.Name()) + if ext == "" || ext == ".yaml" || ext == ".yml" { + listOfFiles = append(listOfFiles, filepath.Join(path, file.Name())) } } - if errString != "" { - fmt.Printf("failed to extract policies: %s\n", errString) - os.Exit(2) + + policiesFromDir, errorsFromDir := GetPolicies(listOfFiles) + errors = append(errors, errorsFromDir...) + policies = append(policies, policiesFromDir...) + + } else { + fileBytes, err := ioutil.ReadFile(path) + if err != nil { + errors = append(errors, fmt.Errorf("failed to read %v: %v", path, err.Error())) + continue } - policies = append(policies, getPolicies...) + policiesFromFile, errFromFile := utils.GetPolicy(fileBytes) + if errFromFile != nil { + err := fmt.Errorf("failed to process %s: %v", path, errFromFile.Error()) + errors = append(errors, err) + continue + } + + policies = append(policies, policiesFromFile...) } } - return policies, nil -} - -//ValidateAndGetPolicies - validating policies -func ValidateAndGetPolicies(policyPaths []string) ([]*v1.ClusterPolicy, error) { - policies, err := GetPolicies(policyPaths) - if err != nil { - if !sanitizederror.IsErrorSanitized(err) { - return nil, sanitizederror.NewWithError((fmt.Sprintf("failed to parse %v path/s.", policyPaths)), err) - } - return nil, err - } - return policies, nil + log.Log.V(3).Info("read policies", "policies", len(policies), "errors", len(errors)) + return policies, errors } // PolicyHasVariables - check for variables in the policy diff --git a/pkg/kyverno/sanitizedError/error.go b/pkg/kyverno/sanitizedError/error.go index 1baca7fe33..1e90303572 100644 --- a/pkg/kyverno/sanitizedError/error.go +++ b/pkg/kyverno/sanitizedError/error.go @@ -1,6 +1,9 @@ package sanitizederror -import "fmt" +import ( + "fmt" + "strings" +) type customError struct { message string @@ -10,9 +13,18 @@ func (c customError) Error() string { return c.message } -// New creates a new sanitized error with given message -func New(message string) error { - return customError{message: message} +func New(msg string) error { + return customError{message: msg} +} + +func NewWithErrors(message string, errors []error) error { + bldr := strings.Builder{} + bldr.WriteString(message + "\n") + for _, err := range errors { + bldr.WriteString(err.Error() + "\n") + } + + return customError{message: bldr.String()} } // NewWithError creates a new sanitized error with given message and error diff --git a/pkg/kyverno/validate/command.go b/pkg/kyverno/validate/command.go index aef384e34e..388733356b 100644 --- a/pkg/kyverno/validate/command.go +++ b/pkg/kyverno/validate/command.go @@ -9,13 +9,13 @@ import ( v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/kyverno/common" - sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError" + "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError" "github.com/kyverno/kyverno/pkg/openapi" policy2 "github.com/kyverno/kyverno/pkg/policy" "github.com/kyverno/kyverno/pkg/utils" "github.com/spf13/cobra" - log "sigs.k8s.io/controller-runtime/pkg/log" - yaml "sigs.k8s.io/yaml" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/yaml" ) // Command returns validate command @@ -27,12 +27,10 @@ func Command() *cobra.Command { Short: "Validates kyverno policies", Example: "kyverno validate /path/to/policy.yaml /path/to/folderOfPolicies", RunE: func(cmd *cobra.Command, policyPaths []string) (err error) { - log := log.Log - defer func() { if err != nil { if !sanitizederror.IsErrorSanitized(err) { - log.Error(err, "failed to sanitize") + log.Log.Error(err, "failed to sanitize") err = fmt.Errorf("internal error") } } @@ -58,26 +56,22 @@ func Command() *cobra.Command { } yamlBytes := []byte(policyStr) - var getErrors []error - policies, getErrors = utils.GetPolicy(yamlBytes) - var errString string - - for _, err := range getErrors { - if err != nil { - errString += err.Error() + "\n" - } - } - if errString != "" { - return sanitizederror.NewWithError("failed to extract the resources", errors.New(errString)) + policies, err = utils.GetPolicy(yamlBytes) + if err != nil { + return sanitizederror.NewWithError("failed to parse policy", err) } } } else { - policies, err = common.ValidateAndGetPolicies(policyPaths) - if err != nil { - if !sanitizederror.IsErrorSanitized(err) { - return sanitizederror.NewWithError("failed to mutate policies.", err) + policies, errs := common.GetPolicies(policyPaths) + if len(errs) > 0 && len(policies) == 0 { + return sanitizederror.NewWithErrors("failed to read policies", errs) + } + + if len(errs) > 0 && log.Log.V(1).Enabled() { + fmt.Printf("ignoring errors: \n") + for _, e := range errs { + fmt.Printf(" %v \n", e.Error()) } - return err } } @@ -109,7 +103,7 @@ func Command() *cobra.Command { } else { fmt.Printf("Policy %s is valid.\n\n", policy.Name) if outputType != "" { - logger := log.WithName("validate") + logger := log.Log.WithName("validate") p, err := common.MutatePolicy(policy, logger) if err != nil { if !sanitizederror.IsErrorSanitized(err) { diff --git a/pkg/policymutation/policymutation_test.go b/pkg/policymutation/policymutation_test.go index fccb6e2441..0da1f92078 100644 --- a/pkg/policymutation/policymutation_test.go +++ b/pkg/policymutation/policymutation_test.go @@ -30,9 +30,9 @@ func Test_Exclude(t *testing.T) { if err != nil { t.Log(err) } - policies, errs := utils.GetPolicy(file) - if len(errs) != 0 { - t.Log(errs) + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) } policy := policies[0] @@ -61,9 +61,9 @@ func Test_CronJobOnly(t *testing.T) { if err != nil { t.Log(err) } - policies, errs := utils.GetPolicy(file) - if len(errs) != 0 { - t.Log(errs) + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) } policy := policies[0] @@ -94,9 +94,9 @@ func Test_CronJob_hasExclude(t *testing.T) { if err != nil { t.Log(err) } - policies, errs := utils.GetPolicy(file) - if len(errs) != 0 { - t.Log(errs) + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) } policy := policies[0] @@ -130,9 +130,9 @@ func Test_CronJobAndDeployment(t *testing.T) { if err != nil { t.Log(err) } - policies, errs := utils.GetPolicy(file) - if len(errs) != 0 { - t.Log(errs) + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) } policy := policies[0] diff --git a/pkg/utils/loadpolicy.go b/pkg/utils/loadpolicy.go index b59ee3f893..1cb7003da9 100644 --- a/pkg/utils/loadpolicy.go +++ b/pkg/utils/loadpolicy.go @@ -11,35 +11,33 @@ import ( "k8s.io/apimachinery/pkg/util/yaml" ) -// GetPolicy - Extracts policies from a YAML -func GetPolicy(file []byte) (clusterPolicies []*v1.ClusterPolicy, errors []error) { - policies, err := SplitYAMLDocuments(file) +// GetPolicy - extracts policies from YAML bytes +func GetPolicy(bytes []byte) (clusterPolicies []*v1.ClusterPolicy, err error) { + policies, err := SplitYAMLDocuments(bytes) if err != nil { - errors = append(errors, err) - return clusterPolicies, errors + return nil, err } for _, thisPolicyBytes := range policies { policyBytes, err := yaml.ToJSON(thisPolicyBytes) if err != nil { - errors = append(errors, fmt.Errorf(fmt.Sprintf("failed to convert json. error: %v", err))) - continue + return nil, fmt.Errorf("failed to convert to JSON: %v", err) } policy := &v1.ClusterPolicy{} if err := json.Unmarshal(policyBytes, policy); err != nil { - errors = append(errors, fmt.Errorf(fmt.Sprintf("failed to decode policy. error: %v", err))) - continue + return nil, fmt.Errorf("failed to decode policy: %v", err) } if !(policy.TypeMeta.Kind == "ClusterPolicy" || policy.TypeMeta.Kind == "Policy") { - errors = append(errors, fmt.Errorf(fmt.Sprintf("resource %v is not a policy/clusterPolicy", policy.Name))) - continue + msg := fmt.Sprintf("resource %s/%s is not a Policy or a ClusterPolicy", policy.Kind, policy.Name) + return nil, fmt.Errorf(msg) } + clusterPolicies = append(clusterPolicies, policy) } - return clusterPolicies, errors + return clusterPolicies, nil } // SplitYAMLDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document diff --git a/test/scenarios/other/scenario_mutate_validate_qos.yaml b/test/scenarios/other/scenario_mutate_validate_qos.yaml index 7497106a8e..83b7723626 100644 --- a/test/scenarios/other/scenario_mutate_validate_qos.yaml +++ b/test/scenarios/other/scenario_mutate_validate_qos.yaml @@ -28,5 +28,5 @@ expected: rules: - name: check-cpu-memory-limits type: Validation - message: Validation rule 'check-cpu-memory-limits' succeeded. + message: validation rule 'check-cpu-memory-limits' passed. success: true \ No newline at end of file diff --git a/test/scenarios/other/scenario_validate_default_proc_mount.yaml b/test/scenarios/other/scenario_validate_default_proc_mount.yaml index a4c5670daa..90828ac310 100644 --- a/test/scenarios/other/scenario_validate_default_proc_mount.yaml +++ b/test/scenarios/other/scenario_validate_default_proc_mount.yaml @@ -15,5 +15,5 @@ expected: rules: - name: validate-default-proc-mount type: Validation - message: "Validation rule 'validate-default-proc-mount' succeeded." + message: "validation rule 'validate-default-proc-mount' passed." success: true \ No newline at end of file diff --git a/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml b/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml index 8c8e82693d..93baed821f 100644 --- a/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml +++ b/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml @@ -14,5 +14,5 @@ expected: rules: - name: prevent-mounting-default-serviceaccount type: Validation - message: "Validation error: Prevent mounting of default service account; Validation rule prevent-mounting-default-serviceaccount failed at path /spec/serviceAccountName/" + message: "validation error: Prevent mounting of default service account. Rule prevent-mounting-default-serviceaccount failed at path /spec/serviceAccountName/" success: false \ No newline at end of file diff --git a/test/scenarios/other/scenario_validate_healthChecks.yaml b/test/scenarios/other/scenario_validate_healthChecks.yaml index 4652859a85..36d618e76e 100644 --- a/test/scenarios/other/scenario_validate_healthChecks.yaml +++ b/test/scenarios/other/scenario_validate_healthChecks.yaml @@ -14,9 +14,9 @@ expected: rules: - name: check-readinessProbe-exists type: Validation - message: Validation rule 'check-readinessProbe-exists' succeeded. + message: validation rule 'check-readinessProbe-exists' passed. success: true - name: check-livenessProbe-exists type: Validation - message: Validation rule 'check-livenessProbe-exists' succeeded. + message: validation rule 'check-livenessProbe-exists' passed. success: true diff --git a/test/scenarios/other/scenario_validate_selinux_context.yaml b/test/scenarios/other/scenario_validate_selinux_context.yaml index 91d0f4e0df..e6b8f75000 100644 --- a/test/scenarios/other/scenario_validate_selinux_context.yaml +++ b/test/scenarios/other/scenario_validate_selinux_context.yaml @@ -15,5 +15,5 @@ expected: rules: - name: validate-selinux-options type: Validation - message: "Validation error: SELinux level is required; Validation rule validate-selinux-options failed at path /spec/containers/0/securityContext/seLinuxOptions/" + message: "validation error: SELinux level is required. Rule validate-selinux-options failed at path /spec/containers/0/securityContext/seLinuxOptions/" success: false \ No newline at end of file diff --git a/test/scenarios/other/scenario_validate_volume_whiltelist.yaml b/test/scenarios/other/scenario_validate_volume_whiltelist.yaml index 96e09a6d97..0e5a62f8fd 100644 --- a/test/scenarios/other/scenario_validate_volume_whiltelist.yaml +++ b/test/scenarios/other/scenario_validate_volume_whiltelist.yaml @@ -15,5 +15,5 @@ expected: rules: - name: validate-volumes-whitelist type: Validation - message: "Validation rule 'validate-volumes-whitelist' anyPattern[2] succeeded." + message: "validation rule 'validate-volumes-whitelist' anyPattern[2] passed." success: true \ No newline at end of file diff --git a/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml b/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml index acb30560e9..0692f262c2 100644 --- a/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml +++ b/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml @@ -14,5 +14,5 @@ expected: rules: - name: validate-docker-sock-mount type: Validation - message: "Validation error: Use of the Docker Unix socket is not allowed; Validation rule validate-docker-sock-mount failed at path /spec/volumes/0/hostPath/path/" + message: "validation error: Use of the Docker Unix socket is not allowed. Rule validate-docker-sock-mount failed at path /spec/volumes/0/hostPath/path/" success: false \ No newline at end of file diff --git a/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml b/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml index 4febde8ad1..6ffe42c2aa 100644 --- a/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml +++ b/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml @@ -12,5 +12,5 @@ expected: rules: - name: validate-helm-tiller type: Validation - message: "Validation error: Helm Tiller is not allowed; Validation rule validate-helm-tiller failed at path /spec/containers/0/image/" + message: "validation error: Helm Tiller is not allowed. Rule validate-helm-tiller failed at path /spec/containers/0/image/" success: false