1
0
Fork 0
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:
Jim Bugwadia 2021-10-26 14:53:14 -07:00 committed by GitHub
commit 9e9af4872e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 18 deletions

6
go.sum
View file

@ -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=

View file

@ -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)

View file

@ -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

View file

@ -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")
}

View file

@ -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 {

View file

@ -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)
}
}