1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-21 07:12:42 +00:00

feat: reconcile ivpol.status (#12392)

* feat: update ivpol.status api

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: fix unit tests

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update codegen

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update codegen

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: reconcile ivpol.status

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: unit tests

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: linter issues

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: autogen fields replacement

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: add unit tests

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update ivpol autogen rules

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: invoke ivpol webhook handler

Signed-off-by: ShutingZhao <shuting@nirmata.com>

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
Signed-off-by: shuting <shuting@nirmata.com>
This commit is contained in:
shuting 2025-03-15 07:59:19 +08:00 committed by GitHub
parent af550f54d5
commit 5c5a5fc0b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 5090 additions and 2554 deletions

View file

@ -6,13 +6,17 @@ import (
"github.com/kyverno/kyverno/api/kyverno"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +genclient:nonNamespaced
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:path=imageverificationpolicies,scope="Cluster",shortName=ivpol,categories=kyverno
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="READY",type=string,JSONPath=`.status.ready`
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ImageVerificationPolicy struct {
@ -21,7 +25,24 @@ type ImageVerificationPolicy struct {
Spec ImageVerificationPolicySpec `json:"spec"`
// Status contains policy runtime data.
// +optional
Status PolicyStatus `json:"status,omitempty"`
Status IvpolStatus `json:"status,omitempty"`
}
type IvpolStatus struct {
ConditionStatus *ConditionStatus `json:",inline"`
// +optional
Autogen IvpolAutogenStatus `json:"autogen,omitempty"`
}
type IvpolAutogenStatus struct {
// +optional
Rules []*IvpolAutogen `json:"rules,omitempty"`
}
type IvpolAutogen struct {
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ImageVerificationPolicySpec `json:"spec"`
}
func (s *ImageVerificationPolicy) GetName() string {
@ -68,7 +89,7 @@ func (s *ImageVerificationPolicy) GetSpec() *ImageVerificationPolicySpec {
return &s.Spec
}
func (s *ImageVerificationPolicy) GetStatus() *PolicyStatus {
func (s *ImageVerificationPolicy) GetStatus() *IvpolStatus {
return &s.Status
}
@ -92,6 +113,32 @@ func (s ImageVerificationPolicySpec) BackgroundEnabled() bool {
return *s.EvaluationConfiguration.Background.Enabled
}
func (status *IvpolStatus) SetReadyByCondition(c PolicyConditionType, s metav1.ConditionStatus, message string) {
if status.ConditionStatus == nil {
status.ConditionStatus = &ConditionStatus{}
}
reason := "Succeeded"
if s != metav1.ConditionTrue {
reason = "Failed"
}
newCondition := metav1.Condition{
Type: string(c),
Reason: reason,
Status: s,
Message: message,
}
meta.SetStatusCondition(&status.ConditionStatus.Conditions, newCondition)
}
func (status *IvpolStatus) GetConditionStatus() *ConditionStatus {
if status.ConditionStatus != nil {
return status.ConditionStatus
}
return &ConditionStatus{}
}
// +kubebuilder:object:root=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -382,10 +382,10 @@ func TestImageVerificationPolicy_GetStatus(t *testing.T) {
tests := []struct {
name string
policy *ImageVerificationPolicy
want *PolicyStatus
want *IvpolStatus
}{{
policy: &ImageVerificationPolicy{},
want: &PolicyStatus{},
want: &IvpolStatus{},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View file

@ -13,5 +13,4 @@ type GenericPolicy interface {
GetFailurePolicy() admissionregistrationv1.FailurePolicyType
GetWebhookConfiguration() *WebhookConfiguration
GetVariables() []admissionregistrationv1.Variable
GetStatus() *PolicyStatus
}

View file

@ -1,7 +1,6 @@
package v1alpha1
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -14,7 +13,8 @@ const (
PolicyConditionTypeRBACPermissionsGranted PolicyConditionType = "RBACPermissionsGranted"
)
type PolicyStatus struct {
// ConditionStatus is the shared status across all policy types
type ConditionStatus struct {
// 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.
// +optional
@ -23,33 +23,13 @@ type PolicyStatus struct {
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// +optional
Autogen AutogenStatus `json:"autogen"`
// Generated indicates whether a ValidatingAdmissionPolicy/MutatingAdmissionPolicy is generated from the policy or not
// +optional
Generated bool `json:"generated"`
// 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.
// +optional
Message string `json:"message"`
}
// AutogenStatus contains autogen status information.
type AutogenStatus struct {
// Rules is a list of Rule instances. It contains auto generated rules added for pod controllers
Rules []AutogenRule `json:"rules,omitempty"`
}
type AutogenRule struct {
MatchConstraints *admissionregistrationv1.MatchResources `json:"matchConstraints,omitempty"`
MatchConditions []admissionregistrationv1.MatchCondition `json:"matchConditions,omitempty"`
Validations []admissionregistrationv1.Validation `json:"validations,omitempty"`
AuditAnnotation []admissionregistrationv1.AuditAnnotation `json:"auditAnnotations,omitempty"`
Variables []admissionregistrationv1.Variable `json:"variables,omitempty"`
}
func (status *PolicyStatus) SetReadyByCondition(c PolicyConditionType, s metav1.ConditionStatus, message string) {
func (status *ConditionStatus) SetReadyByCondition(c PolicyConditionType, s metav1.ConditionStatus, message string) {
reason := "Succeeded"
if s != metav1.ConditionTrue {
reason = "Failed"
@ -64,7 +44,7 @@ func (status *PolicyStatus) SetReadyByCondition(c PolicyConditionType, s metav1.
meta.SetStatusCondition(&status.Conditions, newCondition)
}
func (status *PolicyStatus) IsReady() bool {
func (status *ConditionStatus) IsReady() bool {
if status.Ready != nil {
return *status.Ready
}

View file

@ -12,35 +12,39 @@ import (
func TestPolicyStatus_IsReady(t *testing.T) {
tests := []struct {
name string
status PolicyStatus
status VpolStatus
want bool
}{{
name: "nil",
status: PolicyStatus{},
status: VpolStatus{},
want: false,
}, {
name: "true",
status: PolicyStatus{
Ready: ptr.To(true),
status: VpolStatus{
ConditionStatus: &ConditionStatus{
Ready: ptr.To(true),
},
},
want: true,
}, {
name: "false",
status: PolicyStatus{
Ready: ptr.To(false),
status: VpolStatus{
ConditionStatus: &ConditionStatus{
Ready: ptr.To(false),
},
},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.status.IsReady()
got := tt.status.GetConditionStatus().IsReady()
assert.Equal(t, tt.want, got)
})
}
}
func TestPolicyStatus_SetReadyByCondition_True(t *testing.T) {
var status PolicyStatus
var status ConditionStatus
status.SetReadyByCondition(PolicyConditionTypeWebhookConfigured, metav1.ConditionTrue, "dummy")
got := meta.FindStatusCondition(status.Conditions, string(PolicyConditionTypeWebhookConfigured))
assert.NotNil(t, got)
@ -51,7 +55,7 @@ func TestPolicyStatus_SetReadyByCondition_True(t *testing.T) {
}
func TestPolicyStatus_SetReadyByCondition_False(t *testing.T) {
var status PolicyStatus
var status ConditionStatus
status.SetReadyByCondition(PolicyConditionTypeWebhookConfigured, metav1.ConditionFalse, "dummy")
got := meta.FindStatusCondition(status.Conditions, string(PolicyConditionTypeWebhookConfigured))
assert.NotNil(t, got)

View file

@ -20,7 +20,32 @@ type ValidatingPolicy struct {
Spec ValidatingPolicySpec `json:"spec"`
// Status contains policy runtime data.
// +optional
Status PolicyStatus `json:"status,omitempty"`
Status VpolStatus `json:"status,omitempty"`
}
type VpolStatus struct {
ConditionStatus *ConditionStatus `json:",inline"`
// +optional
Autogen AutogenStatus `json:"autogen"`
// Generated indicates whether a ValidatingAdmissionPolicy/MutatingAdmissionPolicy is generated from the policy or not
// +optional
Generated bool `json:"generated"`
}
// AutogenStatus contains autogen status information.
type AutogenStatus struct {
// Rules is a list of Rule instances. It contains auto generated rules added for pod controllers
Rules []AutogenRule `json:"rules,omitempty"`
}
type AutogenRule struct {
MatchConstraints *admissionregistrationv1.MatchResources `json:"matchConstraints,omitempty"`
MatchConditions []admissionregistrationv1.MatchCondition `json:"matchConditions,omitempty"`
Validations []admissionregistrationv1.Validation `json:"validations,omitempty"`
AuditAnnotation []admissionregistrationv1.AuditAnnotation `json:"auditAnnotations,omitempty"`
Variables []admissionregistrationv1.Variable `json:"variables,omitempty"`
}
func (s *ValidatingPolicy) GetMatchConstraints() admissionregistrationv1.MatchResources {
@ -53,7 +78,7 @@ func (s *ValidatingPolicy) GetSpec() *ValidatingPolicySpec {
return &s.Spec
}
func (s *ValidatingPolicy) GetStatus() *PolicyStatus {
func (s *ValidatingPolicy) GetStatus() *VpolStatus {
return &s.Status
}
@ -61,6 +86,13 @@ func (s *ValidatingPolicy) GetKind() string {
return "ValidatingPolicy"
}
func (status *VpolStatus) GetConditionStatus() *ConditionStatus {
if status.ConditionStatus != nil {
return status.ConditionStatus
}
return &ConditionStatus{}
}
// +kubebuilder:object:root=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -220,10 +220,10 @@ func TestValidatingPolicy_GetStatus(t *testing.T) {
tests := []struct {
name string
policy *ValidatingPolicy
want *PolicyStatus
want *VpolStatus
}{{
policy: &ValidatingPolicy{},
want: &PolicyStatus{},
want: &VpolStatus{},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View file

@ -306,6 +306,34 @@ func (in *Certificate) DeepCopy() *Certificate {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConditionStatus) DeepCopyInto(out *ConditionStatus) {
*out = *in
if in.Ready != nil {
in, out := &in.Ready, &out.Ready
*out = new(bool)
**out = **in
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionStatus.
func (in *ConditionStatus) DeepCopy() *ConditionStatus {
if in == nil {
return nil
}
out := new(ConditionStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Cosign) DeepCopyInto(out *Cosign) {
*out = *in
@ -638,6 +666,73 @@ func (in *InToto) DeepCopy() *InToto {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IvpolAutogen) DeepCopyInto(out *IvpolAutogen) {
*out = *in
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IvpolAutogen.
func (in *IvpolAutogen) DeepCopy() *IvpolAutogen {
if in == nil {
return nil
}
out := new(IvpolAutogen)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IvpolAutogenStatus) DeepCopyInto(out *IvpolAutogenStatus) {
*out = *in
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]*IvpolAutogen, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(IvpolAutogen)
(*in).DeepCopyInto(*out)
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IvpolAutogenStatus.
func (in *IvpolAutogenStatus) DeepCopy() *IvpolAutogenStatus {
if in == nil {
return nil
}
out := new(IvpolAutogenStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IvpolStatus) DeepCopyInto(out *IvpolStatus) {
*out = *in
if in.ConditionStatus != nil {
in, out := &in.ConditionStatus, &out.ConditionStatus
*out = new(ConditionStatus)
(*in).DeepCopyInto(*out)
}
in.Autogen.DeepCopyInto(&out.Autogen)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IvpolStatus.
func (in *IvpolStatus) DeepCopy() *IvpolStatus {
if in == nil {
return nil
}
out := new(IvpolStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Key) DeepCopyInto(out *Key) {
*out = *in
@ -712,35 +807,6 @@ func (in *PolicyRef) DeepCopy() *PolicyRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) {
*out = *in
if in.Ready != nil {
in, out := &in.Ready, &out.Ready
*out = new(bool)
**out = **in
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Autogen.DeepCopyInto(&out.Autogen)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyStatus.
func (in *PolicyStatus) DeepCopy() *PolicyStatus {
if in == nil {
return nil
}
out := new(PolicyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Referrer) DeepCopyInto(out *Referrer) {
*out = *in
@ -940,6 +1006,28 @@ func (in *ValidatingPolicySpec) DeepCopy() *ValidatingPolicySpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VpolStatus) DeepCopyInto(out *VpolStatus) {
*out = *in
if in.ConditionStatus != nil {
in, out := &in.ConditionStatus, &out.ConditionStatus
*out = new(ConditionStatus)
(*in).DeepCopyInto(*out)
}
in.Autogen.DeepCopyInto(&out.Autogen)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VpolStatus.
func (in *VpolStatus) DeepCopy() *VpolStatus {
if in == nil {
return nil
}
out := new(VpolStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) {
*out = *in

View file

@ -1334,8 +1334,6 @@ spec:
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

View file

@ -1328,8 +1328,6 @@ spec:
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

View file

@ -50,6 +50,7 @@ import (
webhooksglobalcontext "github.com/kyverno/kyverno/pkg/webhooks/globalcontext"
webhookspolicy "github.com/kyverno/kyverno/pkg/webhooks/policy"
webhooksresource "github.com/kyverno/kyverno/pkg/webhooks/resource"
"github.com/kyverno/kyverno/pkg/webhooks/resource/ivpol"
"github.com/kyverno/kyverno/pkg/webhooks/resource/vpol"
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
@ -605,7 +606,8 @@ func main() {
setup.Logger.Error(err, "failed to create cel context provider")
os.Exit(1)
}
var celEngine celengine.Engine
var vpolEngine celengine.Engine
var ivpolEngine celengine.ImageVerifyEngine
{
// create a controller manager
scheme := kruntime.NewScheme()
@ -644,7 +646,7 @@ func main() {
setup.Logger.Error(err, "failed to create policy provider")
os.Exit(1)
}
celEngine = celengine.NewEngine(
vpolEngine = celengine.NewEngine(
provider.CompiledValidationPolicies,
func(name string) *corev1.Namespace {
ns, err := setup.KubeClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
@ -655,6 +657,19 @@ func main() {
},
matching.NewMatcher(),
)
ivpolEngine = celengine.NewImageVerifyEngine(
provider.ImageVerificationPolicies,
func(name string) *corev1.Namespace {
ns, err := setup.KubeClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return nil
}
return ns
},
matching.NewMatcher(),
setup.KubeClient.CoreV1().Secrets(""),
nil,
)
}
ephrs, err := breaker.StartAdmissionReportsCounter(signalCtx, setup.MetadataClient)
if err != nil {
@ -691,11 +706,15 @@ func main() {
reportsBreaker,
)
voplHandlers := vpol.New(
celEngine,
vpolEngine,
contextProvider,
setup.KyvernoClient,
reportsBreaker,
)
ivpolHandlers := ivpol.New(
ivpolEngine,
contextProvider,
)
exceptionHandlers := webhooksexception.NewHandlers(exception.ValidationOptions{
Enabled: internal.PolicyExceptionEnabled(),
Namespace: internal.ExceptionNamespace(),
@ -712,9 +731,10 @@ func main() {
Validation: webhooks.HandlerFunc(policyHandlers.Validate),
},
webhooks.ResourceHandlers{
Mutation: webhooks.HandlerFunc(resourceHandlers.Mutate),
Validation: webhooks.HandlerFunc(resourceHandlers.Validate),
ValidatingPolicies: webhooks.HandlerFunc(voplHandlers.Validate),
Mutation: webhooks.HandlerFunc(resourceHandlers.Mutate),
Validation: webhooks.HandlerFunc(resourceHandlers.Validate),
ValidatingPolicies: webhooks.HandlerFunc(voplHandlers.Validate),
ImageVerificationPolicies: webhooks.HandlerFunc(ivpolHandlers.Validate),
},
webhooks.ExceptionHandlers{
Validation: webhooks.HandlerFunc(exceptionHandlers.Validate),

View file

@ -1328,8 +1328,6 @@ spec:
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

File diff suppressed because it is too large Load diff

View file

@ -10560,8 +10560,8 @@ EvaluationConfiguration
<td>
<code>status</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.PolicyStatus">
PolicyStatus
<a href="#policies.kyverno.io/v1alpha1.IvpolStatus">
IvpolStatus
</a>
</em>
</td>
@ -10807,8 +10807,8 @@ EvaluationConfiguration
<td>
<code>status</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.PolicyStatus">
PolicyStatus
<a href="#policies.kyverno.io/v1alpha1.VpolStatus">
VpolStatus
</a>
</em>
</td>
@ -11053,7 +11053,7 @@ Kubernetes admissionregistration/v1.MatchResources
</h3>
<p>
(<em>Appears on:</em>
<a href="#policies.kyverno.io/v1alpha1.PolicyStatus">PolicyStatus</a>)
<a href="#policies.kyverno.io/v1alpha1.VpolStatus">VpolStatus</a>)
</p>
<p>
<p>AutogenStatus contains autogen status information.</p>
@ -11304,6 +11304,66 @@ parent intermediate CA certificate of the signing certificate and end with the r
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.ConditionStatus">ConditionStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#policies.kyverno.io/v1alpha1.IvpolStatus">IvpolStatus</a>,
<a href="#policies.kyverno.io/v1alpha1.VpolStatus">VpolStatus</a>)
</p>
<p>
<p>ConditionStatus is the shared status across all policy types</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>ready</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>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&rsquo;s status.</p>
</td>
</tr>
<tr>
<td>
<code>conditions</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#condition-v1-meta">
[]Kubernetes meta/v1.Condition
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>message</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.Cosign">Cosign
</h3>
<p>
@ -11726,7 +11786,8 @@ string
</h3>
<p>
(<em>Appears on:</em>
<a href="#policies.kyverno.io/v1alpha1.ImageVerificationPolicy">ImageVerificationPolicy</a>)
<a href="#policies.kyverno.io/v1alpha1.ImageVerificationPolicy">ImageVerificationPolicy</a>,
<a href="#policies.kyverno.io/v1alpha1.IvpolAutogen">IvpolAutogen</a>)
</p>
<p>
<p>ImageVerificationPolicySpec is the specification of the desired behavior of the ImageVerificationPolicy.</p>
@ -11997,6 +12058,355 @@ string
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.IvpolAutogen">IvpolAutogen
</h3>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>metadata</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#objectmeta-v1-meta">
Kubernetes meta/v1.ObjectMeta
</a>
</em>
</td>
<td>
Refer to the Kubernetes API documentation for the fields of the
<code>metadata</code> field.
</td>
</tr>
<tr>
<td>
<code>spec</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.ImageVerificationPolicySpec">
ImageVerificationPolicySpec
</a>
</em>
</td>
<td>
<br/>
<br/>
<table class="table table-striped">
<tr>
<td>
<code>matchConstraints</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#matchresources-v1-admissionregistration">
Kubernetes admissionregistration/v1.MatchResources
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>MatchConstraints specifies what resources this policy is designed to validate.</p>
</td>
</tr>
<tr>
<td>
<code>failurePolicy</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#failurepolicytype-v1-admissionregistration">
Kubernetes admissionregistration/v1.FailurePolicyType
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>FailurePolicy defines how to handle failures for the admission policy. Failures can
occur from CEL expression parse errors, type check errors, runtime errors and invalid
or mis-configured policy definitions or bindings.</p>
</td>
</tr>
<tr>
<td>
<code>validationActions</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#validationaction-v1-admissionregistration">
[]Kubernetes admissionregistration/v1.ValidationAction
</a>
</em>
</td>
<td>
<p>ValidationAction specifies the action to be taken when the matched resource violates the policy.
Required.</p>
</td>
</tr>
<tr>
<td>
<code>matchConditions</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#matchcondition-v1-admissionregistration">
[]Kubernetes admissionregistration/v1.MatchCondition
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>MatchConditions is a list of conditions that must be met for a request to be validated.
Match conditions filter requests that have already been matched by the rules,
namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
There are a maximum of 64 match conditions allowed.</p>
</td>
</tr>
<tr>
<td>
<code>variables</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#variable-v1-admissionregistration">
[]Kubernetes admissionregistration/v1.Variable
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Variables contain definitions of variables that can be used in composition of other expressions.
Each variable is defined as a named CEL expression.</p>
</td>
</tr>
<tr>
<td>
<code>imageRules</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.ImageRule">
[]ImageRule
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ImagesRules is a list of Glob and CELExpressions to match images.
Any image that matches one of the rules is considered for validation
Any image that does not match a rule is skipped, even when they are passed as arguments to
image verification functions</p>
</td>
</tr>
<tr>
<td>
<code>mutateDigest</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>MutateDigest enables replacement of image tags with digests.
Defaults to true.</p>
</td>
</tr>
<tr>
<td>
<code>verifyDigest</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>VerifyDigest validates that images have a digest.</p>
</td>
</tr>
<tr>
<td>
<code>required</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Required validates that images are verified i.e. have matched passed a signature or attestation check.</p>
</td>
</tr>
<tr>
<td>
<code>credentials</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.Credentials">
Credentials
</a>
</em>
</td>
<td>
<p>Credentials provides credentials that will be used for authentication with registry.</p>
</td>
</tr>
<tr>
<td>
<code>images</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.Image">
[]Image
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Images is a list of CEL expression to extract images from the resource</p>
</td>
</tr>
<tr>
<td>
<code>attestors</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.Attestor">
[]Attestor
</a>
</em>
</td>
<td>
<p>Attestors provides a list of trusted authorities.</p>
</td>
</tr>
<tr>
<td>
<code>attestations</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.Attestation">
[]Attestation
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Attestations provides a list of image metadata to verify</p>
</td>
</tr>
<tr>
<td>
<code>verifications</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#validation-v1-admissionregistration">
[]Kubernetes admissionregistration/v1.Validation
</a>
</em>
</td>
<td>
<p>Verifications contain CEL expressions which is used to apply the image verification checks.</p>
</td>
</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">
EvaluationConfiguration
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>EvaluationConfiguration defines the configuration for the policy evaluation.</p>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.IvpolAutogenStatus">IvpolAutogenStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#policies.kyverno.io/v1alpha1.IvpolStatus">IvpolStatus</a>)
</p>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>rules</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.*github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1.IvpolAutogen">
[]*github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1.IvpolAutogen
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.IvpolStatus">IvpolStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#policies.kyverno.io/v1alpha1.ImageVerificationPolicy">ImageVerificationPolicy</a>)
</p>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>ConditionStatus</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.ConditionStatus">
ConditionStatus
</a>
</em>
</td>
<td>
<p>
(Members of <code>ConditionStatus</code> are embedded into this type.)
</p>
</td>
</tr>
<tr>
<td>
<code>autogen</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.IvpolAutogenStatus">
IvpolAutogenStatus
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.Key">Key
</h3>
<p>
@ -12205,89 +12615,6 @@ string
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.PolicyStatus">PolicyStatus
</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>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>ready</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>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&rsquo;s status.</p>
</td>
</tr>
<tr>
<td>
<code>conditions</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#condition-v1-meta">
[]Kubernetes meta/v1.Condition
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>autogen</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.AutogenStatus">
AutogenStatus
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>generated</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Generated indicates whether a ValidatingAdmissionPolicy/MutatingAdmissionPolicy is generated from the policy or not</p>
</td>
</tr>
<tr>
<td>
<code>message</code><br/>
<em>
string
</em>
</td>
<td>
<p>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.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.Referrer">Referrer
</h3>
<p>
@ -12661,6 +12988,65 @@ EvaluationConfiguration
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.VpolStatus">VpolStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#policies.kyverno.io/v1alpha1.ValidatingPolicy">ValidatingPolicy</a>)
</p>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>ConditionStatus</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.ConditionStatus">
ConditionStatus
</a>
</em>
</td>
<td>
<p>
(Members of <code>ConditionStatus</code> are embedded into this type.)
</p>
</td>
</tr>
<tr>
<td>
<code>autogen</code><br/>
<em>
<a href="#policies.kyverno.io/v1alpha1.AutogenStatus">
AutogenStatus
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>generated</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Generated indicates whether a ValidatingAdmissionPolicy/MutatingAdmissionPolicy is generated from the policy or not</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="policies.kyverno.io/v1alpha1.WebhookConfiguration">WebhookConfiguration
</h3>
<p>

View file

@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
)
func GetAutogenRulesImageVerify(policy *policiesv1alpha1.ImageVerificationPolicy) ([]*policiesv1alpha1.ImageVerificationPolicy, error) {
func GetAutogenRulesImageVerify(policy *policiesv1alpha1.ImageVerificationPolicy) ([]*policiesv1alpha1.IvpolAutogen, error) {
if policy == nil {
return nil, nil
}
@ -27,7 +27,6 @@ func GetAutogenRulesImageVerify(policy *policiesv1alpha1.ImageVerificationPolicy
} else {
actualControllers = sets.New(strings.Split(actualControllersString, ",")...)
}
genRules, err := autogenIvPols(policy, actualControllers)
if err != nil {
return nil, err
@ -36,8 +35,8 @@ func GetAutogenRulesImageVerify(policy *policiesv1alpha1.ImageVerificationPolicy
return genRules, nil
}
func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSet sets.Set[string]) ([]*policiesv1alpha1.ImageVerificationPolicy, error) {
genPolicy := func(resource autogencontroller, controllers string) (*policiesv1alpha1.ImageVerificationPolicy, error) {
func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSet sets.Set[string]) ([]*policiesv1alpha1.IvpolAutogen, error) {
genPolicy := func(resource autogencontroller, controllers string) (policy *policiesv1alpha1.IvpolAutogen, err error) {
if len(controllers) == 0 {
return nil, nil
}
@ -45,8 +44,11 @@ func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSe
if ivpol == nil {
return nil, nil
}
var err error
policy := ivpol.DeepCopy()
policy = &policiesv1alpha1.IvpolAutogen{}
copied := ivpol.DeepCopy()
policy.ObjectMeta = copied.ObjectMeta
policy.Spec = copied.Spec
if controllers == "cronjobs" {
policy.Name = "autogen-cronjobs-" + policy.Name
} else {
@ -57,7 +59,7 @@ func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSe
policy.Spec.MatchConstraints = createMatchConstraints(controllers, operations)
// convert match conditions
policy.Spec.MatchConditions, err = convertMatchconditions(ivpol.Spec.MatchConditions, resource)
policy.Spec.MatchConditions, err = convertMatchConditions(policy.Spec.MatchConditions, resource)
if err != nil {
return nil, err
}
@ -74,7 +76,7 @@ func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSe
return policy, nil
}
ivpols := make([]*policiesv1alpha1.ImageVerificationPolicy, 0)
ivpols := make([]*policiesv1alpha1.IvpolAutogen, 0)
if controllerSet.Has("cronjobs") {
p, err := genPolicy(CRONJOBS, "cronjobs")
if err != nil {
@ -85,7 +87,8 @@ func autogenIvPols(ivpol *policiesv1alpha1.ImageVerificationPolicy, controllerSe
}
}
p, err := genPolicy(PODS, strings.Join(sets.List(controllerSet.Delete("cronjobs")), ","))
controllerSetCopied := controllerSet.Clone()
p, err := genPolicy(PODS, strings.Join(sets.List(controllerSetCopied.Delete("cronjobs")), ","))
if err != nil {
return nil, err
}

View file

@ -17,56 +17,36 @@ var (
CRONJOBS autogencontroller = "cronjobs"
)
func generateRuleForControllers(spec *policiesv1alpha1.ValidatingPolicySpec, controllers string, resource autogencontroller) (*policiesv1alpha1.AutogenRule, error) {
func generateRuleForControllers(spec *policiesv1alpha1.ValidatingPolicySpec, controllers string, resource autogencontroller) (autogenRule *policiesv1alpha1.AutogenRule, err error) {
operations := spec.MatchConstraints.ResourceRules[0].Operations
newSpec := &policiesv1alpha1.ValidatingPolicySpec{}
// create a resource rule for pod controllers
matchConstraints := createMatchConstraints(controllers, operations)
newSpec.MatchConstraints = createMatchConstraints(controllers, operations)
// convert match conditions
matchConditions, err := convertMatchconditions(spec.MatchConditions, resource)
newSpec.MatchConditions, err = convertMatchConditions(spec.MatchConditions, resource)
if err != nil {
return nil, err
}
// convert validations
validations := spec.Validations
if bytes, err := json.Marshal(validations); err != nil {
newSpec.Validations = spec.Validations
newSpec.AuditAnnotations = spec.AuditAnnotations
newSpec.Variables = spec.Variables
if bytes, err := json.Marshal(newSpec); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, resource)
if err := json.Unmarshal(bytes, &validations); err != nil {
return nil, err
}
}
// convert audit annotations
auditAnnotations := spec.AuditAnnotations
if bytes, err := json.Marshal(auditAnnotations); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, resource)
if err := json.Unmarshal(bytes, &auditAnnotations); err != nil {
return nil, err
}
}
// convert variables
variables := spec.Variables
if bytes, err := json.Marshal(variables); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, resource)
if err := json.Unmarshal(bytes, &variables); err != nil {
if err := json.Unmarshal(bytes, &newSpec); err != nil {
return nil, err
}
}
return &policiesv1alpha1.AutogenRule{
MatchConstraints: matchConstraints,
MatchConditions: matchConditions,
Validations: validations,
AuditAnnotation: auditAnnotations,
Variables: variables,
MatchConstraints: newSpec.MatchConstraints,
MatchConditions: newSpec.MatchConditions,
Validations: newSpec.Validations,
AuditAnnotation: newSpec.AuditAnnotations,
Variables: newSpec.Variables,
}, nil
}
@ -124,15 +104,15 @@ func createMatchConstraints(controllers string, operations []admissionregistrati
}
}
func convertMatchconditions(conditions []admissionregistrationv1.MatchCondition, resource autogencontroller) (matchConditions []admissionregistrationv1.MatchCondition, err error) {
func convertMatchConditions(conditions []admissionregistrationv1.MatchCondition, resource autogencontroller) (matchConditions []admissionregistrationv1.MatchCondition, err error) {
var name, expression string
switch resource {
case PODS:
name = podControllerMatchConditionName
expression = podControllersMatchConditionExpression
expression = PodControllersMatchConditionExpression
case CRONJOBS:
name = cronjobMatchConditionName
expression = cronJobMatchConditionExpression
expression = CronJobMatchConditionExpression
}
for _, m := range conditions {
@ -140,22 +120,22 @@ func convertMatchconditions(conditions []admissionregistrationv1.MatchCondition,
m.Expression = expression + m.Expression
matchConditions = append(matchConditions, m)
}
if bytes, err := json.Marshal(matchConditions); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, resource)
if err := json.Unmarshal(bytes, &matchConditions); err != nil {
return nil, err
}
}
// if bytes, err := json.Marshal(matchConditions); err != nil {
// return nil, err
// } else {
// bytes = updateFields(bytes, resource)
// if err := json.Unmarshal(bytes, &matchConditions); err != nil {
// return nil, err
// }
// }
return matchConditions, nil
}
var (
podControllerMatchConditionName = "autogen-"
podControllersMatchConditionExpression = "!(object.kind =='Deployment' || object.kind =='ReplicaSet' || object.kind =='StatefulSet' || object.kind =='DaemonSet') || "
PodControllersMatchConditionExpression = "!(object.kind =='Deployment' || object.kind =='ReplicaSet' || object.kind =='StatefulSet' || object.kind =='DaemonSet') || "
cronjobMatchConditionName = "autogen-cronjobs-"
cronJobMatchConditionExpression = "!(object.kind =='CronJob') || "
CronJobMatchConditionExpression = "!(object.kind =='CronJob') || "
)
func updateFields(data []byte, resource autogencontroller) []byte {

View file

@ -6,7 +6,6 @@ import (
"fmt"
"reflect"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/kyverno"
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
"github.com/kyverno/kyverno/pkg/cel/matching"
@ -31,7 +30,6 @@ type ImageVerifyEngine interface {
}
type ivengine struct {
logger logr.Logger
provider ImageVerifyPolProviderFunc
nsResolver NamespaceResolver
matcher matching.Matcher
@ -39,9 +37,8 @@ type ivengine struct {
registryOpts []imagedataloader.Option
}
func NewImageVerifyEngine(logger logr.Logger, provider ImageVerifyPolProviderFunc, nsResolver NamespaceResolver, matcher matching.Matcher, lister k8scorev1.SecretInterface, registryOpts []imagedataloader.Option) ImageVerifyEngine {
func NewImageVerifyEngine(provider ImageVerifyPolProviderFunc, nsResolver NamespaceResolver, matcher matching.Matcher, lister k8scorev1.SecretInterface, registryOpts []imagedataloader.Option) ImageVerifyEngine {
return &ivengine{
logger: logger,
provider: provider,
nsResolver: nsResolver,
matcher: matcher,
@ -203,7 +200,7 @@ func (e *ivengine) handleMutation(ctx context.Context, policies []CompiledImageV
Actions: ivpol.Actions,
}
if p, errList := c.Compile(e.logger, ivpol.Policy); errList != nil {
if p, errList := c.Compile(ivpol.Policy); errList != nil {
response.Result = *engineapi.RuleError("evaluation", engineapi.ImageVerify, "failed to compile policy", errList.ToAggregate(), nil)
} else {
result, err := p.Evaluate(ctx, ictx, attr, request, namespace, true)
@ -227,7 +224,7 @@ func (e *ivengine) handleMutation(ctx context.Context, policies []CompiledImageV
if err != nil {
return nil, nil, err
}
patches, err := eval.MakeImageVerifyOutcomePatch(len(ann) != 0, e.logger, results)
patches, err := eval.MakeImageVerifyOutcomePatch(len(ann) != 0, results)
if err != nil {
return nil, nil, err
}

View file

@ -5,7 +5,6 @@ import (
"encoding/json"
"testing"
"github.com/go-logr/logr"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
"github.com/kyverno/kyverno/pkg/cel/matching"
@ -148,7 +147,7 @@ uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
)
func Test_ImageVerifyEngine(t *testing.T) {
engine := NewImageVerifyEngine(logr.Discard(), ivfunc, nsResolver, matching.NewMatcher(), nil, nil)
engine := NewImageVerifyEngine(ivfunc, nsResolver, matching.NewMatcher(), nil, nil)
engineRequest := EngineRequest{
request: v1.AdmissionRequest{
Operation: v1.Create,

View file

@ -304,7 +304,10 @@ func (r *ivpolpolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
Name: p.Name,
}
r.policies[namespacedName.String()] = CompiledImageVerificationPolicy{
Policy: p,
Policy: &policiesv1alpha1.ImageVerificationPolicy{
ObjectMeta: p.ObjectMeta,
Spec: p.Spec,
},
Actions: actions,
}
}

View file

@ -54,6 +54,8 @@ const (
VerifyMutatingWebhookName = "monitor-webhooks.kyverno.svc"
// ValidatingPolicyWebhookName defines default webhook name for validatingpolicies
ValidatingPolicyWebhookName = "vpol.validate.kyverno.svc"
// ImageVerificationPolicyWebhookName defines default webhook name for imageverificationpolicies
ImageVerificationPolicyWebhookName = "ivpol.validate.kyverno.svc"
)
// paths
@ -62,8 +64,12 @@ const (
PolicyValidatingWebhookServicePath = "/policyvalidate"
// ValidatingWebhookServicePath is the path for validation webhook
ValidatingWebhookServicePath = "/validate"
// ValidatingPolicyServicePath is the path for validating policies execution
ValidatingPolicyServicePath = "/policies"
// PolicyServicePath is the prefix path for policies execution
PolicyServicePath = "/policies"
// ValidatingPolicyServicePath is the sub path for validatingpolicies execution
ValidatingPolicyServicePath = "/vpol"
// ImageVerificationPolicyServicePath is the sub path for imageverificationpolicies execution
ImageVerificationPolicyServicePath = "/ivpol"
// ExceptionValidatingWebhookServicePath is the path for policy exception validation webhook(used to validate policy exception resource)
ExceptionValidatingWebhookServicePath = "/exceptionvalidate"
// CELExceptionValidatingWebhookServicePath is the path for CELPolicyException validation webhook(used to validate CELPolicyException resource)

View file

@ -8,15 +8,15 @@ import (
"github.com/go-logr/logr"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
auth "github.com/kyverno/kyverno/pkg/auth/checker"
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
policiesv1alpha1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/policies.kyverno.io/v1alpha1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/controllers"
"github.com/kyverno/kyverno/pkg/controllers/webhook"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
"go.uber.org/multierr"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/workqueue"
@ -76,27 +76,54 @@ func (c *controller) watchdog(ctx context.Context, logger logr.Logger) {
}
func (c controller) reconcile(ctx context.Context, logger logr.Logger, key string, namespace string, name string) error {
vpol, err := c.client.PoliciesV1alpha1().ValidatingPolicies().Get(ctx, name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
logger.V(4).Info("validating policy not found", "name", name)
return nil
polType, polName := webhook.ParsePolicyKey(key)
if polType == webhook.ValidatingPolicyType {
vpol, err := c.client.PoliciesV1alpha1().ValidatingPolicies().Get(ctx, polName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
logger.V(4).Info("validating policy not found", "name", polName)
return nil
}
return err
}
return err
}
return c.updateStatus(ctx, vpol)
return c.updateVpolStatus(ctx, vpol)
}
if polType == webhook.ImageVerificationPolicy {
ivpol, err := c.client.PoliciesV1alpha1().ImageVerificationPolicies().Get(ctx, polName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
logger.V(4).Info("imageVerification policy not found", "name", polName)
return nil
}
return err
}
return c.updateIvpolStatus(ctx, ivpol)
}
return nil
}
func (c controller) reconcileConditions(ctx context.Context, vpol *policiesv1alpha1.ValidatingPolicy) {
if ready, ok := c.vpolStateRecorder.Ready(vpol.GetName()); ready {
vpol.GetStatus().SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeWebhookConfigured, metav1.ConditionTrue, "Webhook configured.")
func (c controller) reconcileConditions(ctx context.Context, policy engineapi.GenericPolicy) {
var key string
var matchConstraints admissionregistrationv1.MatchResources
status := &policiesv1alpha1.ConditionStatus{}
switch policy.GetKind() {
case webhook.ValidatingPolicyType:
key = webhook.BuildPolicyKey(webhook.ValidatingPolicyType, policy.GetName())
matchConstraints = policy.AsValidatingPolicy().GetMatchConstraints()
case webhook.ImageVerificationPolicy:
key = webhook.BuildPolicyKey(webhook.ImageVerificationPolicy, policy.GetName())
matchConstraints = policy.AsImageVerificationPolicy().GetMatchConstraints()
}
if ready, ok := c.vpolStateRecorder.Ready(key); ready {
status.SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeWebhookConfigured, metav1.ConditionTrue, "Webhook configured.")
} else if ok {
vpol.GetStatus().SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeWebhookConfigured, metav1.ConditionFalse, "Policy is not configured in the webhook.")
status.SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeWebhookConfigured, metav1.ConditionFalse, "Policy is not configured in the webhook.")
}
gvrs := []metav1.GroupVersionResource{}
for _, rule := range vpol.GetMatchConstraints().ResourceRules {
for _, rule := range matchConstraints.ResourceRules {
for _, g := range rule.RuleWithOperations.APIGroups {
for _, v := range rule.RuleWithOperations.APIVersions {
for _, r := range rule.RuleWithOperations.Resources {
@ -123,57 +150,8 @@ func (c controller) reconcileConditions(ctx context.Context, vpol *policiesv1alp
}
if errs != nil {
vpol.GetStatus().SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeRBACPermissionsGranted, metav1.ConditionFalse, fmt.Sprintf("Policy is not ready for reporting, missing permissions: %v.", multierr.Combine(errs...)))
status.SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeRBACPermissionsGranted, metav1.ConditionFalse, fmt.Sprintf("Policy is not ready for reporting, missing permissions: %v.", multierr.Combine(errs...)))
} else {
vpol.GetStatus().SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeRBACPermissionsGranted, metav1.ConditionTrue, "Policy is ready for reporting.")
status.SetReadyByCondition(policiesv1alpha1.PolicyConditionTypeRBACPermissionsGranted, metav1.ConditionTrue, "Policy is ready for reporting.")
}
}
func (c controller) updateStatus(ctx context.Context, vpol *policiesv1alpha1.ValidatingPolicy) error {
updateFunc := func(vpol *policiesv1alpha1.ValidatingPolicy) error {
c.reconcileConditions(ctx, vpol)
status := vpol.GetStatus()
status.Autogen.Rules = nil
rules := vpolautogen.ComputeRules(vpol)
status.Autogen.Rules = append(status.Autogen.Rules, rules...)
ready := true
for _, condition := range status.Conditions {
if condition.Status != metav1.ConditionTrue {
ready = false
break
}
}
if status.Ready == nil || *status.Ready != ready {
status.Ready = &ready
}
return nil
}
err := controllerutils.UpdateStatus(ctx,
vpol,
c.client.PoliciesV1alpha1().ValidatingPolicies(),
updateFunc,
func(current, expect *policiesv1alpha1.ValidatingPolicy) bool {
if current.GetStatus().Ready == nil || current.GetStatus().IsReady() != expect.GetStatus().IsReady() {
return false
}
if len(current.GetStatus().Conditions) != len(expect.GetStatus().Conditions) {
return false
}
for _, condition := range current.GetStatus().Conditions {
for _, expectCondition := range expect.GetStatus().Conditions {
if condition.Type == expectCondition.Type && condition.Status != expectCondition.Status {
return false
}
}
}
return datautils.DeepEqual(current.GetStatus().Autogen, expect.GetStatus().Autogen)
},
)
return err
}

View file

@ -0,0 +1,69 @@
package policystatus
import (
"context"
"fmt"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
celautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (c controller) updateIvpolStatus(ctx context.Context, ivpol *policiesv1alpha1.ImageVerificationPolicy) error {
updateFunc := func(ivpol *policiesv1alpha1.ImageVerificationPolicy) error {
p := engineapi.NewImageVerificationPolicy(ivpol)
c.reconcileConditions(ctx, p)
status := ivpol.GetStatus()
status.Autogen.Rules = nil
autogeneratedIvPols, err := celautogen.GetAutogenRulesImageVerify(ivpol)
if err != nil {
return fmt.Errorf("failed to build autogen rules for ivpol %s: %v", ivpol.GetName(), err)
}
status.Autogen.Rules = append(status.Autogen.Rules, autogeneratedIvPols...)
ready := true
conditionStatus := status.GetConditionStatus()
for _, condition := range conditionStatus.Conditions {
if condition.Status != metav1.ConditionTrue {
ready = false
break
}
}
if conditionStatus.Ready == nil || conditionStatus.IsReady() != ready {
conditionStatus.Ready = &ready
}
status.ConditionStatus = conditionStatus
return nil
}
err := controllerutils.UpdateStatus(ctx,
ivpol,
c.client.PoliciesV1alpha1().ImageVerificationPolicies(),
updateFunc,
func(current, expect *policiesv1alpha1.ImageVerificationPolicy) bool {
if current.GetStatus().GetConditionStatus().Ready == nil || current.GetStatus().GetConditionStatus().IsReady() != expect.GetStatus().GetConditionStatus().IsReady() {
return false
}
if len(current.GetStatus().GetConditionStatus().Conditions) != len(expect.GetStatus().GetConditionStatus().Conditions) {
return false
}
for _, condition := range current.GetStatus().GetConditionStatus().Conditions {
for _, expectCondition := range expect.GetStatus().GetConditionStatus().Conditions {
if condition.Type == expectCondition.Type && condition.Status != expectCondition.Status {
return false
}
}
}
return datautils.DeepEqual(current.GetStatus().Autogen, expect.GetStatus().Autogen)
},
)
return err
}

View file

@ -0,0 +1,64 @@
package policystatus
import (
"context"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (c controller) updateVpolStatus(ctx context.Context, vpol *policiesv1alpha1.ValidatingPolicy) error {
updateFunc := func(vpol *policiesv1alpha1.ValidatingPolicy) error {
p := engineapi.NewValidatingPolicy(vpol)
c.reconcileConditions(ctx, p)
status := vpol.GetStatus()
status.Autogen.Rules = nil
rules := vpolautogen.ComputeRules(vpol)
status.Autogen.Rules = append(status.Autogen.Rules, rules...)
ready := true
conditionStatus := status.GetConditionStatus()
for _, condition := range conditionStatus.Conditions {
if condition.Status != metav1.ConditionTrue {
ready = false
break
}
}
if conditionStatus.Ready == nil || conditionStatus.IsReady() != ready {
conditionStatus.Ready = &ready
}
status.ConditionStatus = conditionStatus
return nil
}
err := controllerutils.UpdateStatus(ctx,
vpol,
c.client.PoliciesV1alpha1().ValidatingPolicies(),
updateFunc,
func(current, expect *policiesv1alpha1.ValidatingPolicy) bool {
if current.GetStatus().GetConditionStatus().Ready == nil || current.GetStatus().GetConditionStatus().IsReady() != expect.GetStatus().GetConditionStatus().IsReady() {
return false
}
if len(current.GetStatus().GetConditionStatus().Conditions) != len(expect.GetStatus().GetConditionStatus().Conditions) {
return false
}
for _, condition := range current.GetStatus().GetConditionStatus().Conditions {
for _, expectCondition := range expect.GetStatus().GetConditionStatus().Conditions {
if condition.Type == expectCondition.Type && condition.Status != expectCondition.Status {
return false
}
}
}
return datautils.DeepEqual(current.GetStatus().Autogen, expect.GetStatus().Autogen)
},
)
return err
}

View file

@ -520,7 +520,7 @@ func (c *controller) updatePolicyStatus(ctx context.Context, policy engineapi.Ge
} else if vpol := policy.AsValidatingPolicy(); vpol != nil {
latest := vpol.DeepCopy()
latest.Status.Generated = generated
latest.Status.Message = msg
latest.Status.GetConditionStatus().Message = msg
new, err := c.kyvernoClient.PoliciesV1alpha1().ValidatingPolicies().UpdateStatus(ctx, latest, metav1.UpdateOptions{})
if err != nil {

View file

@ -380,7 +380,7 @@ func (c *controller) enqueueVerifyWebhook() {
c.queue.Add(config.VerifyMutatingWebhookConfigurationName)
}
func (c *controller) recordPolicyState(webhookConfigurationName string, policies ...kyvernov1.PolicyInterface) {
func (c *controller) recordKyvernoPolicyState(webhookConfigurationName string, policies ...kyvernov1.PolicyInterface) {
c.lock.Lock()
defer c.lock.Unlock()
if _, ok := c.policyState[webhookConfigurationName]; !ok {
@ -397,9 +397,11 @@ func (c *controller) recordPolicyState(webhookConfigurationName string, policies
}
}
func (c *controller) recordValidatingPolicyState(validatingpolicies ...engineapi.GenericPolicy) {
for _, policy := range validatingpolicies {
c.vpolStateRecorder.Record(policy.GetName())
func (c *controller) recordPolicyState(policies ...engineapi.GenericPolicy) {
for _, policy := range policies {
if key := BuildPolicyKey(policy.GetKind(), policy.GetName()); key != "" {
c.vpolStateRecorder.Record(key)
}
}
}
@ -822,7 +824,7 @@ func (c *controller) buildResourceMutatingWebhookConfiguration(ctx context.Conte
return nil, err
}
var fineGrainedIgnoreList, fineGrainedFailList []*webhook
c.recordPolicyState(config.MutatingWebhookConfigurationName, policies...)
c.recordKyvernoPolicyState(config.MutatingWebhookConfigurationName, policies...)
for _, p := range policies {
if p.AdmissionProcessingEnabled() {
spec := p.GetSpec()
@ -852,7 +854,7 @@ func (c *controller) buildResourceMutatingWebhookConfiguration(ctx context.Conte
webhooks = append(webhooks, fineGrainedFailList...)
result.Webhooks = c.buildResourceMutatingWebhookRules(caBundle, webhookCfg, &noneOnDryRun, webhooks)
} else {
c.recordPolicyState(config.MutatingWebhookConfigurationName)
c.recordKyvernoPolicyState(config.MutatingWebhookConfigurationName)
}
return &result, nil
}
@ -968,22 +970,31 @@ func (c *controller) buildForValidatingPolicies(cfg config.Configuration, caBund
return nil
}
var policies []engineapi.GenericPolicy
pols, err := c.getValidatingPolicies()
if err != nil {
return err
}
policies = append(policies, pols...)
result.Webhooks = append(result.Webhooks, buildWebhookRules(cfg,
c.server,
config.ValidatingPolicyWebhookName,
config.PolicyServicePath+config.ValidatingPolicyServicePath,
c.servicePort,
caBundle,
pols)...)
ivpols, err := c.getImageVerificationPolicy()
if err != nil {
return err
}
policies = append(policies, ivpols...)
result.Webhooks = append(result.Webhooks, buildWebhookRules(cfg,
c.server,
config.ImageVerificationPolicyWebhookName,
config.PolicyServicePath+config.ImageVerificationPolicyServicePath,
c.servicePort,
caBundle,
ivpols)...)
webhooks := buildWebhookRules(cfg, c.server, c.servicePort, caBundle, policies)
result.Webhooks = append(result.Webhooks, webhooks...)
c.recordValidatingPolicyState(policies...)
c.recordPolicyState(append(pols, ivpols...)...)
return nil
}
@ -998,7 +1009,7 @@ func (c *controller) buildForPolicies(ctx context.Context, cfg config.Configurat
}
var fineGrainedIgnoreList, fineGrainedFailList []*webhook
c.recordPolicyState(config.ValidatingWebhookConfigurationName, policies...)
c.recordKyvernoPolicyState(config.ValidatingWebhookConfigurationName, policies...)
for _, p := range policies {
if p.AdmissionProcessingEnabled() {
spec := p.GetSpec()
@ -1032,7 +1043,7 @@ func (c *controller) buildForPolicies(ctx context.Context, cfg config.Configurat
webhooks = append(webhooks, fineGrainedFailList...)
result.Webhooks = c.buildResourceValidatingWebhookRules(caBundle, webhookCfg, sideEffects, webhooks)
} else {
c.recordPolicyState(config.ValidatingWebhookConfigurationName)
c.recordKyvernoPolicyState(config.ValidatingWebhookConfigurationName)
}
return nil
}

View file

@ -174,3 +174,28 @@ func less[T cmp.Ordered](a []T, b []T) int {
}
return 0
}
const (
ValidatingPolicyType = "ValidatingPolicy"
ImageVerificationPolicy = "ImageVerificationPolicy"
)
// ParsePolicyKey builds policy key in kind/name format
func BuildPolicyKey(policyType, name string) string {
switch policyType {
case ValidatingPolicyType:
return ValidatingPolicyType + "/" + name
case ImageVerificationPolicy:
return ImageVerificationPolicy + "/" + name
}
return ""
}
// ParsePolicyKey parses policy key in kind/name format
func ParsePolicyKey(key string) (policyType, name string) {
vars := strings.Split(key, "/")
if len(vars) < 2 {
return "", ""
}
return vars[0], vars[1]
}

View file

@ -9,20 +9,20 @@ import (
"k8s.io/utils/ptr"
)
func buildWebhookRules(cfg config.Configuration, server string, servicePort int32, caBundle []byte, policies []engineapi.GenericPolicy) (webhooks []admissionregistrationv1.ValidatingWebhook) {
func buildWebhookRules(cfg config.Configuration, server, name, path string, servicePort int32, caBundle []byte, policies []engineapi.GenericPolicy) (webhooks []admissionregistrationv1.ValidatingWebhook) {
var (
webhookIgnoreList []admissionregistrationv1.ValidatingWebhook
webhookFailList []admissionregistrationv1.ValidatingWebhook
webhookIgnore = admissionregistrationv1.ValidatingWebhook{
Name: config.ValidatingPolicyWebhookName + "-ignore",
ClientConfig: newClientConfig(server, servicePort, caBundle, config.ValidatingPolicyServicePath+"/ignore"),
Name: name + "-ignore",
ClientConfig: newClientConfig(server, servicePort, caBundle, path+"/ignore"),
FailurePolicy: ptr.To(admissionregistrationv1.Ignore),
SideEffects: &noneOnDryRun,
AdmissionReviewVersions: []string{"v1"},
}
webhookFail = admissionregistrationv1.ValidatingWebhook{
Name: config.ValidatingPolicyWebhookName + "-fail",
ClientConfig: newClientConfig(server, servicePort, caBundle, config.ValidatingPolicyServicePath+"/fail"),
Name: name + "-fail",
ClientConfig: newClientConfig(server, servicePort, caBundle, path+"/fail"),
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
SideEffects: &noneOnDryRun,
AdmissionReviewVersions: []string{"v1"},
@ -45,6 +45,7 @@ func buildWebhookRules(cfg config.Configuration, server string, servicePort int3
matchResource = vpol.Spec.MatchConstraints
} else if ivpol := pol.AsImageVerificationPolicy(); ivpol != nil {
p = ivpol
matchResource = ivpol.Spec.MatchConstraints
}
webhook := admissionregistrationv1.ValidatingWebhook{}
@ -91,16 +92,29 @@ func buildWebhookRules(cfg config.Configuration, server string, servicePort int3
}
}
if ivpol, ok := p.(*policiesv1alpha1.ImageVerificationPolicy); ok {
autogeneratedIvPols, err := autogen.GetAutogenRulesImageVerify(ivpol)
if err != nil {
continue
}
for _, p := range autogeneratedIvPols {
webhook.MatchConditions = append(webhook.MatchConditions, p.Spec.MatchConditions...)
for _, match := range p.Spec.MatchConstraints.ResourceRules {
webhook.Rules = append(webhook.Rules, match.RuleWithOperations)
}
}
}
if fineGrainedWebhook {
webhook.SideEffects = &noneOnDryRun
webhook.AdmissionReviewVersions = []string{"v1"}
if failurePolicyIgnore {
webhook.Name = config.ValidatingPolicyWebhookName + "-ignore-finegrained-" + p.GetName()
webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, config.ValidatingPolicyServicePath+"/ignore"+config.FineGrainedWebhookPath+"/"+p.GetName())
webhook.Name = name + "-ignore-finegrained-" + p.GetName()
webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, path+"/ignore"+config.FineGrainedWebhookPath+"/"+p.GetName())
webhookIgnoreList = append(webhookIgnoreList, webhook)
} else {
webhook.Name = config.ValidatingPolicyWebhookName + "-fail-finegrained-" + p.GetName()
webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, config.ValidatingPolicyServicePath+"/fail"+config.FineGrainedWebhookPath+"/"+p.GetName())
webhook.Name = name + "-fail-finegrained-" + p.GetName()
webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, path+"/fail"+config.FineGrainedWebhookPath+"/"+p.GetName())
webhookFailList = append(webhookFailList, webhook)
}
} else {

View file

@ -1,9 +1,12 @@
package webhook
import (
"fmt"
"testing"
"github.com/kyverno/kyverno/api/kyverno"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
celautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/stretchr/testify/assert"
@ -12,7 +15,7 @@ import (
"k8s.io/utils/ptr"
)
func TestBuildWebhookRules(t *testing.T) {
func TestBuildWebhookRules_ValidatingPolicy(t *testing.T) {
tests := []struct {
name string
vpols []*policiesv1alpha1.ValidatingPolicy
@ -136,7 +139,7 @@ func TestBuildWebhookRules(t *testing.T) {
expectedWebhooks: []admissionregistrationv1.ValidatingWebhook{
{
Name: config.ValidatingPolicyWebhookName + "-ignore-finegrained-test-fine-grained-ignore",
ClientConfig: newClientConfig("", 0, nil, "/policies/ignore"+config.FineGrainedWebhookPath+"/test-fine-grained-ignore"),
ClientConfig: newClientConfig("", 0, nil, "/policies/vpol/ignore"+config.FineGrainedWebhookPath+"/test-fine-grained-ignore"),
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
@ -194,7 +197,7 @@ func TestBuildWebhookRules(t *testing.T) {
expectedWebhooks: []admissionregistrationv1.ValidatingWebhook{
{
Name: config.ValidatingPolicyWebhookName + "-fail-finegrained-test-fine-grained-fail",
ClientConfig: newClientConfig("", 0, nil, "/policies/fail"+config.FineGrainedWebhookPath+"/test-fine-grained-fail"),
ClientConfig: newClientConfig("", 0, nil, "/policies/vpol/fail"+config.FineGrainedWebhookPath+"/test-fine-grained-fail"),
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
@ -226,7 +229,8 @@ func TestBuildWebhookRules(t *testing.T) {
for _, vpol := range tt.vpols {
vpols = append(vpols, engineapi.NewValidatingPolicy(vpol))
}
webhooks := buildWebhookRules(config.NewDefaultConfiguration(false), "", 0, nil, vpols)
webhooks := buildWebhookRules(config.NewDefaultConfiguration(false), "", config.ValidatingPolicyWebhookName,
config.PolicyServicePath+config.ValidatingPolicyServicePath, 0, nil, vpols)
assert.Equal(t, len(tt.expectedWebhooks), len(webhooks))
for i, expect := range tt.expectedWebhooks {
assert.Equal(t, expect.Name, webhooks[i].Name)
@ -249,3 +253,215 @@ func TestBuildWebhookRules(t *testing.T) {
})
}
}
func TestBuildWebhookRules_ImageVerificationPolicy(t *testing.T) {
tests := []struct {
name string
ivpols []*policiesv1alpha1.ImageVerificationPolicy
expectedWebhooks []admissionregistrationv1.ValidatingWebhook
}{
{
name: "Autogen Single Ignore Policy",
ivpols: []*policiesv1alpha1.ImageVerificationPolicy{
{
Spec: policiesv1alpha1.ImageVerificationPolicySpec{
FailurePolicy: ptr.To(admissionregistrationv1.Ignore),
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"pods"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
},
},
},
},
},
},
expectedWebhooks: []admissionregistrationv1.ValidatingWebhook{
{
Name: config.ImageVerificationPolicyWebhookName + "-ignore",
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"pods"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"daemonsets", "deployments", "replicasets", "statefulsets"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"jobs"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"cronjobs"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
},
FailurePolicy: ptr.To(admissionregistrationv1.Ignore),
},
},
},
{
name: "Autogen Fine-grained Ignore Policy",
ivpols: []*policiesv1alpha1.ImageVerificationPolicy{
{
ObjectMeta: metav1.ObjectMeta{
Name: "ivpol-sample",
Annotations: map[string]string{
kyverno.AnnotationAutogenControllers: "deployments,jobs,cronjobs",
},
},
Spec: policiesv1alpha1.ImageVerificationPolicySpec{
FailurePolicy: ptr.To(admissionregistrationv1.Ignore),
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"pods"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
},
},
},
MatchConditions: []admissionregistrationv1.MatchCondition{
{
Name: "check-prod-label",
Expression: "has(object.metadata.labels) && has(object.metadata.labels.prod) && object.metadata.labels.prod == 'true'",
},
},
},
},
},
expectedWebhooks: []admissionregistrationv1.ValidatingWebhook{
{
Name: config.ImageVerificationPolicyWebhookName + "-ignore-finegrained-ivpol-sample",
ClientConfig: newClientConfig("", 0, nil, "/policies/ivpol/ignore"+config.FineGrainedWebhookPath+"/ivpol-sample"),
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"pods"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"jobs"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"cronjobs"},
Scope: ptr.To(admissionregistrationv1.ScopeType("*")),
},
},
},
FailurePolicy: ptr.To(admissionregistrationv1.Ignore),
MatchConditions: []admissionregistrationv1.MatchCondition{
{
Name: "check-prod-label",
Expression: "!(object.kind == 'Pod') || has(object.metadata.labels) && has(object.metadata.labels.prod) && object.metadata.labels.prod == 'true'",
},
{
Name: "autogen-check-prod-label",
Expression: celautogen.PodControllersMatchConditionExpression + "has(object.spec.template.metadata.labels) && has(object.spec.template.metadata.labels.prod) && object.spec.template.metadata.labels.prod == 'true'",
},
{
Name: "autogen-cronjobs-check-prod-label",
Expression: celautogen.CronJobMatchConditionExpression + "has(object.spec.jobTemplate.spec.template.metadata.labels) && has(object.spec.jobTemplate.spec.template.metadata.labels.prod) && object.spec.jobTemplate.spec.template.metadata.labels.prod == 'true'",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var ivpols []engineapi.GenericPolicy
for _, ivpol := range tt.ivpols {
ivpols = append(ivpols, engineapi.NewImageVerificationPolicy(ivpol))
}
webhooks := buildWebhookRules(config.NewDefaultConfiguration(false), "", config.ImageVerificationPolicyWebhookName,
config.PolicyServicePath+config.ImageVerificationPolicyServicePath, 0, nil, ivpols)
assert.Equal(t, len(tt.expectedWebhooks), len(webhooks), tt.name)
for i, expect := range tt.expectedWebhooks {
assert.Equal(t, expect.Name, webhooks[i].Name)
assert.Equal(t, expect.FailurePolicy, webhooks[i].FailurePolicy)
assert.Equal(t, len(expect.Rules), len(webhooks[i].Rules), fmt.Sprintf("expected: %v,\n got: %v", expect.Rules, webhooks[i].Rules))
if expect.MatchConditions != nil {
for m, mExpect := range expect.MatchConditions {
for n, mActual := range webhooks[i].MatchConditions {
if mExpect.Name != mActual.Name {
continue
}
assert.Equal(t, expect.MatchConditions[m], webhooks[i].MatchConditions[n])
}
}
}
if expect.MatchPolicy != nil {
assert.Equal(t, expect.MatchPolicy, webhooks[i].MatchPolicy)
}
if expect.TimeoutSeconds != nil {
assert.Equal(t, expect.TimeoutSeconds, webhooks[i].TimeoutSeconds)
}
if expect.ClientConfig.Service != nil {
assert.Equal(t, *webhooks[i].ClientConfig.Service.Path, *expect.ClientConfig.Service.Path)
}
}
})
}
}

View file

@ -1,7 +1,6 @@
package eval
import (
"github.com/go-logr/logr"
"github.com/google/cel-go/cel"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
engine "github.com/kyverno/kyverno/pkg/cel"
@ -32,7 +31,7 @@ const (
)
type Compiler interface {
Compile(logr.Logger, *policiesv1alpha1.ImageVerificationPolicy) (CompiledPolicy, field.ErrorList)
Compile(*policiesv1alpha1.ImageVerificationPolicy) (CompiledPolicy, field.ErrorList)
}
func NewCompiler(ictx imagedataloader.ImageContext, lister k8scorev1.SecretInterface, reqGVR *metav1.GroupVersionResource) Compiler {
@ -49,7 +48,7 @@ type compiler struct {
reqGVR *metav1.GroupVersionResource
}
func (c *compiler) Compile(logger logr.Logger, ivpolicy *policiesv1alpha1.ImageVerificationPolicy) (CompiledPolicy, field.ErrorList) {
func (c *compiler) Compile(ivpolicy *policiesv1alpha1.ImageVerificationPolicy) (CompiledPolicy, field.ErrorList) {
var allErrs field.ErrorList
base, err := engine.NewEnv()
if err != nil {
@ -77,7 +76,7 @@ func (c *compiler) Compile(logger logr.Logger, ivpolicy *policiesv1alpha1.ImageV
for _, declType := range declTypes {
options = append(options, cel.Types(declType.CelType()))
}
options = append(options, imageverifierfunctions.Lib(logger, c.ictx, ivpolicy, c.lister), context.Lib(), http.Lib(), user.Lib())
options = append(options, imageverifierfunctions.Lib(c.ictx, ivpolicy, c.lister), context.Lib(), http.Lib(), user.Lib())
env, err := base.Extend(options...)
if err != nil {
return nil, append(allErrs, field.InternalError(nil, err))

View file

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
admissionv1 "k8s.io/api/admission/v1"
@ -14,7 +13,7 @@ import (
k8scorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
func Evaluate(ctx context.Context, logger logr.Logger, ivpols []*v1alpha1.ImageVerificationPolicy, request interface{}, admissionAttr admission.Attributes, namespace runtime.Object, lister k8scorev1.SecretInterface, registryOpts ...imagedataloader.Option) (map[string]*EvaluationResult, error) {
func Evaluate(ctx context.Context, ivpols []*v1alpha1.ImageVerificationPolicy, request interface{}, admissionAttr admission.Attributes, namespace runtime.Object, lister k8scorev1.SecretInterface, registryOpts ...imagedataloader.Option) (map[string]*EvaluationResult, error) {
ictx, err := imagedataloader.NewImageContext(lister, registryOpts...)
if err != nil {
return nil, err
@ -32,7 +31,7 @@ func Evaluate(ctx context.Context, logger logr.Logger, ivpols []*v1alpha1.ImageV
c := NewCompiler(ictx, lister, gvr)
results := make(map[string]*EvaluationResult, len(policies))
for _, ivpol := range policies {
p, errList := c.Compile(logger, ivpol)
p, errList := c.Compile(ivpol)
if errList != nil {
return nil, fmt.Errorf("failed to compile policy %v", errList)
}

View file

@ -4,7 +4,6 @@ import (
"context"
"testing"
"github.com/go-logr/logr"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
"github.com/stretchr/testify/assert"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
@ -92,12 +91,12 @@ uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
)
func Test_Eval(t *testing.T) {
result, err := Evaluate(context.Background(), logr.Discard(), []*policiesv1alpha1.ImageVerificationPolicy{ivpol}, obj(signedImage), nil, nil, nil)
result, err := Evaluate(context.Background(), []*policiesv1alpha1.ImageVerificationPolicy{ivpol}, obj(signedImage), nil, nil, nil)
assert.NoError(t, err)
assert.True(t, len(result) == 1)
assert.True(t, result[ivpol.Name].Result)
result, err = Evaluate(context.Background(), logr.Discard(), []*policiesv1alpha1.ImageVerificationPolicy{ivpol}, obj(unsignedImage), nil, nil, nil)
result, err = Evaluate(context.Background(), []*policiesv1alpha1.ImageVerificationPolicy{ivpol}, obj(unsignedImage), nil, nil, nil)
assert.NoError(t, err)
assert.True(t, len(result) == 1)
assert.False(t, result[ivpol.Name].Result)

View file

@ -0,0 +1,5 @@
package eval
import "github.com/kyverno/kyverno/pkg/logging"
var logger = logging.WithName("ivpol/evaluator")

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"strings"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/kyverno"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -55,7 +54,7 @@ func outcomeFromPolicyResponse(responses map[string]ImageVerifyPolicyResponse) m
return outcomes
}
func MakeImageVerifyOutcomePatch(hasAnnotations bool, log logr.Logger, responses map[string]ImageVerifyPolicyResponse) ([]jsonpatch.JsonPatchOperation, error) {
func MakeImageVerifyOutcomePatch(hasAnnotations bool, responses map[string]ImageVerifyPolicyResponse) ([]jsonpatch.JsonPatchOperation, error) {
patches := make([]jsonpatch.JsonPatchOperation, 0)
annotationKey := "/metadata/annotations/" + strings.ReplaceAll(kyverno.AnnotationImageVerifyOutcomes, "/", "~1")
if !hasAnnotations {
@ -64,7 +63,7 @@ func MakeImageVerifyOutcomePatch(hasAnnotations bool, log logr.Logger, responses
Path: "/metadata/annotations",
Value: map[string]string{},
}
log.V(4).Info("adding annotation patch", "patch", patch)
logger.V(4).Info("adding annotation patch", "patch", patch)
patches = append(patches, patch)
}
@ -80,7 +79,7 @@ func MakeImageVerifyOutcomePatch(hasAnnotations bool, log logr.Logger, responses
Value: string(data),
}
log.V(4).Info("adding image verification patch", "patch", patch)
logger.V(4).Info("adding image verification patch", "patch", patch)
patches = append(patches, patch)
return patches, nil
}

View file

@ -3,7 +3,6 @@ package imageverifierfunctions
import (
"testing"
"github.com/go-logr/logr"
"github.com/google/cel-go/cel"
"github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
@ -57,7 +56,7 @@ uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
func Test_impl_verify_image_signature_string_stringarray(t *testing.T) {
imgCtx, err := imagedataloader.NewImageContext(nil)
assert.NoError(t, err)
opts := Lib(logr.Discard(), imgCtx, ivpol, nil)
opts := Lib(imgCtx, ivpol, nil)
env, err := cel.NewEnv(opts)
assert.NoError(t, err)
assert.NotNil(t, env)
@ -79,7 +78,7 @@ func Test_impl_verify_image_signature_string_stringarray(t *testing.T) {
func Test_impl_verify_image_attestations_string_string_stringarray(t *testing.T) {
imgCtx, err := imagedataloader.NewImageContext(nil)
assert.NoError(t, err)
opts := Lib(logr.Discard(), imgCtx, ivpol, nil)
opts := Lib(imgCtx, ivpol, nil)
env, err := cel.NewEnv(opts)
assert.NoError(t, err)
assert.NotNil(t, env)

View file

@ -21,7 +21,7 @@ type lib struct {
lister k8scorev1.SecretInterface
}
func Lib(logger logr.Logger, imgCtx imagedataloader.ImageContext, ivpol *v1alpha1.ImageVerificationPolicy, lister k8scorev1.SecretInterface) cel.EnvOption {
func Lib(imgCtx imagedataloader.ImageContext, ivpol *v1alpha1.ImageVerificationPolicy, lister k8scorev1.SecretInterface) cel.EnvOption {
// create the cel lib env option
return cel.Lib(&lib{
imgCtx: imgCtx,

View file

@ -70,6 +70,7 @@ func NewServer(
globalContextLogger := logger.WithName("globalcontext")
verifyLogger := logger.WithName("verify")
vpolLogger := logger.WithName("vpol")
ivpolLogger := logger.WithName("ivpol")
registerWebhookHandlersWithAll(
mux,
"MUTATE",
@ -106,7 +107,7 @@ func NewServer(
registerWebhookHandlers(
mux,
"VPOL",
config.ValidatingPolicyServicePath,
config.PolicyServicePath+config.ValidatingPolicyServicePath,
resourceHandlers.ValidatingPolicies,
func(handler handlers.AdmissionHandler) handlers.HttpHandler {
return handler.
@ -119,6 +120,22 @@ func NewServer(
WithAdmission(vpolLogger.WithName("validate"))
},
)
registerWebhookHandlers(
mux,
"IVPOL",
config.PolicyServicePath+config.ImageVerificationPolicyServicePath,
resourceHandlers.ImageVerificationPolicies,
func(handler handlers.AdmissionHandler) handlers.HttpHandler {
return handler.
WithFilter(configuration).
WithProtection(toggle.FromContext(ctx).ProtectManagedResources()).
WithDump(debugModeOpts.DumpPayload).
WithTopLevelGVK(discovery).
WithRoles(rbLister, crbLister).
WithMetrics(resourceLogger, metricsConfig.Config(), metrics.WebhookValidating).
WithAdmission(ivpolLogger.WithName("validate"))
},
)
mux.HandlerFunc(
"POST",
config.PolicyMutatingWebhookServicePath,

View file

@ -54,4 +54,6 @@ type ResourceHandlers struct {
Validation Handler
// ValidatingPolicies evaluates validating policies against kube resources
ValidatingPolicies Handler
// ImageVerificationPolicies evaluates imageverificationpolicies against kube resources
ImageVerificationPolicies Handler
}

View file

@ -11,7 +11,7 @@ webhooks:
service:
name: kyverno-svc
namespace: kyverno
path: /policies/fail/finegrained/disallow-privilege-escalation
path: /policies/vpol/fail/finegrained/disallow-privilege-escalation
port: 443
failurePolicy: Fail
matchConditions:

View file

@ -11,7 +11,7 @@ webhooks:
service:
name: kyverno-svc
namespace: kyverno
path: /policies/fail
path: /policies/vpol/fail
port: 443
failurePolicy: Fail
matchPolicy: Equivalent

View file

@ -11,7 +11,7 @@ webhooks:
service:
name: kyverno-svc
namespace: kyverno
path: /policies/fail
path: /policies/vpol/fail
port: 443
failurePolicy: Fail
matchPolicy: Equivalent