mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 15:37:19 +00:00
feat: autogenerate image verification policies for pod controllers (#12290)
* feat: autogen for image verification Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: linter Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> --------- Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
This commit is contained in:
parent
84e9517bad
commit
c47b48bda6
14 changed files with 1135 additions and 38 deletions
|
@ -88,6 +88,10 @@ type ImageVerificationPolicySpec struct {
|
|||
// +listType=atomic
|
||||
Verifications []admissionregistrationv1.Validation `json:"verifications"`
|
||||
|
||||
// WebhookConfiguration defines the configuration for the webhook.
|
||||
// +optional
|
||||
WebhookConfiguration *WebhookConfiguration `json:"webhookConfiguration,omitempty"`
|
||||
|
||||
// EvaluationConfiguration defines the configuration for the policy evaluation.
|
||||
// +optional
|
||||
EvaluationConfiguration *EvaluationConfiguration `json:"evaluation,omitempty"`
|
||||
|
|
|
@ -15,6 +15,24 @@ type ImageVerificationPolicy struct {
|
|||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Spec ImageVerificationPolicySpec `json:"spec"`
|
||||
// Status contains policy runtime data.
|
||||
// +optional
|
||||
Status PolicyStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetMatchConstraints() admissionregistrationv1.MatchResources {
|
||||
if s.Spec.MatchConstraints == nil {
|
||||
return admissionregistrationv1.MatchResources{}
|
||||
}
|
||||
return *s.Spec.MatchConstraints
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetMatchConditions() []admissionregistrationv1.MatchCondition {
|
||||
return s.Spec.MatchConditions
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetWebhookConfiguration() *WebhookConfiguration {
|
||||
return s.Spec.WebhookConfiguration
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetFailurePolicy() admissionregistrationv1.FailurePolicyType {
|
||||
|
@ -24,6 +42,22 @@ func (s *ImageVerificationPolicy) GetFailurePolicy() admissionregistrationv1.Fai
|
|||
return *s.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetVariables() []admissionregistrationv1.Variable {
|
||||
return s.Spec.Variables
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetSpec() *ImageVerificationPolicySpec {
|
||||
return &s.Spec
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetStatus() *PolicyStatus {
|
||||
return &s.Status
|
||||
}
|
||||
|
||||
func (s *ImageVerificationPolicy) GetKind() string {
|
||||
return "ImageVerificationPolicy"
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
|
|
|
@ -465,6 +465,7 @@ func (in *ImageVerificationPolicy) DeepCopyInto(out *ImageVerificationPolicy) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -598,6 +599,11 @@ func (in *ImageVerificationPolicySpec) DeepCopyInto(out *ImageVerificationPolicy
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.WebhookConfiguration != nil {
|
||||
in, out := &in.WebhookConfiguration, &out.WebhookConfiguration
|
||||
*out = new(WebhookConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.EvaluationConfiguration != nil {
|
||||
in, out := &in.EvaluationConfiguration, &out.EvaluationConfiguration
|
||||
*out = new(EvaluationConfiguration)
|
||||
|
|
|
@ -924,10 +924,651 @@ spec:
|
|||
default: true
|
||||
description: VerifyDigest validates that images have a digest.
|
||||
type: boolean
|
||||
webhookConfiguration:
|
||||
description: WebhookConfiguration defines the configuration for the
|
||||
webhook.
|
||||
properties:
|
||||
timeoutSeconds:
|
||||
description: |-
|
||||
TimeoutSeconds specifies the maximum time in seconds allowed to apply this policy.
|
||||
After the configured time expires, the admission request may fail, or may simply ignore the policy results,
|
||||
based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
required:
|
||||
- attestors
|
||||
- verifications
|
||||
type: object
|
||||
status:
|
||||
description: Status contains policy runtime data.
|
||||
properties:
|
||||
autogen:
|
||||
description: AutogenStatus contains autogen status information.
|
||||
properties:
|
||||
rules:
|
||||
description: Rules is a list of Rule instances. It contains auto
|
||||
generated rules added for pod controllers
|
||||
items:
|
||||
properties:
|
||||
auditAnnotations:
|
||||
items:
|
||||
description: AuditAnnotation describes how to produce
|
||||
an audit annotation for an API request.
|
||||
properties:
|
||||
key:
|
||||
description: |-
|
||||
key specifies the audit annotation key. The audit annotation keys of
|
||||
a ValidatingAdmissionPolicy must be unique. The key must be a qualified
|
||||
name ([A-Za-z0-9][-A-Za-z0-9_.]*) no more than 63 bytes in length.
|
||||
|
||||
The key is combined with the resource name of the
|
||||
ValidatingAdmissionPolicy to construct an audit annotation key:
|
||||
"{ValidatingAdmissionPolicy name}/{key}".
|
||||
|
||||
If an admission webhook uses the same resource name as this ValidatingAdmissionPolicy
|
||||
and the same audit annotation key, the annotation key will be identical.
|
||||
In this case, the first annotation written with the key will be included
|
||||
in the audit event and all subsequent annotations with the same key
|
||||
will be discarded.
|
||||
|
||||
Required.
|
||||
type: string
|
||||
valueExpression:
|
||||
description: |-
|
||||
valueExpression represents the expression which is evaluated by CEL to
|
||||
produce an audit annotation value. The expression must evaluate to either
|
||||
a string or null value. If the expression evaluates to a string, the
|
||||
audit annotation is included with the string value. If the expression
|
||||
evaluates to null or empty string the audit annotation will be omitted.
|
||||
The valueExpression may be no longer than 5kb in length.
|
||||
If the result of the valueExpression is more than 10kb in length, it
|
||||
will be truncated to 10kb.
|
||||
|
||||
If multiple ValidatingAdmissionPolicyBinding resources match an
|
||||
API request, then the valueExpression will be evaluated for
|
||||
each binding. All unique values produced by the valueExpressions
|
||||
will be joined together in a comma-separated list.
|
||||
|
||||
Required.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- valueExpression
|
||||
type: object
|
||||
type: array
|
||||
matchConditions:
|
||||
items:
|
||||
description: MatchCondition represents a condition which
|
||||
must by fulfilled for a request to be sent to a webhook.
|
||||
properties:
|
||||
expression:
|
||||
description: |-
|
||||
Expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
|
||||
CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:
|
||||
|
||||
'object' - The object from the incoming request. The value is null for DELETE requests.
|
||||
'oldObject' - The existing object. The value is null for CREATE requests.
|
||||
'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest).
|
||||
'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
|
||||
See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
|
||||
'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
|
||||
request resource.
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
|
||||
Required.
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name is an identifier for this match condition, used for strategic merging of MatchConditions,
|
||||
as well as providing an identifier for logging purposes. A good name should be descriptive of
|
||||
the associated expression.
|
||||
Name must be a qualified name consisting of alphanumeric characters, '-', '_' or '.', and
|
||||
must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or
|
||||
'123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') with an
|
||||
optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')
|
||||
|
||||
Required.
|
||||
type: string
|
||||
required:
|
||||
- expression
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
matchConstraints:
|
||||
description: |-
|
||||
MatchResources decides whether to run the admission control policy on an object based
|
||||
on whether it meets the match criteria.
|
||||
The exclude rules take precedence over include rules (if a resource matches both, it is excluded)
|
||||
properties:
|
||||
excludeResourceRules:
|
||||
description: |-
|
||||
ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about.
|
||||
The exclude rules take precedence over include rules (if a resource matches both, it is excluded)
|
||||
items:
|
||||
description: NamedRuleWithOperations is a tuple of
|
||||
Operations and Resources with ResourceNames.
|
||||
properties:
|
||||
apiGroups:
|
||||
description: |-
|
||||
APIGroups is the API groups the resources belong to. '*' is all groups.
|
||||
If '*' is present, the length of the slice must be one.
|
||||
Required.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
apiVersions:
|
||||
description: |-
|
||||
APIVersions is the API versions the resources belong to. '*' is all versions.
|
||||
If '*' is present, the length of the slice must be one.
|
||||
Required.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
operations:
|
||||
description: |-
|
||||
Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or *
|
||||
for all of those operations and any future admission operations that are added.
|
||||
If '*' is present, the length of the slice must be one.
|
||||
Required.
|
||||
items:
|
||||
description: OperationType specifies an operation
|
||||
for a request.
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
resourceNames:
|
||||
description: ResourceNames is an optional white
|
||||
list of names that the rule applies to. An
|
||||
empty set means that everything is allowed.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
resources:
|
||||
description: |-
|
||||
Resources is a list of resources this rule applies to.
|
||||
|
||||
For example:
|
||||
'pods' means pods.
|
||||
'pods/log' means the log subresource of pods.
|
||||
'*' means all resources, but not subresources.
|
||||
'pods/*' means all subresources of pods.
|
||||
'*/scale' means all scale subresources.
|
||||
'*/*' means all resources and their subresources.
|
||||
|
||||
If wildcard is present, the validation rule will ensure resources do not
|
||||
overlap with each other.
|
||||
|
||||
Depending on the enclosing object, subresources might not be allowed.
|
||||
Required.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
scope:
|
||||
description: |-
|
||||
scope specifies the scope of this rule.
|
||||
Valid values are "Cluster", "Namespaced", and "*"
|
||||
"Cluster" means that only cluster-scoped resources will match this rule.
|
||||
Namespace API objects are cluster-scoped.
|
||||
"Namespaced" means that only namespaced resources will match this rule.
|
||||
"*" means that there are no scope restrictions.
|
||||
Subresources match the scope of their parent resource.
|
||||
Default is "*".
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchPolicy:
|
||||
description: |-
|
||||
matchPolicy defines how the "MatchResources" list is used to match incoming requests.
|
||||
Allowed values are "Exact" or "Equivalent".
|
||||
|
||||
- Exact: match a request only if it exactly matches a specified rule.
|
||||
For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1,
|
||||
but "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`,
|
||||
a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the ValidatingAdmissionPolicy.
|
||||
|
||||
- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version.
|
||||
For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1,
|
||||
and "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`,
|
||||
a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the ValidatingAdmissionPolicy.
|
||||
|
||||
Defaults to "Equivalent"
|
||||
type: string
|
||||
namespaceSelector:
|
||||
description: |-
|
||||
NamespaceSelector decides whether to run the admission control policy on an object based
|
||||
on whether the namespace for that object matches the selector. If the
|
||||
object itself is a namespace, the matching is performed on
|
||||
object.metadata.labels. If the object is another cluster scoped resource,
|
||||
it never skips the policy.
|
||||
|
||||
For example, to run the webhook on any objects whose namespace is not
|
||||
associated with "runlevel" of "0" or "1"; you will set the selector as
|
||||
follows:
|
||||
"namespaceSelector": {
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "runlevel",
|
||||
"operator": "NotIn",
|
||||
"values": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
If instead you want to only run the policy on any objects whose
|
||||
namespace is associated with the "environment" of "prod" or "staging";
|
||||
you will set the selector as follows:
|
||||
"namespaceSelector": {
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "environment",
|
||||
"operator": "In",
|
||||
"values": [
|
||||
"prod",
|
||||
"staging"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
See
|
||||
https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
for more examples of label selectors.
|
||||
|
||||
Default to the empty LabelSelector, which matches everything.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
objectSelector:
|
||||
description: |-
|
||||
ObjectSelector decides whether to run the validation based on if the
|
||||
object has matching labels. objectSelector is evaluated against both
|
||||
the oldObject and newObject that would be sent to the cel validation, and
|
||||
is considered to match if either object matches the selector. A null
|
||||
object (oldObject in the case of create, or newObject in the case of
|
||||
delete) or an object that cannot have labels (like a
|
||||
DeploymentRollback or a PodProxyOptions object) is not considered to
|
||||
match.
|
||||
Use the object selector only if the webhook is opt-in, because end
|
||||
users may skip the admission webhook by setting the labels.
|
||||
Default to the empty LabelSelector, which matches everything.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
resourceRules:
|
||||
description: |-
|
||||
ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches.
|
||||
The policy cares about an operation if it matches _any_ Rule.
|
||||
items:
|
||||
description: NamedRuleWithOperations is a tuple of
|
||||
Operations and Resources with ResourceNames.
|
||||
properties:
|
||||
apiGroups:
|
||||
description: |-
|
||||
APIGroups is the API groups the resources belong to. '*' is all groups.
|
||||
If '*' is present, the length of the slice must be one.
|
||||
Required.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
apiVersions:
|
||||
description: |-
|
||||
APIVersions is the API versions the resources belong to. '*' is all versions.
|
||||
If '*' is present, the length of the slice must be one.
|
||||
Required.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
operations:
|
||||
description: |-
|
||||
Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or *
|
||||
for all of those operations and any future admission operations that are added.
|
||||
If '*' is present, the length of the slice must be one.
|
||||
Required.
|
||||
items:
|
||||
description: OperationType specifies an operation
|
||||
for a request.
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
resourceNames:
|
||||
description: ResourceNames is an optional white
|
||||
list of names that the rule applies to. An
|
||||
empty set means that everything is allowed.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
resources:
|
||||
description: |-
|
||||
Resources is a list of resources this rule applies to.
|
||||
|
||||
For example:
|
||||
'pods' means pods.
|
||||
'pods/log' means the log subresource of pods.
|
||||
'*' means all resources, but not subresources.
|
||||
'pods/*' means all subresources of pods.
|
||||
'*/scale' means all scale subresources.
|
||||
'*/*' means all resources and their subresources.
|
||||
|
||||
If wildcard is present, the validation rule will ensure resources do not
|
||||
overlap with each other.
|
||||
|
||||
Depending on the enclosing object, subresources might not be allowed.
|
||||
Required.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
scope:
|
||||
description: |-
|
||||
scope specifies the scope of this rule.
|
||||
Valid values are "Cluster", "Namespaced", and "*"
|
||||
"Cluster" means that only cluster-scoped resources will match this rule.
|
||||
Namespace API objects are cluster-scoped.
|
||||
"Namespaced" means that only namespaced resources will match this rule.
|
||||
"*" means that there are no scope restrictions.
|
||||
Subresources match the scope of their parent resource.
|
||||
Default is "*".
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
validations:
|
||||
items:
|
||||
description: Validation specifies the CEL expression which
|
||||
is used to apply the validation.
|
||||
properties:
|
||||
expression:
|
||||
description: "Expression represents the expression
|
||||
which will be evaluated by CEL.\nref: https://github.com/google/cel-spec\nCEL
|
||||
expressions have access to the contents of the API
|
||||
request/response, organized into CEL variables as
|
||||
well as some other useful variables:\n\n- 'object'
|
||||
- The object from the incoming request. The value
|
||||
is null for DELETE requests.\n- 'oldObject' - The
|
||||
existing object. The value is null for CREATE requests.\n-
|
||||
'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)).\n-
|
||||
'params' - Parameter resource referred to by the
|
||||
policy binding being evaluated. Only populated if
|
||||
the policy has a ParamKind.\n- 'namespaceObject'
|
||||
- The namespace object that the incoming object
|
||||
belongs to. The value is null for cluster-scoped
|
||||
resources.\n- 'variables' - Map of composited variables,
|
||||
from its name to its lazily evaluated value.\n For
|
||||
example, a variable named 'foo' can be accessed
|
||||
as 'variables.foo'.\n- 'authorizer' - A CEL Authorizer.
|
||||
May be used to perform authorization checks for
|
||||
the principal (user or service account) of the request.\n
|
||||
\ See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n-
|
||||
'authorizer.requestResource' - A CEL ResourceCheck
|
||||
constructed from the 'authorizer' and configured
|
||||
with the\n request resource.\n\nThe `apiVersion`,
|
||||
`kind`, `metadata.name` and `metadata.generateName`
|
||||
are always accessible from the root of the\nobject.
|
||||
No other metadata properties are accessible.\n\nOnly
|
||||
property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*`
|
||||
are accessible.\nAccessible property names are escaped
|
||||
according to the following rules when accessed in
|
||||
the expression:\n- '__' escapes to '__underscores__'\n-
|
||||
'.' escapes to '__dot__'\n- '-' escapes to '__dash__'\n-
|
||||
'/' escapes to '__slash__'\n- Property names that
|
||||
exactly match a CEL RESERVED keyword escape to '__{keyword}__'.
|
||||
The keywords are:\n\t \"true\", \"false\", \"null\",
|
||||
\"in\", \"as\", \"break\", \"const\", \"continue\",
|
||||
\"else\", \"for\", \"function\", \"if\",\n\t \"import\",
|
||||
\"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n
|
||||
\ - Expression accessing a property named \"namespace\":
|
||||
{\"Expression\": \"object.__namespace__ > 0\"}\n
|
||||
\ - Expression accessing a property named \"x-prop\":
|
||||
{\"Expression\": \"object.x__dash__prop > 0\"}\n
|
||||
\ - Expression accessing a property named \"redact__d\":
|
||||
{\"Expression\": \"object.redact__underscores__d
|
||||
> 0\"}\n\nEquality on arrays with list type of 'set'
|
||||
or 'map' ignores element order, i.e. [1, 2] == [2,
|
||||
1].\nConcatenation on arrays with x-kubernetes-list-type
|
||||
use the semantics of the list type:\n - 'set':
|
||||
`X + Y` performs a union where the array positions
|
||||
of all elements in `X` are preserved and\n non-intersecting
|
||||
elements in `Y` are appended, retaining their partial
|
||||
order.\n - 'map': `X + Y` performs a merge where
|
||||
the array positions of all keys in `X` are preserved
|
||||
but the values\n are overwritten by values in
|
||||
`Y` when the key sets of `X` and `Y` intersect.
|
||||
Elements in `Y` with\n non-intersecting keys
|
||||
are appended, retaining their partial order.\nRequired."
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
Message represents the message displayed when validation fails. The message is required if the Expression contains
|
||||
line breaks. The message must not contain line breaks.
|
||||
If unset, the message is "failed rule: {Rule}".
|
||||
e.g. "must be a URL with the host matching spec.host"
|
||||
If the Expression contains line breaks. Message is required.
|
||||
The message must not contain line breaks.
|
||||
If unset, the message is "failed Expression: {Expression}".
|
||||
type: string
|
||||
messageExpression:
|
||||
description: |-
|
||||
messageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails.
|
||||
Since messageExpression is used as a failure message, it must evaluate to a string.
|
||||
If both message and messageExpression are present on a validation, then messageExpression will be used if validation fails.
|
||||
If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced
|
||||
as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string
|
||||
that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and
|
||||
the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged.
|
||||
messageExpression has access to all the same variables as the `expression` except for 'authorizer' and 'authorizer.requestResource'.
|
||||
Example:
|
||||
"object.x must be less than max ("+string(params.max)+")"
|
||||
type: string
|
||||
reason:
|
||||
description: |-
|
||||
Reason represents a machine-readable description of why this validation failed.
|
||||
If this is the first validation in the list to fail, this reason, as well as the
|
||||
corresponding HTTP response code, are used in the
|
||||
HTTP response to the client.
|
||||
The currently supported reasons are: "Unauthorized", "Forbidden", "Invalid", "RequestEntityTooLarge".
|
||||
If not set, StatusReasonInvalid is used in the response to the client.
|
||||
type: string
|
||||
required:
|
||||
- expression
|
||||
type: object
|
||||
type: array
|
||||
variables:
|
||||
items:
|
||||
description: Variable is the definition of a variable
|
||||
that is used for composition. A variable is defined
|
||||
as a named expression.
|
||||
properties:
|
||||
expression:
|
||||
description: |-
|
||||
Expression is the expression that will be evaluated as the value of the variable.
|
||||
The CEL expression has access to the same identifiers as the CEL expressions in Validation.
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables.
|
||||
The variable can be accessed in other expressions through `variables`
|
||||
For example, if name is "foo", the variable will be available as `variables.foo`
|
||||
type: string
|
||||
required:
|
||||
- expression
|
||||
- name
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
generated:
|
||||
description: Generated indicates whether a ValidatingAdmissionPolicy/MutatingAdmissionPolicy
|
||||
is generated from the policy or not
|
||||
type: boolean
|
||||
message:
|
||||
description: |-
|
||||
Message is a human readable message indicating details about the generation of ValidatingAdmissionPolicy/MutatingAdmissionPolicy
|
||||
It is an empty string when ValidatingAdmissionPolicy/MutatingAdmissionPolicy is successfully generated.
|
||||
type: string
|
||||
ready:
|
||||
description: |-
|
||||
The ready of a policy is a high-level summary of where the policy is in its lifecycle.
|
||||
The conditions array, the reason and message fields contain more detail about the policy's status.
|
||||
type: boolean
|
||||
required:
|
||||
- message
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
|
|
|
@ -10514,6 +10514,20 @@ Credentials
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>webhookConfiguration</code><br/>
|
||||
<em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.WebhookConfiguration">
|
||||
WebhookConfiguration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>WebhookConfiguration defines the configuration for the webhook.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>evaluation</code><br/>
|
||||
<em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.EvaluationConfiguration">
|
||||
|
@ -10529,6 +10543,20 @@ EvaluationConfiguration
|
|||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>status</code><br/>
|
||||
<em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.PolicyStatus">
|
||||
PolicyStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Status contains policy runtime data.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
|
@ -11897,6 +11925,20 @@ Credentials
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>webhookConfiguration</code><br/>
|
||||
<em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.WebhookConfiguration">
|
||||
WebhookConfiguration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>WebhookConfiguration defines the configuration for the webhook.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>evaluation</code><br/>
|
||||
<em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.EvaluationConfiguration">
|
||||
|
@ -12154,6 +12196,7 @@ string
|
|||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.ImageVerificationPolicy">ImageVerificationPolicy</a>,
|
||||
<a href="#policies.kyverno.io/v1alpha1.ValidatingPolicy">ValidatingPolicy</a>)
|
||||
</p>
|
||||
<p>
|
||||
|
@ -12609,6 +12652,7 @@ EvaluationConfiguration
|
|||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#policies.kyverno.io/v1alpha1.ImageVerificationPolicySpec">ImageVerificationPolicySpec</a>,
|
||||
<a href="#policies.kyverno.io/v1alpha1.ValidatingPolicySpec">ValidatingPolicySpec</a>)
|
||||
</p>
|
||||
<p>
|
||||
|
|
91
pkg/cel/autogen/ivpol.go
Normal file
91
pkg/cel/autogen/ivpol.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package autogen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/kyverno/kyverno/api/kyverno"
|
||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func GetAutogenRulesImageVerify(policy *policiesv1alpha1.ImageVerificationPolicy) ([]*policiesv1alpha1.ImageVerificationPolicy, error) {
|
||||
if policy == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
applyAutoGen, desiredControllers := CanAutoGen(policy.GetSpec().MatchConstraints)
|
||||
if !applyAutoGen {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var actualControllers sets.Set[string]
|
||||
ann := policy.GetAnnotations()
|
||||
actualControllersString, ok := ann[kyverno.AnnotationAutogenControllers]
|
||||
if !ok {
|
||||
actualControllers = desiredControllers
|
||||
} else {
|
||||
actualControllers = sets.New(strings.Split(actualControllersString, ",")...)
|
||||
}
|
||||
|
||||
genRules, err := autogenIvPols(policy, actualControllers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return genRules, nil
|
||||
}
|
||||
|
||||
func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSet sets.Set[string]) ([]*policiesv1alpha1.ImageVerificationPolicy, error) {
|
||||
genPolicy := func(resource autogencontroller, controllers string) (*policiesv1alpha1.ImageVerificationPolicy, error) {
|
||||
if len(controllers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if ivpol == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var err error
|
||||
policy := ivpol.DeepCopy()
|
||||
operations := ivpol.Spec.MatchConstraints.ResourceRules[0].Operations
|
||||
// create a resource rule for pod controllers
|
||||
policy.Spec.MatchConstraints = createMatchConstraints(controllers, operations)
|
||||
|
||||
// convert match conditions
|
||||
policy.Spec.MatchConditions, err = convertMatchconditions(ivpol.Spec.MatchConditions, resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// convert validations
|
||||
if bytes, err := json.Marshal(policy); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
bytes = updateFields(bytes, resource)
|
||||
if err := json.Unmarshal(bytes, &policy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
ivpols := make([]*policiesv1alpha1.ImageVerificationPolicy, 0)
|
||||
if controllerSet.Has("cronjobs") {
|
||||
p, err := genPolicy(CRONJOBS, "cronjobs")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p != nil {
|
||||
ivpols = append(ivpols, p)
|
||||
}
|
||||
}
|
||||
|
||||
p, err := genPolicy(PODS, strings.Join(sets.List(controllerSet.Delete("cronjobs")), ","))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p != nil {
|
||||
ivpols = append(ivpols, p)
|
||||
}
|
||||
return ivpols, nil
|
||||
}
|
139
pkg/cel/autogen/ivpol_test.go
Normal file
139
pkg/cel/autogen/ivpol_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package autogen
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/kyverno/api/kyverno"
|
||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ivpol = &policiesv1alpha1.ImageVerificationPolicy{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test",
|
||||
Annotations: map[string]string{
|
||||
kyverno.AnnotationAutogenControllers: "cronjobs",
|
||||
},
|
||||
},
|
||||
Spec: policiesv1alpha1.ImageVerificationPolicySpec{
|
||||
MatchConstraints: &admissionregistrationv1.MatchResources{
|
||||
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{""},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ImageRules: []policiesv1alpha1.ImageRule{
|
||||
{
|
||||
Glob: "ghcr.io/*",
|
||||
},
|
||||
},
|
||||
Images: []policiesv1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.containers.map(e, e.image)",
|
||||
},
|
||||
},
|
||||
Attestors: []policiesv1alpha1.Attestor{
|
||||
{
|
||||
Name: "notary",
|
||||
Notary: &policiesv1alpha1.Notary{
|
||||
Certs: `-----BEGIN CERTIFICATE----------END CERTIFICATE-----`,
|
||||
},
|
||||
},
|
||||
},
|
||||
Attestations: []policiesv1alpha1.Attestation{
|
||||
{
|
||||
Name: "sbom",
|
||||
Referrer: &policiesv1alpha1.Referrer{
|
||||
Type: "sbom/cyclone-dx",
|
||||
},
|
||||
},
|
||||
},
|
||||
Verifications: []admissionregistrationv1.Validation{
|
||||
{
|
||||
Expression: "images.bar.map(image, verifyImageSignatures(image, [attestors.notary])).all(e, e > 0)",
|
||||
Message: "failed to verify image with notary cert",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func Test_AutogenImageVerify(t *testing.T) {
|
||||
cronRule := []admissionregistrationv1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"batch"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"cronjobs"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
podctrl := []admissionregistrationv1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments", "statefulsets"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cronimg := []policiesv1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.jobTemplate.spec.template.spec.containers.map(e, e.image)",
|
||||
},
|
||||
}
|
||||
|
||||
podctrlimg := []policiesv1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.template.spec.containers.map(e, e.image)",
|
||||
},
|
||||
}
|
||||
|
||||
autogenerated, err := GetAutogenRulesImageVerify(ivpol)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(autogenerated), 1)
|
||||
assert.Equal(t, autogenerated[0].Spec.MatchConstraints.ResourceRules, cronRule)
|
||||
assert.Equal(t, len(autogenerated[0].Spec.Images), 1)
|
||||
assert.Equal(t, autogenerated[0].Spec.Images, cronimg)
|
||||
|
||||
pol := ivpol
|
||||
pol.Annotations[kyverno.AnnotationAutogenControllers] = "cronjobs,deployments,statefulsets"
|
||||
autogenerated, err = GetAutogenRulesImageVerify(pol)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(autogenerated), 2)
|
||||
assert.Equal(t, autogenerated[0].Spec.MatchConstraints.ResourceRules, cronRule)
|
||||
assert.Equal(t, autogenerated[1].Spec.MatchConstraints.ResourceRules, podctrl)
|
||||
assert.Equal(t, len(autogenerated[1].Spec.Images), 1)
|
||||
assert.Equal(t, autogenerated[1].Spec.Images, podctrlimg)
|
||||
}
|
|
@ -95,6 +95,17 @@ func (c *FakeImageVerificationPolicies) Update(ctx context.Context, imageVerific
|
|||
return obj.(*v1alpha1.ImageVerificationPolicy), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeImageVerificationPolicies) UpdateStatus(ctx context.Context, imageVerificationPolicy *v1alpha1.ImageVerificationPolicy, opts v1.UpdateOptions) (*v1alpha1.ImageVerificationPolicy, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootUpdateSubresourceAction(imageverificationpoliciesResource, "status", imageVerificationPolicy), &v1alpha1.ImageVerificationPolicy{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.ImageVerificationPolicy), err
|
||||
}
|
||||
|
||||
// Delete takes name of the imageVerificationPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeImageVerificationPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
|
|
|
@ -40,6 +40,7 @@ type ImageVerificationPoliciesGetter interface {
|
|||
type ImageVerificationPolicyInterface interface {
|
||||
Create(ctx context.Context, imageVerificationPolicy *v1alpha1.ImageVerificationPolicy, opts v1.CreateOptions) (*v1alpha1.ImageVerificationPolicy, error)
|
||||
Update(ctx context.Context, imageVerificationPolicy *v1alpha1.ImageVerificationPolicy, opts v1.UpdateOptions) (*v1alpha1.ImageVerificationPolicy, error)
|
||||
UpdateStatus(ctx context.Context, imageVerificationPolicy *v1alpha1.ImageVerificationPolicy, opts v1.UpdateOptions) (*v1alpha1.ImageVerificationPolicy, error)
|
||||
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ImageVerificationPolicy, error)
|
||||
|
@ -128,6 +129,21 @@ func (c *imageVerificationPolicies) Update(ctx context.Context, imageVerificatio
|
|||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *imageVerificationPolicies) UpdateStatus(ctx context.Context, imageVerificationPolicy *v1alpha1.ImageVerificationPolicy, opts v1.UpdateOptions) (result *v1alpha1.ImageVerificationPolicy, err error) {
|
||||
result = &v1alpha1.ImageVerificationPolicy{}
|
||||
err = c.client.Put().
|
||||
Resource("imageverificationpolicies").
|
||||
Name(imageVerificationPolicy.Name).
|
||||
SubResource("status").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(imageVerificationPolicy).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the imageVerificationPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *imageVerificationPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
|
|
|
@ -111,6 +111,17 @@ func (c *withLogging) Update(arg0 context.Context, arg1 *github_com_kyverno_kyve
|
|||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
func (c *withLogging) UpdateStatus(arg0 context.Context, arg1 *github_com_kyverno_kyverno_api_policies_kyverno_io_v1alpha1.ImageVerificationPolicy, arg2 k8s_io_apimachinery_pkg_apis_meta_v1.UpdateOptions) (*github_com_kyverno_kyverno_api_policies_kyverno_io_v1alpha1.ImageVerificationPolicy, error) {
|
||||
start := time.Now()
|
||||
logger := c.logger.WithValues("operation", "UpdateStatus")
|
||||
ret0, ret1 := c.inner.UpdateStatus(arg0, arg1, arg2)
|
||||
if err := multierr.Combine(ret1); err != nil {
|
||||
logger.Error(err, "UpdateStatus failed", "duration", time.Since(start))
|
||||
} else {
|
||||
logger.Info("UpdateStatus done", "duration", time.Since(start))
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
func (c *withLogging) Watch(arg0 context.Context, arg1 k8s_io_apimachinery_pkg_apis_meta_v1.ListOptions) (k8s_io_apimachinery_pkg_watch.Interface, error) {
|
||||
start := time.Now()
|
||||
logger := c.logger.WithValues("operation", "Watch")
|
||||
|
@ -156,6 +167,10 @@ func (c *withMetrics) Update(arg0 context.Context, arg1 *github_com_kyverno_kyve
|
|||
defer c.recorder.RecordWithContext(arg0, "update")
|
||||
return c.inner.Update(arg0, arg1, arg2)
|
||||
}
|
||||
func (c *withMetrics) UpdateStatus(arg0 context.Context, arg1 *github_com_kyverno_kyverno_api_policies_kyverno_io_v1alpha1.ImageVerificationPolicy, arg2 k8s_io_apimachinery_pkg_apis_meta_v1.UpdateOptions) (*github_com_kyverno_kyverno_api_policies_kyverno_io_v1alpha1.ImageVerificationPolicy, error) {
|
||||
defer c.recorder.RecordWithContext(arg0, "update_status")
|
||||
return c.inner.UpdateStatus(arg0, arg1, arg2)
|
||||
}
|
||||
func (c *withMetrics) Watch(arg0 context.Context, arg1 k8s_io_apimachinery_pkg_apis_meta_v1.ListOptions) (k8s_io_apimachinery_pkg_watch.Interface, error) {
|
||||
defer c.recorder.RecordWithContext(arg0, "watch")
|
||||
return c.inner.Watch(arg0, arg1)
|
||||
|
@ -314,6 +329,27 @@ func (c *withTracing) Update(arg0 context.Context, arg1 *github_com_kyverno_kyve
|
|||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
func (c *withTracing) UpdateStatus(arg0 context.Context, arg1 *github_com_kyverno_kyverno_api_policies_kyverno_io_v1alpha1.ImageVerificationPolicy, arg2 k8s_io_apimachinery_pkg_apis_meta_v1.UpdateOptions) (*github_com_kyverno_kyverno_api_policies_kyverno_io_v1alpha1.ImageVerificationPolicy, error) {
|
||||
var span trace.Span
|
||||
if tracing.IsInSpan(arg0) {
|
||||
arg0, span = tracing.StartChildSpan(
|
||||
arg0,
|
||||
"",
|
||||
fmt.Sprintf("KUBE %s/%s/%s", c.client, c.kind, "UpdateStatus"),
|
||||
trace.WithAttributes(
|
||||
tracing.KubeClientGroupKey.String(c.client),
|
||||
tracing.KubeClientKindKey.String(c.kind),
|
||||
tracing.KubeClientOperationKey.String("UpdateStatus"),
|
||||
),
|
||||
)
|
||||
defer span.End()
|
||||
}
|
||||
ret0, ret1 := c.inner.UpdateStatus(arg0, arg1, arg2)
|
||||
if span != nil {
|
||||
tracing.SetSpanStatus(span, ret1)
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
func (c *withTracing) Watch(arg0 context.Context, arg1 k8s_io_apimachinery_pkg_apis_meta_v1.ListOptions) (k8s_io_apimachinery_pkg_watch.Interface, error) {
|
||||
var span trace.Span
|
||||
if tracing.IsInSpan(arg0) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/imageverification/imageverifierfunctions"
|
||||
"github.com/kyverno/kyverno/pkg/imageverification/match"
|
||||
"github.com/kyverno/kyverno/pkg/imageverification/variables"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
k8scorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
@ -29,18 +30,18 @@ type Compiler interface {
|
|||
Compile(logr.Logger, *policiesv1alpha1.ImageVerificationPolicy) (CompiledPolicy, field.ErrorList)
|
||||
}
|
||||
|
||||
func NewCompiler(ictx imagedataloader.ImageContext, lister k8scorev1.SecretInterface, isPod bool) Compiler {
|
||||
func NewCompiler(ictx imagedataloader.ImageContext, lister k8scorev1.SecretInterface, reqGVR *metav1.GroupVersionResource) Compiler {
|
||||
return &compiler{
|
||||
ictx: ictx,
|
||||
lister: lister,
|
||||
isPod: isPod,
|
||||
reqGVR: reqGVR,
|
||||
}
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
ictx imagedataloader.ImageContext
|
||||
lister k8scorev1.SecretInterface
|
||||
isPod bool
|
||||
reqGVR *metav1.GroupVersionResource
|
||||
}
|
||||
|
||||
func (c *compiler) Compile(logger logr.Logger, ivpolicy *policiesv1alpha1.ImageVerificationPolicy) (CompiledPolicy, field.ErrorList) {
|
||||
|
@ -52,14 +53,20 @@ func (c *compiler) Compile(logger logr.Logger, ivpolicy *policiesv1alpha1.ImageV
|
|||
var declTypes []*apiservercel.DeclType
|
||||
declTypes = append(declTypes, imageverifierfunctions.Types()...)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable(RequestKey, policy.RequestType.CelType()),
|
||||
cel.Variable(NamespaceObjectKey, policy.NamespaceType.CelType()),
|
||||
cel.Variable(ObjectKey, cel.DynType),
|
||||
cel.Variable(OldObjectKey, cel.DynType),
|
||||
cel.Variable(ImagesKey, cel.MapType(cel.StringType, cel.ListType(cel.StringType))),
|
||||
cel.Variable(AttestorKey, cel.MapType(cel.StringType, cel.StringType)),
|
||||
cel.Variable(AttestationKey, cel.MapType(cel.StringType, cel.StringType)),
|
||||
}
|
||||
|
||||
if ivpolicy.Spec.EvaluationMode() == policiesv1alpha1.EvaluationModeKubernetes {
|
||||
options = append(options, cel.Variable(RequestKey, policy.RequestType.CelType()))
|
||||
options = append(options, cel.Variable(NamespaceObjectKey, policy.NamespaceType.CelType()))
|
||||
options = append(options, cel.Variable(ObjectKey, cel.DynType))
|
||||
options = append(options, cel.Variable(OldObjectKey, cel.DynType))
|
||||
} else {
|
||||
options = append(options, cel.Variable(RequestKey, cel.DynType))
|
||||
}
|
||||
|
||||
for _, declType := range declTypes {
|
||||
options = append(options, cel.Types(declType.CelType()))
|
||||
}
|
||||
|
@ -83,12 +90,12 @@ func (c *compiler) Compile(logger logr.Logger, ivpolicy *policiesv1alpha1.ImageV
|
|||
matchConditions = append(matchConditions, programs...)
|
||||
}
|
||||
imageRules, errs := match.CompileMatches(path.Child("imageRules"), ivpolicy.Spec.ImageRules)
|
||||
if err != nil {
|
||||
if errs != nil {
|
||||
return nil, append(allErrs, errs...)
|
||||
}
|
||||
|
||||
imageExtractors, errs := variables.CompileImageExtractors(path.Child("images"), ivpolicy.Spec.Images, c.isPod)
|
||||
if err != nil {
|
||||
imageExtractors, errs := variables.CompileImageExtractors(path.Child("images"), ivpolicy.Spec.Images, c.reqGVR)
|
||||
if errs != nil {
|
||||
return nil, append(allErrs, errs...)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
k8scorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
@ -20,15 +21,16 @@ func Evaluate(ctx context.Context, logger logr.Logger, ivpols []*v1alpha1.ImageV
|
|||
}
|
||||
|
||||
// TODO: use environmentconfig, add support for other controllers (autogen)
|
||||
isPod := false
|
||||
if r, ok := request.(*admissionv1.AdmissionRequest); ok && r.RequestKind.Group == "" && r.RequestKind.Version == "v1" && r.RequestKind.Kind == "Pod" {
|
||||
isPod = true
|
||||
isAdmissionRequest := false
|
||||
var gvr *metav1.GroupVersionResource
|
||||
if r, ok := request.(*admissionv1.AdmissionRequest); ok {
|
||||
isAdmissionRequest = true
|
||||
gvr = requestGVR(r)
|
||||
}
|
||||
|
||||
isAdmissionRequest := isK8s(request)
|
||||
policies := filterPolicies(ivpols, isAdmissionRequest)
|
||||
|
||||
c := NewCompiler(ictx, lister, isPod)
|
||||
c := NewCompiler(ictx, lister, gvr)
|
||||
results := make([]*EvaluationResult, 0)
|
||||
for _, ivpol := range policies {
|
||||
p, errList := c.Compile(logger, ivpol)
|
||||
|
@ -50,6 +52,14 @@ func isK8s(request interface{}) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
func requestGVR(request *admissionv1.AdmissionRequest) *metav1.GroupVersionResource {
|
||||
if request == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return request.RequestResource
|
||||
}
|
||||
|
||||
func filterPolicies(ivpols []*v1alpha1.ImageVerificationPolicy, isK8s bool) []*v1alpha1.ImageVerificationPolicy {
|
||||
filteredPolicies := make([]*v1alpha1.ImageVerificationPolicy, 0)
|
||||
|
||||
|
|
|
@ -6,24 +6,54 @@ import (
|
|||
"github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
"github.com/kyverno/kyverno/pkg/cel/policy"
|
||||
"github.com/kyverno/kyverno/pkg/cel/utils"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
var podImageExtractors = []v1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.containers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "initContainers",
|
||||
Expression: "request.object.spec.initContainers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "ephemeralContainers",
|
||||
Expression: "request.object.spec.ephemeralContainers.map(e, e.image)",
|
||||
},
|
||||
// TODO: add one for all
|
||||
}
|
||||
var (
|
||||
podImageExtractors = []v1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.containers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "initContainers",
|
||||
Expression: "request.object.spec.initContainers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "ephemeralContainers",
|
||||
Expression: "request.object.spec.ephemeralContainers.map(e, e.image)",
|
||||
},
|
||||
}
|
||||
podControllerImageExtractors = []v1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.template.spec.containers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "initContainers",
|
||||
Expression: "request.object.spec.template.spec.initContainers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "ephemeralContainers",
|
||||
Expression: "request.object.spec.template.spec.ephemeralContainers.map(e, e.image)",
|
||||
},
|
||||
}
|
||||
cronJobImageExtractors = []v1alpha1.Image{
|
||||
{
|
||||
Name: "containers",
|
||||
Expression: "request.object.spec.jobTemplate.spec.template.spec.containers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "initContainers",
|
||||
Expression: "request.object.spec.jobTemplate.spec.template.spec.initContainers.map(e, e.image)",
|
||||
},
|
||||
{
|
||||
Name: "ephemeralContainers",
|
||||
Expression: "request.object.spec.jobTemplate.spec.template.spec.ephemeralContainers.map(e, e.image)",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type CompiledImageExtractor struct {
|
||||
key string
|
||||
|
@ -46,10 +76,10 @@ func (c *CompiledImageExtractor) GetImages(request interface{}) (string, []strin
|
|||
return c.key, result, nil
|
||||
}
|
||||
|
||||
func CompileImageExtractors(path *field.Path, imageExtractors []v1alpha1.Image, isPod bool) ([]*CompiledImageExtractor, field.ErrorList) {
|
||||
func CompileImageExtractors(path *field.Path, imageExtractors []v1alpha1.Image, gvr *metav1.GroupVersionResource) ([]*CompiledImageExtractor, field.ErrorList) {
|
||||
var allErrs field.ErrorList
|
||||
if isPod {
|
||||
imageExtractors = append(imageExtractors, podImageExtractors...)
|
||||
if gvr != nil {
|
||||
imageExtractors = append(imageExtractors, getExtractorForGVR(gvr)...)
|
||||
}
|
||||
|
||||
compiledMatches := make([]*CompiledImageExtractor, 0, len(imageExtractors))
|
||||
|
@ -92,3 +122,30 @@ func ExtractImages(c []*CompiledImageExtractor, request interface{}) (map[string
|
|||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getExtractorForGVR(gvr *metav1.GroupVersionResource) []v1alpha1.Image {
|
||||
if gvr == nil {
|
||||
return []v1alpha1.Image{}
|
||||
}
|
||||
|
||||
if gvr.Group == "batch" && gvr.Version == "v1" {
|
||||
if gvr.Resource == "jobs" {
|
||||
return podControllerImageExtractors
|
||||
} else if gvr.Resource == "cronjobs" {
|
||||
return cronJobImageExtractors
|
||||
}
|
||||
}
|
||||
|
||||
if gvr.Group == "apps" && gvr.Version == "v1" {
|
||||
r := gvr.Resource
|
||||
if r == "deployments" || r == "statefulsets" || r == "daemonsets" || r == "replicasets" {
|
||||
return podControllerImageExtractors
|
||||
}
|
||||
}
|
||||
|
||||
if gvr.Group == "" && gvr.Version == "v1" && gvr.Resource == "pods" {
|
||||
return podImageExtractors
|
||||
}
|
||||
|
||||
return []v1alpha1.Image{}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
|
@ -15,7 +16,7 @@ func Test_Match(t *testing.T) {
|
|||
name string
|
||||
imageExtractor []v1alpha1.Image
|
||||
request any
|
||||
isPod bool
|
||||
gvr *metav1.GroupVersionResource
|
||||
wantResult map[string][]string
|
||||
wantErr bool
|
||||
}{
|
||||
|
@ -39,7 +40,7 @@ func Test_Match(t *testing.T) {
|
|||
"alpine:latest",
|
||||
},
|
||||
},
|
||||
isPod: false,
|
||||
gvr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
@ -102,7 +103,7 @@ func Test_Match(t *testing.T) {
|
|||
"kyverno/ephr-image-two",
|
||||
},
|
||||
},
|
||||
isPod: true,
|
||||
gvr: &metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
@ -116,13 +117,13 @@ func Test_Match(t *testing.T) {
|
|||
request: map[string][]int{
|
||||
"images": {0, 1},
|
||||
},
|
||||
isPod: false,
|
||||
gvr: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c, errList := CompileImageExtractors(field.NewPath("spec", "images"), tt.imageExtractor, tt.isPod)
|
||||
c, errList := CompileImageExtractors(field.NewPath("spec", "images"), tt.imageExtractor, tt.gvr)
|
||||
assert.Nil(t, errList)
|
||||
images, err := ExtractImages(c, tt.request)
|
||||
if tt.wantErr {
|
||||
|
|
Loading…
Add table
Reference in a new issue