mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Merge pull request #2600 from JimBugwadia/image_verify_variable_substitution
add variable substitution for imageVerify and allow PEM in ConfigMaps
This commit is contained in:
commit
9e9af4872e
6 changed files with 89 additions and 18 deletions
6
go.sum
6
go.sum
|
@ -437,7 +437,9 @@ github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
|
|||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ=
|
||||
github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
|
@ -1527,6 +1529,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
|||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
||||
github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
|
@ -1538,6 +1541,7 @@ github.com/shirou/gopsutil/v3 v3.21.4/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0
|
|||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sigstore/cosign v1.2.1 h1:xuEhyQ3gE/jzVch1d9WQ4dg9ZbI6x2FSGq78MZm5wX4=
|
||||
github.com/sigstore/cosign v1.2.1/go.mod h1:HjFCBUZd/q5IXxSVf2Qazr0IFSlxqSKv4Y79XoA5to8=
|
||||
github.com/sigstore/fulcio v0.1.2-0.20210831152525-42f7422734bb h1:smRYK5Ii+6MzPPz6yisB65v2Pam5oHPOTLDlxyM3qYY=
|
||||
github.com/sigstore/fulcio v0.1.2-0.20210831152525-42f7422734bb/go.mod h1:LznI5ABAkquvZrJ1PQaGCgspMfw2CB6ODBCQyhU3Q0w=
|
||||
github.com/sigstore/rekor v0.3.0 h1:OBEvo/Rv8NKKtiWq0WRHgXFpVPe1fGiqz93dfBh/Myo=
|
||||
github.com/sigstore/rekor v0.3.0/go.mod h1:cL9B3+/gp3BG+/bhkSHBA3MQZMten5xM6BhJYd5b5zU=
|
||||
|
@ -1555,6 +1559,7 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
|||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
|
@ -2504,6 +2509,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
|
||||
"github.com/sigstore/cosign/pkg/cosign/attestation"
|
||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
||||
"strings"
|
||||
|
@ -54,7 +55,7 @@ func VerifySignature(imageRef string, key []byte, repository string, log logr.Lo
|
|||
}
|
||||
|
||||
cosignOpts := &cosign.CheckOpts{
|
||||
//RootCerts: fulcio.GetRoots(),
|
||||
RootCerts: fulcio.GetRoots(),
|
||||
Annotations: map[string]interface{}{},
|
||||
SigVerifier: pubKey,
|
||||
RegistryClientOpts: []remote.Option{
|
||||
|
@ -81,13 +82,13 @@ func VerifySignature(imageRef string, key []byte, repository string, log logr.Lo
|
|||
if err != nil {
|
||||
msg := err.Error()
|
||||
logger.Info("image verification failed", "error", msg)
|
||||
if strings.Contains(msg, "NAME_UNKNOWN: repository name not known to registry") {
|
||||
if strings.Contains(msg, "MANIFEST_UNKNOWN: manifest unknown") {
|
||||
return "", fmt.Errorf("signature not found")
|
||||
} else if strings.Contains(msg, "no matching signatures") {
|
||||
return "", fmt.Errorf("invalid signature")
|
||||
return "", fmt.Errorf("signature mismatch")
|
||||
}
|
||||
|
||||
return "", errors.Wrap(err, "failed to verify image")
|
||||
return "", err
|
||||
}
|
||||
|
||||
digest, err = extractDigest(imageRef, verified, log)
|
||||
|
@ -125,7 +126,13 @@ func FetchAttestations(imageRef string, key []byte, repository string) ([]map[st
|
|||
|
||||
verified, err := client.Verify(context.Background(), ref, cosignOpts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to verify image attestations")
|
||||
msg := err.Error()
|
||||
logger.Info("failed to fetch attestations", "error", msg)
|
||||
if strings.Contains(msg, "MANIFEST_UNKNOWN: manifest unknown") {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inTotoStatements, err := decodeStatements(verified)
|
||||
|
|
|
@ -57,14 +57,25 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
|
|||
|
||||
policyContext.JSONContext.Restore()
|
||||
|
||||
if err := LoadContext(logger, rule.Context, policyContext.ResourceCache, policyContext, rule.Name); err != nil {
|
||||
appendError(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), response.RuleStatusError)
|
||||
continue
|
||||
}
|
||||
|
||||
ruleCopy, err := substituteVariables(rule, policyContext.JSONContext, logger)
|
||||
if err != nil {
|
||||
appendError(resp, rule, fmt.Sprintf("failed to substitute variables: %s", err.Error()), response.RuleStatusError)
|
||||
continue
|
||||
}
|
||||
|
||||
iv := &imageVerifier{
|
||||
logger: logger,
|
||||
policyContext: policyContext,
|
||||
rule: rule,
|
||||
rule: ruleCopy,
|
||||
resp: resp,
|
||||
}
|
||||
|
||||
for _, imageVerify := range rule.VerifyImages {
|
||||
for _, imageVerify := range ruleCopy.VerifyImages {
|
||||
iv.verify(imageVerify, images.Containers)
|
||||
iv.verify(imageVerify, images.InitContainers)
|
||||
}
|
||||
|
@ -73,6 +84,34 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
|
|||
return
|
||||
}
|
||||
|
||||
func appendError(resp *response.EngineResponse, rule *v1.Rule, msg string, status response.RuleStatus) {
|
||||
rr := ruleResponse(rule, utils.ImageVerify, msg, status)
|
||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *rr)
|
||||
incrementErrorCount(resp)
|
||||
}
|
||||
|
||||
func substituteVariables(rule *v1.Rule, ctx context.EvalInterface, logger logr.Logger) (*v1.Rule, error) {
|
||||
|
||||
// remove attestations as variables are not substituted in them
|
||||
ruleCopy := rule.DeepCopy()
|
||||
for _, iv := range ruleCopy.VerifyImages {
|
||||
iv.Attestations = nil
|
||||
}
|
||||
|
||||
var err error
|
||||
*ruleCopy, err = variables.SubstituteAllInRule(logger, ctx, *ruleCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// replace attestations
|
||||
for i := range rule.VerifyImages {
|
||||
ruleCopy.VerifyImages[i].Attestations = rule.VerifyImages[i].Attestations
|
||||
}
|
||||
|
||||
return ruleCopy, nil
|
||||
}
|
||||
|
||||
type imageVerifier struct {
|
||||
logger logr.Logger
|
||||
policyContext *PolicyContext
|
||||
|
|
|
@ -36,8 +36,8 @@ func LoadContext(logger logr.Logger, contextEntries []kyverno.ContextEntry, resC
|
|||
if trimmedTypedValue := strings.Trim(value, "\n"); strings.Contains(trimmedTypedValue, "\n") {
|
||||
tmp := map[string]interface{}{key: value}
|
||||
tmp = parseMultilineBlockBody(tmp)
|
||||
new_val, _ := json.Marshal(tmp[key])
|
||||
value = string(new_val)
|
||||
newVal, _ := json.Marshal(tmp[key])
|
||||
value = string(newVal)
|
||||
}
|
||||
|
||||
jsonData := pkgcommon.VariableToJSON(key, value)
|
||||
|
@ -238,15 +238,16 @@ func fetchConfigMap(logger logr.Logger, entry kyverno.ContextEntry, lister dynam
|
|||
return data, nil
|
||||
}
|
||||
|
||||
// parseMultilineBlockBody recursively iterates through a map and updates its values in the following way
|
||||
// whenever it encounters a string value containing "\n",
|
||||
// it converts it into a []string by splitting it by "\n"
|
||||
// parseMultilineBlockBody recursively iterates through a map and updates its values to a list of strings
|
||||
// if it encounters a string value containing newline delimiters "\n" and not in PEM format. This is done to
|
||||
// allow specifying a list with newlines. Since PEM format keys can also contain newlines, an additional check
|
||||
// is performed to skip splitting those into an array.
|
||||
func parseMultilineBlockBody(m map[string]interface{}) map[string]interface{} {
|
||||
for k, v := range m {
|
||||
switch typedValue := v.(type) {
|
||||
case string:
|
||||
trimmedTypedValue := strings.Trim(typedValue, "\n")
|
||||
if strings.Contains(trimmedTypedValue, "\n") {
|
||||
if !pemFormat(trimmedTypedValue) && strings.Contains(trimmedTypedValue, "\n") {
|
||||
m[k] = strings.Split(trimmedTypedValue, "\n")
|
||||
} else {
|
||||
m[k] = trimmedTypedValue // trimming a str if it has trailing newline characters
|
||||
|
@ -257,3 +258,8 @@ func parseMultilineBlockBody(m map[string]interface{}) map[string]interface{} {
|
|||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// check for PEM header found in certs and public keys
|
||||
func pemFormat(s string) bool {
|
||||
return strings.Contains(s, "-----BEGIN")
|
||||
}
|
||||
|
|
|
@ -51,6 +51,13 @@ func Test_parseMultilineBlockBody(t *testing.T) {
|
|||
expectedMultilineBlockRaw: []byte(`{"key1":"value1","key2":"[\"cluster-admin\", \"cluster-operator\", \"tenant-admin\"]"}`),
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
multilineBlockRaw: []byte(`{
|
||||
"key1": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155\nJI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----"
|
||||
}`),
|
||||
expectedMultilineBlockRaw: []byte(`{"key1":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155\nJI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----"}`),
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
|
|
|
@ -173,7 +173,7 @@ func PolicyHasVariables(policy v1.ClusterPolicy) [][]string {
|
|||
}
|
||||
|
||||
// for now forbidden sections are match, exclude and
|
||||
func ruleForbiddenSectionsHaveVariables(rule v1.Rule) error {
|
||||
func ruleForbiddenSectionsHaveVariables(rule *v1.Rule) error {
|
||||
var err error
|
||||
|
||||
err = JSONPatchPathHasVariables(rule.Mutation.PatchesJSON6902)
|
||||
|
@ -236,9 +236,15 @@ func objectHasVariables(object interface{}) error {
|
|||
|
||||
// PolicyHasNonAllowedVariables - checks for unexpected variables in the policy
|
||||
func PolicyHasNonAllowedVariables(policy v1.ClusterPolicy) error {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
var err error
|
||||
for _, r := range policy.Spec.Rules {
|
||||
rule := r.DeepCopy()
|
||||
|
||||
// do not validate attestation variables as they are based on external data
|
||||
for _, vi := range rule.VerifyImages {
|
||||
vi.Attestations = nil
|
||||
}
|
||||
|
||||
var err error
|
||||
ruleJSON, err := json.Marshal(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -251,9 +257,9 @@ func PolicyHasNonAllowedVariables(policy v1.ClusterPolicy) error {
|
|||
|
||||
matchesAll := RegexVariables.FindAllStringSubmatch(string(ruleJSON), -1)
|
||||
matchesAllowed := AllowedVariables.FindAllStringSubmatch(string(ruleJSON), -1)
|
||||
|
||||
if (len(matchesAll) > len(matchesAllowed)) && len(rule.Context) == 0 {
|
||||
return fmt.Errorf("Rule \"%s\" has forbidden variables. Allowed variables are: {{request.*}}, {{serviceAccountName}}, {{serviceAccountNamespace}}, {{@}} and ones defined by the context", rule.Name)
|
||||
allowed := "{{request.*}}, {{element.*}}, {{serviceAccountName}}, {{serviceAccountNamespace}}, {{@}}, and context variables"
|
||||
return fmt.Errorf("rule \"%s\" has forbidden variables. Allowed variables are: %s", rule.Name, allowed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue