mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-13 19:28:55 +00:00
Signed-off-by: Brian Dunnigan <bdunnigan@clarityinnovates.com> Co-authored-by: bdunnigan <bdunnigan@clarityinnovates.com> Co-authored-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
parent
cfd4501dcc
commit
d33e616d69
15 changed files with 487 additions and 16 deletions
|
@ -32,6 +32,12 @@ type ImageExtractorConfig struct {
|
|||
// Note - this field MUST be unique.
|
||||
// +optional
|
||||
Key string `json:"key,omitempty" yaml:"key,omitempty"`
|
||||
// JMESPath is an optional JMESPath expression to apply to the image value.
|
||||
// This is useful when the extracted image begins with a prefix like 'docker://'.
|
||||
// The 'trim_prefix' function may be used to trim the prefix: trim_prefix(@, 'docker://').
|
||||
// Note - Image digest mutation may not be used when applying a JMESPAth to an image.
|
||||
// +optional
|
||||
JMESPath string `json:"jmesPath,omitempty" yaml:"jmesPath,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines a validation, mutation, or generation control for matching resources.
|
||||
|
|
|
@ -4397,6 +4397,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -7658,6 +7667,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
@ -10875,6 +10893,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -14066,6 +14093,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
@ -17508,6 +17544,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -20770,6 +20815,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
@ -23988,6 +24042,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -27179,6 +27242,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
|
|
@ -995,6 +995,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -4256,6 +4265,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
@ -7473,6 +7491,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -10664,6 +10691,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
|
|
@ -996,6 +996,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -4258,6 +4267,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
@ -7476,6 +7494,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when the
|
||||
extracted image begins with a prefix like ''docker://''.
|
||||
The ''trim_prefix'' function may be used to trim the
|
||||
prefix: trim_prefix(@, ''docker://''). Note - Image
|
||||
digest mutation may not be used when applying a JMESPAth
|
||||
to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field within
|
||||
'path' that will be used to uniquely identify an image.
|
||||
|
@ -10667,6 +10694,15 @@ spec:
|
|||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
jmesPath:
|
||||
description: 'JMESPath is an optional JMESPath expression
|
||||
to apply to the image value. This is useful when
|
||||
the extracted image begins with a prefix like
|
||||
''docker://''. The ''trim_prefix'' function may
|
||||
be used to trim the prefix: trim_prefix(@, ''docker://'').
|
||||
Note - Image digest mutation may not be used when
|
||||
applying a JMESPAth to an image.'
|
||||
type: string
|
||||
key:
|
||||
description: Key is an optional name of the field
|
||||
within 'path' that will be used to uniquely identify
|
||||
|
|
|
@ -1689,6 +1689,21 @@ string
|
|||
Note - this field MUST be unique.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>jmesPath</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>JMESPath is an optional JMESPath expression to apply to the image value.
|
||||
This is useful when the extracted image begins with a prefix like ‘docker://’.
|
||||
The ‘trim_prefix’ function may be used to trim the prefix: trim_prefix(@, ‘docker://’).
|
||||
Note - Image digest mutation may not be used when applying a JMESPAth to an image.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
|
|
|
@ -42,6 +42,7 @@ var (
|
|||
toUpper = "to_upper"
|
||||
toLower = "to_lower"
|
||||
trim = "trim"
|
||||
trimPrefix = "trim_prefix"
|
||||
split = "split"
|
||||
regexReplaceAll = "regex_replace_all"
|
||||
regexReplaceAllLiteral = "regex_replace_all_literal"
|
||||
|
@ -145,6 +146,17 @@ func GetFunctions() []FunctionEntry {
|
|||
},
|
||||
ReturnType: []jpType{jpString},
|
||||
Note: "trims both ends of the source string by characters appearing in the second string",
|
||||
}, {
|
||||
FunctionEntry: gojmespath.FunctionEntry{
|
||||
Name: trimPrefix,
|
||||
Arguments: []argSpec{
|
||||
{Types: []jpType{jpString}},
|
||||
{Types: []jpType{jpString}},
|
||||
},
|
||||
Handler: jpfTrimPrefix,
|
||||
},
|
||||
ReturnType: []jpType{jpString},
|
||||
Note: "trims the second string prefix from the first string if the first string starts with the prefix",
|
||||
}, {
|
||||
FunctionEntry: gojmespath.FunctionEntry{
|
||||
Name: split,
|
||||
|
@ -588,6 +600,21 @@ func jpfTrim(arguments []interface{}) (interface{}, error) {
|
|||
return strings.Trim(str.String(), cutset.String()), nil
|
||||
}
|
||||
|
||||
func jpfTrimPrefix(arguments []interface{}) (interface{}, error) {
|
||||
var err error
|
||||
str, err := validateArg(trimPrefix, arguments, 0, reflect.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefix, err := validateArg(trimPrefix, arguments, 1, reflect.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(str.String(), prefix.String()), nil
|
||||
}
|
||||
|
||||
func jpfSplit(arguments []interface{}) (interface{}, error) {
|
||||
var err error
|
||||
str, err := validateArg(split, arguments, 0, reflect.String)
|
||||
|
|
|
@ -334,6 +334,65 @@ func Test_Trim(t *testing.T) {
|
|||
assert.Equal(t, trim, "Hello, Gophers")
|
||||
}
|
||||
|
||||
func Test_TrimPrefix(t *testing.T) {
|
||||
type args struct {
|
||||
arguments []interface{}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "trims prefix",
|
||||
args: args{
|
||||
arguments: []interface{}{"¡¡¡Hello, Gophers!!!", "¡¡¡Hello, "},
|
||||
},
|
||||
want: "Gophers!!!",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "does not trim prefix",
|
||||
args: args{
|
||||
arguments: []interface{}{"¡¡¡Hello, Gophers!!!", "¡¡¡Hola, "},
|
||||
},
|
||||
want: "¡¡¡Hello, Gophers!!!",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid first argument",
|
||||
args: args{
|
||||
arguments: []interface{}{1, "¡¡¡Hello, "},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid first argument",
|
||||
args: args{
|
||||
arguments: []interface{}{"¡¡¡Hello, Gophers!!!", 1},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := jpfTrimPrefix(tt.args.arguments)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("jpfTrimPrefix() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("jpfTrimPrefix() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Split(t *testing.T) {
|
||||
jp, err := New("split('Hello, Gophers', ', ')")
|
||||
assert.NilError(t, err)
|
||||
|
|
|
@ -373,6 +373,7 @@ func TestNotAllowedVars_VariableFormats(t *testing.T) {
|
|||
{"custom_func_to_upper", "to_upper(string)", true},
|
||||
{"custom_func_to_lower", "to_lower(string)", true},
|
||||
{"custom_func_trim", "trim(str string, cutset string)", true},
|
||||
{"custom_func_trim_prefix", "trim_prefix(str string, prefix string)", true},
|
||||
{"custom_func_split", "split(str string, sep string)", true},
|
||||
{"custom_func_regex_replace_all", "regex_replace_all(regex string, src string|number, replace string|number)", true},
|
||||
{"custom_func_regex_replace_all_literal", "regex_replace_all_literal(regex string, src string|number, replace string|number)", true},
|
||||
|
|
|
@ -219,6 +219,10 @@ func Validate(policy kyvernov1.PolicyInterface, client dclient.Interface, mock b
|
|||
return warnings, fmt.Errorf("path: spec.rules[%d]: %v", i, err)
|
||||
}
|
||||
|
||||
if err := validateRuleImageExtractorsJMESPath(rule); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d]: %v", i, err)
|
||||
}
|
||||
|
||||
// If a rule's match block does not match any kind,
|
||||
// we should only allow it to have metadata in its overlay
|
||||
if len(rule.MatchResources.Any) > 0 {
|
||||
|
@ -1014,6 +1018,44 @@ func validateRuleContext(rule kyvernov1.Rule) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// validateRuleImageExtractorsJMESPath ensures that the rule does not
|
||||
// mutate image digests if it has an image extractor that uses a JMESPath.
|
||||
func validateRuleImageExtractorsJMESPath(rule kyvernov1.Rule) error {
|
||||
imageExtractorConfigs := rule.ImageExtractors
|
||||
imageVerifications := rule.VerifyImages
|
||||
if imageExtractorConfigs == nil || imageVerifications == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
anyMutateDigest := false
|
||||
for _, imageVerification := range imageVerifications {
|
||||
if imageVerification.MutateDigest {
|
||||
anyMutateDigest = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !anyMutateDigest {
|
||||
return nil
|
||||
}
|
||||
|
||||
anyJMESPath := false
|
||||
for _, imageExtractors := range imageExtractorConfigs {
|
||||
for _, imageExtractor := range imageExtractors {
|
||||
if imageExtractor.JMESPath != "" {
|
||||
anyJMESPath = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if anyJMESPath {
|
||||
return fmt.Errorf("jmespath may not be used in an image extractor when mutating digests with verify images")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateVariable(entry kyvernov1.ContextEntry) error {
|
||||
// If JMESPath contains variables, the validation will fail because it's not possible to infer which value
|
||||
// will be inserted by the variable
|
||||
|
|
|
@ -3,6 +3,7 @@ package policy
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
|
@ -2239,3 +2240,61 @@ func Test_Any_wildcard_policy(t *testing.T) {
|
|||
_, err = Validate(policy, nil, true, openApiManager)
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_RuleImageExtractorsJMESPath(t *testing.T) {
|
||||
rawPolicy := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "jmes-path-and-mutate-digest"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"CRD"
|
||||
]
|
||||
}
|
||||
},
|
||||
"imageExtractors": {
|
||||
"CRD": [
|
||||
{
|
||||
"path": "/path/to/image/prefixed/with/scheme",
|
||||
"jmesPath": "trim_prefix(@, 'docker://')"
|
||||
}
|
||||
]
|
||||
},
|
||||
"verifyImages": [
|
||||
{
|
||||
"mutateDigest": true,
|
||||
"attestors": [
|
||||
{
|
||||
"count": 1,
|
||||
"entries": [
|
||||
{
|
||||
"keys": {
|
||||
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM\n5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy *kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
expectedErr := fmt.Errorf("path: spec.rules[0]: jmespath may not be used in an image extractor when mutating digests with verify images")
|
||||
|
||||
openApiManager, _ := openapi.NewManager()
|
||||
_, actualErr := Validate(policy, nil, true, openApiManager)
|
||||
assert.Equal(t, expectedErr.Error(), actualErr.Error())
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
||||
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
@ -34,21 +35,22 @@ var (
|
|||
)
|
||||
|
||||
type imageExtractor struct {
|
||||
Fields []string
|
||||
Key string
|
||||
Value string
|
||||
Name string
|
||||
Fields []string
|
||||
Key string
|
||||
Value string
|
||||
Name string
|
||||
JMESPath string
|
||||
}
|
||||
|
||||
func (i *imageExtractor) ExtractFromResource(resource interface{}, cfg config.Configuration) (map[string]ImageInfo, error) {
|
||||
imageInfo := map[string]ImageInfo{}
|
||||
if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, &imageInfo, cfg); err != nil {
|
||||
if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, i.JMESPath, &imageInfo, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return imageInfo, nil
|
||||
}
|
||||
|
||||
func extract(obj interface{}, path []string, keyPath, valuePath string, fields []string, imageInfos *map[string]ImageInfo, cfg config.Configuration) error {
|
||||
func extract(obj interface{}, path []string, keyPath, valuePath string, fields []string, jmesPath string, imageInfos *map[string]ImageInfo, cfg config.Configuration) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -56,13 +58,13 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [
|
|||
switch typedObj := obj.(type) {
|
||||
case []interface{}:
|
||||
for i, v := range typedObj {
|
||||
if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], imageInfos, cfg); err != nil {
|
||||
if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case map[string]interface{}:
|
||||
for i, v := range typedObj {
|
||||
if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], imageInfos, cfg); err != nil {
|
||||
if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +92,26 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [
|
|||
if !ok {
|
||||
return fmt.Errorf("invalid value")
|
||||
}
|
||||
|
||||
if jmesPath != "" {
|
||||
jp, err := jmespath.New(jmesPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid jmespath %s: %v", jmesPath, err)
|
||||
}
|
||||
|
||||
result, err := jp.Search(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply jmespath %s: %v", jmesPath, err)
|
||||
}
|
||||
|
||||
resultStr, ok := result.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("jmespath %s must produce a string, but produced %v", jmesPath, result)
|
||||
}
|
||||
|
||||
value = resultStr
|
||||
}
|
||||
|
||||
if imageInfo, err := imageutils.GetImageInfo(value, cfg); err != nil {
|
||||
return fmt.Errorf("invalid image %s", value)
|
||||
} else {
|
||||
|
@ -99,7 +121,7 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [
|
|||
}
|
||||
|
||||
currentPath := fields[0]
|
||||
return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], imageInfos, cfg)
|
||||
return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg)
|
||||
}
|
||||
|
||||
func BuildStandardExtractors(tags ...string) []imageExtractor {
|
||||
|
@ -139,10 +161,11 @@ func lookupImageExtractor(kind string, configs kyvernov1.ImageExtractorConfigs)
|
|||
fields = fields[:len(fields)-1]
|
||||
}
|
||||
extractors = append(extractors, imageExtractor{
|
||||
Fields: fields,
|
||||
Key: c.Key,
|
||||
Name: name,
|
||||
Value: value,
|
||||
Fields: fields,
|
||||
Key: c.Key,
|
||||
Name: name,
|
||||
Value: value,
|
||||
JMESPath: c.JMESPath,
|
||||
})
|
||||
}
|
||||
return extractors
|
||||
|
|
|
@ -217,6 +217,27 @@ func Test_extractImageInfo(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
extractionConfig: kyvernov1.ImageExtractorConfigs{
|
||||
"DataVolume": []kyvernov1.ImageExtractorConfig{
|
||||
{Path: "/spec/source/registry/url", JMESPath: "trim_prefix(@, 'docker://')"},
|
||||
},
|
||||
},
|
||||
raw: []byte(`{"apiVersion":"cdi.kubevirt.io/v1beta1","kind":"DataVolume","metadata":{"name":"registry-image-datavolume"},"spec":{"source":{"registry":{"url":"docker://kubevirt/fedora-cloud-registry-disk-demo"}},"pvc":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}}}}}`),
|
||||
images: map[string]map[string]ImageInfo{
|
||||
"custom": {
|
||||
"/spec/source/registry/url": {
|
||||
imageutils.ImageInfo{
|
||||
Registry: "docker.io",
|
||||
Name: "fedora-cloud-registry-disk-demo",
|
||||
Path: "kubevirt/fedora-cloud-registry-disk-demo",
|
||||
Tag: "latest",
|
||||
},
|
||||
"/spec/source/registry/url",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -9,8 +9,18 @@ results:
|
|||
resource: signed
|
||||
kind: Pod
|
||||
status: pass
|
||||
- policy: check-image
|
||||
- policy: check-image
|
||||
rule: verify-signature
|
||||
resource: unsigned
|
||||
kind: Pod
|
||||
status: fail
|
||||
- policy: check-data-volume-image
|
||||
rule: verify-signature
|
||||
resource: signed-registry-image-datavolume
|
||||
kind: DataVolume
|
||||
status: pass
|
||||
- policy: check-data-volume-image
|
||||
rule: verify-signature
|
||||
resource: unsigned-registry-image-datavolume
|
||||
kind: DataVolume
|
||||
status: fail
|
|
@ -24,4 +24,38 @@ spec:
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
|
||||
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
|
||||
-----END PUBLIC KEY-----
|
||||
-----END PUBLIC KEY-----
|
||||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: check-data-volume-image
|
||||
annotations:
|
||||
pod-policies.kyverno.io/autogen-controllers: none
|
||||
spec:
|
||||
validationFailureAction: enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: verify-signature
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- DataVolume
|
||||
imageExtractors:
|
||||
DataVolume:
|
||||
- path: /spec/source/registry/url
|
||||
jmesPath: "trim_prefix(@, 'docker://')"
|
||||
verifyImages:
|
||||
- imageReferences:
|
||||
- "*"
|
||||
mutateDigest: false
|
||||
attestors:
|
||||
- count: 1
|
||||
entries:
|
||||
- keys:
|
||||
publicKeys: |-
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
|
||||
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
|
||||
-----END PUBLIC KEY-----
|
|
@ -15,4 +15,34 @@ metadata:
|
|||
spec:
|
||||
containers:
|
||||
- name: signed
|
||||
image: ghcr.io/kyverno/test-verify-image:unsigned
|
||||
image: ghcr.io/kyverno/test-verify-image:unsigned
|
||||
---
|
||||
apiVersion: cdi.kubevirt.io/v1beta1
|
||||
kind: DataVolume
|
||||
metadata:
|
||||
name: signed-registry-image-datavolume
|
||||
spec:
|
||||
source:
|
||||
registry:
|
||||
url: "docker://ghcr.io/kyverno/test-verify-image:signed"
|
||||
pvc:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: cdi.kubevirt.io/v1beta1
|
||||
kind: DataVolume
|
||||
metadata:
|
||||
name: unsigned-registry-image-datavolume
|
||||
spec:
|
||||
source:
|
||||
registry:
|
||||
url: "docker://ghcr.io/kyverno/test-verify-image:unsigned"
|
||||
pvc:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
Loading…
Add table
Reference in a new issue