mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-15 08:46:36 +00:00
merge and update
Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
commit
90edc69dcf
44 changed files with 5837 additions and 6138 deletions
File diff suppressed because it is too large
Load diff
|
@ -1502,6 +1502,160 @@ spec:
|
|||
in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
foreach:
|
||||
description: ForEach applies policy rule checks to nested
|
||||
elements.
|
||||
properties:
|
||||
anyPattern:
|
||||
description: AnyPattern specifies list of validation
|
||||
patterns. At least one of the patterns must be satisfied
|
||||
for the validation rule to succeed.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
context:
|
||||
description: Context defines variables and data sources
|
||||
that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data
|
||||
sources to a rule Context. Either a ConfigMap reference
|
||||
or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall defines an HTTP request to
|
||||
the Kubernetes API server. The JSON data retrieved
|
||||
is stored in the context.
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON
|
||||
Match Expression that can be used to transform
|
||||
the JSON response returned from the API
|
||||
server. For example a JMESPath of "items
|
||||
| length(@)" applied to the API server response
|
||||
to the URLPath "/apis/apps/v1/deployments"
|
||||
will return the total count of deployments
|
||||
across all namespaces.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be
|
||||
used in the HTTP GET request to the Kubernetes
|
||||
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
|
||||
The format required is the same format used
|
||||
by the `kubectl get --raw` command.
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
deny:
|
||||
description: Deny defines conditions used to pass or
|
||||
fail a validation rule.
|
||||
properties:
|
||||
conditions:
|
||||
description: 'Multiple conditions can be declared
|
||||
under an `any` or `all` statement. A direct list
|
||||
of conditions (without `any` or `all` statements)
|
||||
is also supported for backwards compatibility
|
||||
but will be deprecated in the next major release.
|
||||
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
list:
|
||||
description: List specifies a JMESPath expression that
|
||||
results in one or more elements to which the validation
|
||||
logic is applied.
|
||||
type: string
|
||||
pattern:
|
||||
description: Pattern specifies an overlay-style pattern
|
||||
used to check resources.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
preconditions:
|
||||
description: 'Preconditions are used to determine if
|
||||
a policy rule should be applied by evaluating a set
|
||||
of conditions. The declaration can contain nested
|
||||
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
|
||||
properties:
|
||||
all:
|
||||
description: AllConditions enable variable-based
|
||||
conditional rule execution. This is useful for
|
||||
finer control of when an rule is applied. A condition
|
||||
can reference object data using JMESPath notation.
|
||||
Here, all of the conditions need to pass
|
||||
items:
|
||||
description: Condition defines variable-based
|
||||
conditional criteria for rule execution.
|
||||
properties:
|
||||
key:
|
||||
description: Key is the context entry (using
|
||||
JMESPath) for conditional rule evaluation.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
operator:
|
||||
description: Operator is the operation to
|
||||
perform. Valid operators are Equals, NotEquals,
|
||||
In and NotIn.
|
||||
enum:
|
||||
- Equals
|
||||
- NotEquals
|
||||
- In
|
||||
- NotIn
|
||||
type: string
|
||||
value:
|
||||
description: Value is the conditional value,
|
||||
or set of values. The values can be fixed
|
||||
set or can be variables declared using using
|
||||
JMESPath.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
type: array
|
||||
any:
|
||||
description: AnyConditions enable variable-based
|
||||
conditional rule execution. This is useful for
|
||||
finer control of when an rule is applied. A condition
|
||||
can reference object data using JMESPath notation.
|
||||
Here, at least one of the conditions need to pass
|
||||
items:
|
||||
description: Condition defines variable-based
|
||||
conditional criteria for rule execution.
|
||||
properties:
|
||||
key:
|
||||
description: Key is the context entry (using
|
||||
JMESPath) for conditional rule evaluation.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
operator:
|
||||
description: Operator is the operation to
|
||||
perform. Valid operators are Equals, NotEquals,
|
||||
In and NotIn.
|
||||
enum:
|
||||
- Equals
|
||||
- NotEquals
|
||||
- In
|
||||
- NotIn
|
||||
type: string
|
||||
value:
|
||||
description: Value is the conditional value,
|
||||
or set of values. The values can be fixed
|
||||
set or can be variables declared using using
|
||||
JMESPath.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
message:
|
||||
description: Message specifies a custom message to be displayed
|
||||
on failure.
|
||||
|
|
|
@ -108,6 +108,16 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
admissionRequestInfo:
|
||||
description: Adding required request information to GR
|
||||
properties:
|
||||
admissionRequest:
|
||||
description: Adding Admission Request to GR.
|
||||
type: string
|
||||
operation:
|
||||
description: Current request operation
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
policy:
|
||||
description: Specifies the name of the policy.
|
||||
|
|
|
@ -1503,6 +1503,160 @@ spec:
|
|||
in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
foreach:
|
||||
description: ForEach applies policy rule checks to nested
|
||||
elements.
|
||||
properties:
|
||||
anyPattern:
|
||||
description: AnyPattern specifies list of validation
|
||||
patterns. At least one of the patterns must be satisfied
|
||||
for the validation rule to succeed.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
context:
|
||||
description: Context defines variables and data sources
|
||||
that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data
|
||||
sources to a rule Context. Either a ConfigMap reference
|
||||
or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall defines an HTTP request to
|
||||
the Kubernetes API server. The JSON data retrieved
|
||||
is stored in the context.
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON
|
||||
Match Expression that can be used to transform
|
||||
the JSON response returned from the API
|
||||
server. For example a JMESPath of "items
|
||||
| length(@)" applied to the API server response
|
||||
to the URLPath "/apis/apps/v1/deployments"
|
||||
will return the total count of deployments
|
||||
across all namespaces.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be
|
||||
used in the HTTP GET request to the Kubernetes
|
||||
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
|
||||
The format required is the same format used
|
||||
by the `kubectl get --raw` command.
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
deny:
|
||||
description: Deny defines conditions used to pass or
|
||||
fail a validation rule.
|
||||
properties:
|
||||
conditions:
|
||||
description: 'Multiple conditions can be declared
|
||||
under an `any` or `all` statement. A direct list
|
||||
of conditions (without `any` or `all` statements)
|
||||
is also supported for backwards compatibility
|
||||
but will be deprecated in the next major release.
|
||||
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
list:
|
||||
description: List specifies a JMESPath expression that
|
||||
results in one or more elements to which the validation
|
||||
logic is applied.
|
||||
type: string
|
||||
pattern:
|
||||
description: Pattern specifies an overlay-style pattern
|
||||
used to check resources.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
preconditions:
|
||||
description: 'Preconditions are used to determine if
|
||||
a policy rule should be applied by evaluating a set
|
||||
of conditions. The declaration can contain nested
|
||||
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
|
||||
properties:
|
||||
all:
|
||||
description: AllConditions enable variable-based
|
||||
conditional rule execution. This is useful for
|
||||
finer control of when an rule is applied. A condition
|
||||
can reference object data using JMESPath notation.
|
||||
Here, all of the conditions need to pass
|
||||
items:
|
||||
description: Condition defines variable-based
|
||||
conditional criteria for rule execution.
|
||||
properties:
|
||||
key:
|
||||
description: Key is the context entry (using
|
||||
JMESPath) for conditional rule evaluation.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
operator:
|
||||
description: Operator is the operation to
|
||||
perform. Valid operators are Equals, NotEquals,
|
||||
In and NotIn.
|
||||
enum:
|
||||
- Equals
|
||||
- NotEquals
|
||||
- In
|
||||
- NotIn
|
||||
type: string
|
||||
value:
|
||||
description: Value is the conditional value,
|
||||
or set of values. The values can be fixed
|
||||
set or can be variables declared using using
|
||||
JMESPath.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
type: array
|
||||
any:
|
||||
description: AnyConditions enable variable-based
|
||||
conditional rule execution. This is useful for
|
||||
finer control of when an rule is applied. A condition
|
||||
can reference object data using JMESPath notation.
|
||||
Here, at least one of the conditions need to pass
|
||||
items:
|
||||
description: Condition defines variable-based
|
||||
conditional criteria for rule execution.
|
||||
properties:
|
||||
key:
|
||||
description: Key is the context entry (using
|
||||
JMESPath) for conditional rule evaluation.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
operator:
|
||||
description: Operator is the operation to
|
||||
perform. Valid operators are Equals, NotEquals,
|
||||
In and NotIn.
|
||||
enum:
|
||||
- Equals
|
||||
- NotEquals
|
||||
- In
|
||||
- NotIn
|
||||
type: string
|
||||
value:
|
||||
description: Value is the conditional value,
|
||||
or set of values. The values can be fixed
|
||||
set or can be variables declared using using
|
||||
JMESPath.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
message:
|
||||
description: Message specifies a custom message to be displayed
|
||||
on failure.
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1
go.mod
1
go.mod
|
@ -18,6 +18,7 @@ require (
|
|||
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210216200643-d81088d9983e
|
||||
github.com/googleapis/gnostic v0.5.4
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.2.1-0.20210806133539-f50646681592 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -45,6 +46,15 @@ type GenerateRequestSpec struct {
|
|||
type GenerateRequestContext struct {
|
||||
// +optional
|
||||
UserRequestInfo RequestInfo `json:"userInfo,omitempty" yaml:"userInfo,omitempty"`
|
||||
// +optional
|
||||
AdmissionRequestInfo AdmissionRequestInfoObject `json:"admissionRequestInfo,omitempty" yaml:"admissionRequestInfo,omitempty"`
|
||||
}
|
||||
|
||||
type AdmissionRequestInfoObject struct {
|
||||
// +optional
|
||||
AdmissionRequest string `json:"admissionRequest,omitempty" yaml:"admissionRequest,omitempty"`
|
||||
// +optional
|
||||
Operation v1beta1.Operation `json:"operation,omitempty" yaml:"operation,omitempty"`
|
||||
}
|
||||
|
||||
// RequestInfo contains permission info carried in an admission request.
|
||||
|
|
|
@ -451,6 +451,7 @@ type Deny struct {
|
|||
AnyAllConditions apiextensions.JSON `json:"conditions,omitempty" yaml:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// ForEach applies policy rule checks to nested elements.
|
||||
type ForEachValidation struct {
|
||||
|
||||
// List specifies a JMESPath expression that results in one or more elements
|
||||
|
@ -500,8 +501,24 @@ type ImageVerification struct {
|
|||
// If specified Repository will override the default OCI image repository configured for the installation.
|
||||
Repository string `json:"repository,omitempty" yaml:"repository,omitempty"`
|
||||
|
||||
// Attestations are optional statements used to verify the image.
|
||||
Attestations []*AnyAllConditions `json:"attestations,omitempty" yaml:"attestations,omitempty"`
|
||||
// Attestations are optional checks for signed in-toto Statements used to verify the image.
|
||||
// See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the
|
||||
// OCI registry and decodes them into a list of Statement declarations.
|
||||
Attestations []*Attestation `json:"attestations,omitempty" yaml:"attestations,omitempty"`
|
||||
}
|
||||
|
||||
// Attestation are checks for signed in-toto Statements that are used to verify the image.
|
||||
// See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the
|
||||
// OCI registry and decodes them into a list of Statements.
|
||||
type Attestation struct {
|
||||
|
||||
// PredicateType defines the type of Predicate contained within the Statement.
|
||||
PredicateType string `json:"predicateType,omitempty" yaml:"predicateType,omitempty"`
|
||||
|
||||
// Conditions are used to verify attributes within a Predicate. If no Conditions are specified
|
||||
// the attestation check is satisfied as long there are predicates that match the predicate type.
|
||||
// +optional
|
||||
Conditions []*AnyAllConditions `json:"conditions,omitempty" yaml:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// Generation defines how new resources should be created and managed.
|
||||
|
|
|
@ -199,6 +199,6 @@ type ViolatedRule struct {
|
|||
// +optional
|
||||
Message string `json:"message" yaml:"message"`
|
||||
|
||||
// +optional
|
||||
Check string `json:"check" yaml:"check"`
|
||||
// Status shows the rule response status
|
||||
Status string `json:"status" yaml:"status"`
|
||||
}
|
||||
|
|
26
pkg/cosign/client.go
Normal file
26
pkg/cosign/client.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package cosign
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
)
|
||||
|
||||
var client Cosign = &driver{}
|
||||
|
||||
func setClient() {
|
||||
client = &driver{}
|
||||
}
|
||||
|
||||
type Cosign interface {
|
||||
Verify(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) ([]cosign.SignedPayload, error)
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
}
|
||||
|
||||
func (d *driver) Verify(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) ([]cosign.SignedPayload, error) {
|
||||
return d.Verify(ctx, signedImgRef, co)
|
||||
}
|
||||
|
||||
|
|
@ -6,7 +6,10 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
|
||||
"github.com/sigstore/cosign/pkg/cosign/attestation"
|
||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
||||
"strings"
|
||||
|
||||
|
@ -46,6 +49,7 @@ func Initialize(client kubernetes.Interface, namespace, serviceAccount string, i
|
|||
}
|
||||
|
||||
func VerifySignature(imageRef string, key []byte, repository string, log logr.Logger) (digest string, err error) {
|
||||
setClient()
|
||||
pubKey, err := decodePEM(key)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to decode PEM %v", string(key))
|
||||
|
@ -75,7 +79,7 @@ func VerifySignature(imageRef string, key []byte, repository string, log logr.Lo
|
|||
cosignOpts.SignatureRepo = signatureRepo
|
||||
}
|
||||
|
||||
verified, err := cosign.Verify(context.Background(), ref, cosignOpts)
|
||||
verified, err := client.Verify(context.Background(), ref, cosignOpts)
|
||||
if err != nil {
|
||||
msg := err.Error()
|
||||
logger.Info("image verification failed", "error", msg)
|
||||
|
@ -96,7 +100,10 @@ func VerifySignature(imageRef string, key []byte, repository string, log logr.Lo
|
|||
return digest, nil
|
||||
}
|
||||
|
||||
func FetchAttestations(imageRef string, key []byte, repository string) (map[string]interface{}, error) {
|
||||
// FetchAttestations retrieves signed attestations and decodes them into in-toto statements
|
||||
// https://github.com/in-toto/attestation/blob/main/spec/README.md#statement
|
||||
func FetchAttestations(imageRef string, key []byte, repository string) ([]map[string]interface{}, error) {
|
||||
setClient()
|
||||
pubKey, err := decodePEM(key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode PEM %v", string(key))
|
||||
|
@ -119,53 +126,103 @@ func FetchAttestations(imageRef string, key []byte, repository string) (map[stri
|
|||
return nil, errors.Wrap(err, "failed to set signature repository")
|
||||
}
|
||||
|
||||
verified, err := cosign.Verify(context.Background(), ref, cosignOpts)
|
||||
verified, err := client.Verify(context.Background(), ref, cosignOpts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to verify image attestations")
|
||||
}
|
||||
|
||||
inTotoAttestations, err := decodeAttestations(verified)
|
||||
inTotoStatements, err := decodeStatements(verified)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return inTotoAttestations, nil
|
||||
return inTotoStatements, nil
|
||||
}
|
||||
|
||||
func decodeAttestations(attestations []cosign.SignedPayload) (map[string]interface{}, error) {
|
||||
if len(attestations) == 0 {
|
||||
return map[string]interface{}{}, nil
|
||||
func decodeStatements(sigs []cosign.SignedPayload) ([]map[string]interface{}, error) {
|
||||
if len(sigs) == 0 {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
decodedAttestations := make([]map[string]interface{}, len(attestations))
|
||||
decodedStatements := make([]map[string]interface{}, len(sigs))
|
||||
for i, sig := range sigs {
|
||||
data := make(map[string]interface{})
|
||||
if err := json.Unmarshal(sig.Payload, &data); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal JSON payload: %v", sig)
|
||||
}
|
||||
|
||||
for _, a := range attestations {
|
||||
payload := a.Payload
|
||||
data, err := base64.StdEncoding.DecodeString(string(payload))
|
||||
payloadBase64 := data["payload"].(string)
|
||||
decodedStatement, err := decodeStatement(payloadBase64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to base64 decode payload for attestation: %v", a)
|
||||
return nil, errors.Wrapf(err, "failed to decode statement %s", payloadBase64)
|
||||
}
|
||||
|
||||
inTotoAttestation := make(map[string]interface{})
|
||||
if err := json.Unmarshal(data, &inTotoAttestation); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal JSON payload for attestation: %v", a)
|
||||
}
|
||||
|
||||
attestationPayloadBase64 := inTotoAttestation["payload"].(string)
|
||||
statement, err := base64.StdEncoding.DecodeString(attestationPayloadBase64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to base64 decode payload for ")
|
||||
}
|
||||
|
||||
inTotoAttestation["payload"] = statement
|
||||
decodedAttestations = append(decodedAttestations, inTotoAttestation)
|
||||
decodedStatements[i] = decodedStatement
|
||||
}
|
||||
|
||||
results := map[string]interface{}{
|
||||
"attestations": decodedAttestations,
|
||||
return decodedStatements, nil
|
||||
}
|
||||
|
||||
func decodeStatement(payloadBase64 string) (map[string]interface{}, error) {
|
||||
statementRaw, err := base64.StdEncoding.DecodeString(payloadBase64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to base64 decode payload for %v", statementRaw)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
var statement in_toto.Statement
|
||||
if err := json.Unmarshal(statementRaw, &statement); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if statement.PredicateType != attestation.CosignCustomProvenanceV01 {
|
||||
// This assumes that the following statements are JSON objects:
|
||||
// - in_toto.PredicateSLSAProvenanceV01
|
||||
// - in_toto.PredicateLinkV1
|
||||
// - in_toto.PredicateSPDX
|
||||
// any other custom predicate
|
||||
return common.ToMap(statement)
|
||||
}
|
||||
|
||||
return decodeCosignCustomProvenanceV01(statement)
|
||||
}
|
||||
|
||||
func decodeCosignCustomProvenanceV01(statement in_toto.Statement) (map[string]interface{}, error) {
|
||||
if statement.PredicateType != attestation.CosignCustomProvenanceV01 {
|
||||
return nil, fmt.Errorf("invalid statement type %s", attestation.CosignCustomProvenanceV01)
|
||||
}
|
||||
|
||||
predicate, ok := statement.Predicate.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to decode CosignCustomProvenanceV01")
|
||||
}
|
||||
|
||||
cosignPredicateData := predicate["Data"]
|
||||
if cosignPredicateData == nil {
|
||||
return nil, fmt.Errorf("missing predicate in CosignCustomProvenanceV01")
|
||||
}
|
||||
|
||||
// attempt to parse as a JSON object type
|
||||
data, err := stringToJSONMap(cosignPredicateData)
|
||||
if err == nil {
|
||||
predicate["Data"] = data
|
||||
statement.Predicate = predicate
|
||||
}
|
||||
|
||||
return common.ToMap(statement)
|
||||
}
|
||||
|
||||
func stringToJSONMap(i interface{}) (map[string]interface{}, error) {
|
||||
s, ok := i.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected string type")
|
||||
}
|
||||
|
||||
var data = map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(s), &data); err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal JSON: %s", err.Error())
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func setSignatureRepo(cosignOpts *cosign.CheckOpts, ref name.Reference, repository string) error {
|
||||
|
|
42
pkg/cosign/mock.go
Normal file
42
pkg/cosign/mock.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package cosign
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
)
|
||||
|
||||
func SetMock(image string, data [][]byte) error {
|
||||
imgRef, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloads := make([]cosign.SignedPayload, len(data))
|
||||
for i, p := range data {
|
||||
payloads[i] = cosign.SignedPayload{
|
||||
Payload: p,
|
||||
}
|
||||
}
|
||||
|
||||
client = &mock{data: map[string][]cosign.SignedPayload {
|
||||
imgRef.String(): payloads,
|
||||
}}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type mock struct {
|
||||
data map[string] []cosign.SignedPayload
|
||||
}
|
||||
|
||||
func (m *mock) Verify(_ context.Context, signedImgRef name.Reference, _ *cosign.CheckOpts) ([]cosign.SignedPayload, error) {
|
||||
results, ok := m.data[signedImgRef.String()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to find mock data for %s", signedImgRef.String())
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map
|
|||
// if anchor is present in the resource then fail
|
||||
if _, ok := resourceMap[anchorKey]; ok {
|
||||
// no need to process elements in value as key cannot be present in resource
|
||||
return currentPath, fmt.Errorf("Validation rule failed at %s, field %s is disallowed", currentPath, anchorKey)
|
||||
return currentPath, fmt.Errorf("%s/%s is not allowed", currentPath, anchorKey)
|
||||
}
|
||||
// key is not defined in the resource
|
||||
return "", nil
|
||||
|
@ -118,7 +118,7 @@ func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[
|
|||
if dh.pattern == "*" && resourceMap[dh.element] != nil {
|
||||
return "", nil
|
||||
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
|
||||
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
|
||||
return dh.path, fmt.Errorf("%s/%s not found", dh.path, dh.element)
|
||||
} else {
|
||||
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
|
||||
if err != nil {
|
||||
|
@ -153,7 +153,7 @@ func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resource
|
|||
// validate the values of the pattern
|
||||
returnPath, err := handler(log.Log, value, ch.pattern, originPattern, currentPath, ac)
|
||||
if err != nil {
|
||||
ac.AnchorError = common.NewConditionalAnchorError(fmt.Sprintf("condition anchor did not satisfy: %s", err.Error()))
|
||||
ac.AnchorError = common.NewConditionalAnchorError(err.Error())
|
||||
return returnPath, ac.AnchorError.Error()
|
||||
}
|
||||
return "", nil
|
||||
|
@ -187,7 +187,7 @@ func (gh GlobalAnchorHandler) Handle(handler resourceElementHandler, resourceMap
|
|||
// validate the values of the pattern
|
||||
returnPath, err := handler(log.Log, value, gh.pattern, originPattern, currentPath, ac)
|
||||
if err != nil {
|
||||
ac.AnchorError = common.NewGlobalAnchorError(fmt.Sprintf("global anchor did not satisfy: %s", err.Error()))
|
||||
ac.AnchorError = common.NewGlobalAnchorError(err.Error())
|
||||
return returnPath, ac.AnchorError.Error()
|
||||
}
|
||||
return "", nil
|
||||
|
|
|
@ -74,10 +74,10 @@ type ValidateAnchorError struct {
|
|||
}
|
||||
|
||||
// ConditionalAnchorErrMsg - the error message for conditional anchor error
|
||||
var ConditionalAnchorErrMsg = "conditionalAnchorError"
|
||||
var ConditionalAnchorErrMsg = "conditional anchor mismatch"
|
||||
|
||||
// GlobalAnchorErrMsg - the error message for global anchor error
|
||||
var GlobalAnchorErrMsg = "globalAnchorError"
|
||||
var GlobalAnchorErrMsg = "global anchor mismatch"
|
||||
|
||||
// AnchorKey - contains map of anchors
|
||||
type AnchorKey struct {
|
||||
|
|
|
@ -20,6 +20,16 @@ func CopySlice(s []interface{}) []interface{} {
|
|||
return sliceCopy
|
||||
}
|
||||
|
||||
// CopySliceOfMaps creates a full copy of the target slice
|
||||
func CopySliceOfMaps(s []map[string]interface{}) []interface{} {
|
||||
sliceCopy := make([]interface{}, len(s))
|
||||
for i, v := range s {
|
||||
sliceCopy[i] = CopyMap(v)
|
||||
}
|
||||
|
||||
return sliceCopy
|
||||
}
|
||||
|
||||
func ToMap(data interface{}) (map[string]interface{}, error) {
|
||||
if m, ok := data.(map[string]interface{}); ok {
|
||||
return m, nil
|
||||
|
|
|
@ -3,9 +3,10 @@ package engine
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
|
@ -45,7 +46,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
|
|||
defer policyContext.JSONContext.Restore()
|
||||
|
||||
for i := range policyContext.Policy.Spec.Rules {
|
||||
rule := policyContext.Policy.Spec.Rules[i]
|
||||
rule := &policyContext.Policy.Spec.Rules[i]
|
||||
if len(rule.VerifyImages) == 0 {
|
||||
continue
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
|
|||
iv := &imageVerifier{
|
||||
logger: logger,
|
||||
policyContext: policyContext,
|
||||
rule: &rule,
|
||||
rule: rule,
|
||||
resp: resp,
|
||||
}
|
||||
|
||||
|
@ -167,42 +168,75 @@ func makeAddDigestPatch(imageInfo *context.ImageInfo, digest string) ([]byte, er
|
|||
return json.Marshal(patch)
|
||||
}
|
||||
|
||||
func (iv *imageVerifier) attestImage(repository, key string, imageInfo *context.ImageInfo, attestationChecks []*v1.AnyAllConditions) *response.RuleResponse {
|
||||
func (iv *imageVerifier) attestImage(repository, key string, imageInfo *context.ImageInfo, attestationChecks []*v1.Attestation) *response.RuleResponse {
|
||||
image := imageInfo.String()
|
||||
|
||||
start := time.Now()
|
||||
attestations, err := cosign.FetchAttestations(image, []byte(key), repository)
|
||||
statements, err := cosign.FetchAttestations(image, []byte(key), repository)
|
||||
if err != nil {
|
||||
iv.logger.Info("failed to fetch attestations", "image", image, "error", err, "duration", time.Since(start).Seconds())
|
||||
return ruleError(iv.rule, fmt.Sprintf("failed to fetch attestations for %s", image), err)
|
||||
}
|
||||
|
||||
iv.logger.Info("received attestation", "attestations", attestations)
|
||||
iv.logger.V(3).Info("received attested statements", "statements", statements)
|
||||
|
||||
iv.policyContext.JSONContext.Checkpoint()
|
||||
defer iv.policyContext.JSONContext.Restore()
|
||||
if err := iv.policyContext.JSONContext.AddJSONObject(attestations); err != nil {
|
||||
return ruleError(iv.rule, fmt.Sprintf("failed to add attestations to the context %v", attestations), err)
|
||||
}
|
||||
for _, ac := range attestationChecks {
|
||||
for _, s := range statements {
|
||||
predicateType := s["predicateType"]
|
||||
if ac.PredicateType == predicateType {
|
||||
val, err := iv.checkAttestations(ac, s, imageInfo)
|
||||
if err != nil {
|
||||
return ruleError(iv.rule, "error while checking attestation", err)
|
||||
}
|
||||
|
||||
passed, err := iv.checkConditions(attestationChecks)
|
||||
if err != nil {
|
||||
return ruleError(iv.rule, "failed to check attestation", err)
|
||||
}
|
||||
|
||||
if !passed {
|
||||
return ruleResponse(iv.rule, "attestation checks failed", response.RuleStatusFail)
|
||||
if !val {
|
||||
msg := fmt.Sprintf("attestation check failed for %s and predicate %s", imageInfo.String(), predicateType)
|
||||
return ruleResponse(iv.rule, msg, response.RuleStatusFail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ruleResponse(iv.rule, "attestation checks passed", response.RuleStatusPass)
|
||||
}
|
||||
|
||||
func (iv *imageVerifier) checkConditions(attestationChecks []*v1.AnyAllConditions) (bool, error) {
|
||||
conditions, err := variables.SubstituteAllInConditions(iv.logger, iv.policyContext.JSONContext, attestationChecks)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to substitute variables in conditions")
|
||||
func (iv *imageVerifier) checkAttestations(a *v1.Attestation, s map[string]interface{}, img *context.ImageInfo ) (bool, error) {
|
||||
if len(a.Conditions) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
pass := variables.EvaluateConditions(iv.logger, iv.policyContext.JSONContext, conditions)
|
||||
iv.policyContext.JSONContext.Checkpoint()
|
||||
defer iv.policyContext.JSONContext.Restore()
|
||||
|
||||
predicate, ok := s["predicate"].(map[string]interface{})
|
||||
if !ok {
|
||||
return false, fmt.Errorf("failed to extract predicate from statement: %v", s)
|
||||
}
|
||||
|
||||
if err := iv.policyContext.JSONContext.AddJSONObject(predicate); err != nil {
|
||||
return false, errors.Wrapf(err, fmt.Sprintf("failed to add Statement to the context %v", s))
|
||||
}
|
||||
|
||||
imgMap := map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"image": img.String(),
|
||||
"registry": img.Registry,
|
||||
"path": img.Path,
|
||||
"name": img.Name,
|
||||
"tag": img.Tag,
|
||||
"digest": img.Digest,
|
||||
},
|
||||
}
|
||||
|
||||
if err := iv.policyContext.JSONContext.AddJSONObject(imgMap); err != nil {
|
||||
return false, errors.Wrapf(err, fmt.Sprintf("failed to add image to the context %v", s))
|
||||
}
|
||||
|
||||
conditions, err := variables.SubstituteAllInConditions(iv.logger, iv.policyContext.JSONContext, a.Conditions)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to substitute variables in attestation conditions")
|
||||
}
|
||||
|
||||
pass := variables.EvaluateAnyAllConditions(iv.logger, iv.policyContext.JSONContext, conditions)
|
||||
return pass, nil
|
||||
}
|
||||
|
|
183
pkg/engine/imageVerify_test.go
Normal file
183
pkg/engine/imageVerify_test.go
Normal file
|
@ -0,0 +1,183 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/cosign"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
"github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"gotest.tools/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var test_policy_good = `{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "attest"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "attest",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"verifyImages": [
|
||||
{
|
||||
"image": "*",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----",
|
||||
"attestations": [
|
||||
{
|
||||
"predicateType": "https://example.com/CodeReview/v1",
|
||||
"conditions": [
|
||||
{
|
||||
"all": [
|
||||
{
|
||||
"key": "{{ repo.uri }}",
|
||||
"operator": "Equals",
|
||||
"value": "https://github.com/example/my-project"
|
||||
},
|
||||
{
|
||||
"key": "{{ repo.branch }}",
|
||||
"operator": "Equals",
|
||||
"value": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
var test_policy_bad = `{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "attest"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "attest",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"verifyImages": [
|
||||
{
|
||||
"image": "*",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----",
|
||||
"attestations": [
|
||||
{
|
||||
"predicateType": "https://example.com/CodeReview/v1",
|
||||
"conditions": [
|
||||
{
|
||||
"all": [
|
||||
{
|
||||
"key": "{{ repo.uri }}",
|
||||
"operator": "Equals",
|
||||
"value": "https://github.com/example/my-project"
|
||||
},
|
||||
{
|
||||
"key": "{{ repo.branch }}",
|
||||
"operator": "Equals",
|
||||
"value": "prod"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
|
||||
var test_resource = `{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "pause2",
|
||||
"image": "ghcr.io/jimbugwadia/pause2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
var payloads = [][]byte{
|
||||
[]byte(`{"payloadType":"https://example.com/CodeReview/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL0NvZGVSZXZpZXcvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9qaW1idWd3YWRpYS9wYXVzZTIiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjMxYmZiNGQwMjEzZjI1NGQzNjFlMDA3OWRlYWFlYmVmYTRmODJiYTdhYTc2ZWY4MmU5MGI0OTM1YWQ1YjEwNSJ9fV0sInByZWRpY2F0ZSI6eyJhdXRob3IiOiJtYWlsdG86YWxpY2VAZXhhbXBsZS5jb20iLCJyZXBvIjp7ImJyYW5jaCI6Im1haW4iLCJ0eXBlIjoiZ2l0IiwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2V4YW1wbGUvbXktcHJvamVjdCJ9LCJyZXZpZXdlcnMiOlsibWFpbHRvOmJvYkBleGFtcGxlLmNvbSJdfX0=","signatures":[{"keyid":"","sig":"MEYCIQCrEr+vgPDmNCrqGDE/4z9iMLmCXMXcDlGKtSoiuMTSFgIhAN2riBaGk4accWzVl7ypi1XTRxyrPYHst8DesugPXgOf"}]}`),
|
||||
[]byte(`{"payloadType":"cosign.sigstore.dev/attestation/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vamltYnVnd2FkaWEvcGF1c2UyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImIzMWJmYjRkMDIxM2YyNTRkMzYxZTAwNzlkZWFhZWJlZmE0ZjgyYmE3YWE3NmVmODJlOTBiNDkzNWFkNWIxMDUifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImhlbGxvIVxuIiwiVGltZXN0YW1wIjoiMjAyMS0xMC0wNVQwNToxODoxMVoifX0=","signatures":[{"keyid":"","sig":"MEQCIF5r9lf55rnYNPByZ9v6bortww694UEPvmyBIelIDYbIAiBNTGX4V64Oj6jZVRpkJQRxdzKUPYqC5GZTb4oS6eQ6aQ=="}]}`),
|
||||
[]byte(`{"payloadType":"https://example.com/CodeReview/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL0NvZGVSZXZpZXcvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9qaW1idWd3YWRpYS9wYXVzZTIiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjMxYmZiNGQwMjEzZjI1NGQzNjFlMDA3OWRlYWFlYmVmYTRmODJiYTdhYTc2ZWY4MmU5MGI0OTM1YWQ1YjEwNSJ9fV0sInByZWRpY2F0ZSI6eyJhdXRob3IiOiJtYWlsdG86YWxpY2VAZXhhbXBsZS5jb20iLCJyZXBvIjp7ImJyYW5jaCI6Im1haW4iLCJ0eXBlIjoiZ2l0IiwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2V4YW1wbGUvbXktcHJvamVjdCJ9LCJyZXZpZXdlcnMiOlsibWFpbHRvOmJvYkBleGFtcGxlLmNvbSJdfX0=","signatures":[{"keyid":"","sig":"MEUCIEeZbdBEFQzWqiMhB+SJgM6yFppUuQSKrpOIX1mxLDmRAiEA8pXqFq0GVc9LKhPzrnJRZhSruDNiKbiLHG5x7ETFyY8="}]}`),
|
||||
}
|
||||
|
||||
func Test_CosignAttest(t *testing.T) {
|
||||
policyContext := buildContext(t, test_policy_good, test_resource)
|
||||
|
||||
err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", payloads)
|
||||
assert.NilError(t, err)
|
||||
|
||||
er := VerifyAndPatchImages(policyContext)
|
||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
|
||||
}
|
||||
|
||||
func Test_CosignAttest_fail(t *testing.T) {
|
||||
policyContext := buildContext(t, test_policy_bad, test_resource)
|
||||
err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", payloads)
|
||||
assert.NilError(t, err)
|
||||
|
||||
er := VerifyAndPatchImages(policyContext)
|
||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||
}
|
||||
|
||||
func buildContext(t *testing.T, policy, resource string) *PolicyContext {
|
||||
policyRaw := []byte(policy)
|
||||
resourceRaw := []byte(resource)
|
||||
|
||||
var cpol kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(policyRaw, &cpol)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
ctx := context.NewContext()
|
||||
err = ctx.AddResource(resourceRaw)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
policyContext := &PolicyContext{
|
||||
Policy: cpol,
|
||||
JSONContext: ctx,
|
||||
NewResource: *resourceUnstructured}
|
||||
|
||||
if err := ctx.AddImageInfo(resourceUnstructured); err != nil {
|
||||
t.Errorf("unable to add image info to variables context: %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
return policyContext
|
||||
}
|
||||
|
|
@ -74,6 +74,9 @@ func (t *Traversal) traverseJSON(element interface{}, path string) (interface{},
|
|||
case []interface{}:
|
||||
return t.traverseList(common.CopySlice(typed), path)
|
||||
|
||||
case []map[string]interface{}:
|
||||
return t.traverseList(common.CopySliceOfMaps(typed), path)
|
||||
|
||||
case Key:
|
||||
return typed.Key, nil
|
||||
|
||||
|
@ -125,3 +128,4 @@ func (t *Traversal) traverseList(list []interface{}, path string) ([]interface{}
|
|||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -924,7 +924,7 @@ func Test_CheckConditionAnchor_DoesNotMatch(t *testing.T) {
|
|||
resource := yaml.MustParse(string(resourceRaw))
|
||||
|
||||
err := checkCondition(log.Log, pattern, resource)
|
||||
assert.Error(t, err, "Validation rule failed at '/key1/' to validate value 'sample' with pattern 'value*'")
|
||||
assert.Error(t, err, "resource value 'sample' does not match 'value*' at path /key1/")
|
||||
}
|
||||
|
||||
func Test_ValidateConditions_MapWithOneCondition_Matches(t *testing.T) {
|
||||
|
|
|
@ -20,6 +20,9 @@ type PolicyContext struct {
|
|||
// OldResource is the prior resource for an update, or nil
|
||||
OldResource unstructured.Unstructured
|
||||
|
||||
// Element is set when the context is used for processing a foreach loop
|
||||
Element unstructured.Unstructured
|
||||
|
||||
// AdmissionInfo contains the admission request information
|
||||
AdmissionInfo kyverno.RequestInfo
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ type RuleStats struct {
|
|||
//IsSuccessful checks if any rule has failed or not
|
||||
func (er EngineResponse) IsSuccessful() bool {
|
||||
for _, r := range er.PolicyResponse.Rules {
|
||||
if r.Status != RuleStatusPass {
|
||||
if r.Status == RuleStatusFail || r.Status == RuleStatusError {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -117,12 +117,12 @@ func (er EngineResponse) IsSuccessful() bool {
|
|||
//IsFailed checks if any rule has succeeded or not
|
||||
func (er EngineResponse) IsFailed() bool {
|
||||
for _, r := range er.PolicyResponse.Rules {
|
||||
if r.Status == RuleStatusPass {
|
||||
return false
|
||||
if r.Status == RuleStatusFail {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
//GetPatches returns all the patches joined
|
||||
|
|
|
@ -258,13 +258,13 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User
|
|||
//MatchesResourceDescription checks if the resource matches resource description of the rule or not
|
||||
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string) error {
|
||||
|
||||
rule := *ruleRef.DeepCopy()
|
||||
rule := ruleRef.DeepCopy()
|
||||
resource := *resourceRef.DeepCopy()
|
||||
admissionInfo := *admissionInfoRef.DeepCopy()
|
||||
|
||||
var reasonsForFailure []error
|
||||
if len(rule.MatchResources.Any) > 0 {
|
||||
// inlcude object if ANY of the criterias match
|
||||
// include object if ANY of the criteria match
|
||||
// so if one matches then break from loop
|
||||
oneMatched := false
|
||||
for _, rmr := range rule.MatchResources.Any {
|
||||
|
|
|
@ -36,7 +36,7 @@ func MatchPattern(logger logr.Logger, resource, pattern interface{}) error {
|
|||
// if conditional or global anchors report errors, the rule does not apply to the resource
|
||||
if common.IsConditionalAnchorError(err.Error()) || common.IsGlobalAnchorError(err.Error()) {
|
||||
logger.V(3).Info("skipping resource as anchor does not apply", "msg", ac.AnchorError.Error())
|
||||
return &PatternError{nil, "", true}
|
||||
return &PatternError{err, "", true}
|
||||
}
|
||||
|
||||
// check if an anchor defined in the policy rule is missing in the resource
|
||||
|
@ -48,7 +48,7 @@ func MatchPattern(logger logr.Logger, resource, pattern interface{}) error {
|
|||
return &PatternError{err, elemPath, false}
|
||||
}
|
||||
|
||||
return &PatternError{nil, "", false}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
|
||||
|
@ -82,19 +82,19 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
|
|||
case []interface{}:
|
||||
for _, res := range resource {
|
||||
if !ValidateValueWithPattern(log, res, patternElement) {
|
||||
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
|
||||
return path, fmt.Errorf("resource value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
if !ValidateValueWithPattern(log, resourceElement, patternElement) {
|
||||
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
|
||||
return path, fmt.Errorf("resource value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
log.V(4).Info("Pattern contains unknown type", "path", path, "current", fmt.Sprintf("%T", patternElement))
|
||||
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
|
||||
return path, fmt.Errorf("failed at '%s', pattern contains unknown type", path)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -1507,7 +1507,7 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
|
|||
name: "test-23",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "imagePullPolicy": "Always"}]}}`),
|
||||
nilErr: true,
|
||||
nilErr: false,
|
||||
},
|
||||
{
|
||||
name: "test-24",
|
||||
|
@ -1519,7 +1519,7 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
|
|||
name: "test-25",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo1", "value": "bar" }],"imagePullPolicy": "Always"}]}}`),
|
||||
nilErr: true,
|
||||
nilErr: false,
|
||||
},
|
||||
{
|
||||
name: "test-26",
|
||||
|
@ -1531,7 +1531,7 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
|
|||
name: "test-27",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo1", "value": "bar" }],"imagePullPolicy": "Always"}]}}`),
|
||||
nilErr: true,
|
||||
nilErr: false,
|
||||
},
|
||||
{
|
||||
name: "test-28",
|
||||
|
@ -1549,7 +1549,7 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
|
|||
name: "test-30",
|
||||
pattern: []byte(`{"metadata": {"<(name)": "nginx"},"spec": {"imagePullSecrets": [{"name": "regcred"}]}}`),
|
||||
resource: []byte(`{"metadata": {"name": "somename"},"spec": {"containers": [{"name": "nginx","image": "nginx:latest"}], "imagePullSecrets": [{"name": "cred"}]}}`),
|
||||
nilErr: true,
|
||||
nilErr: false,
|
||||
},
|
||||
{
|
||||
name: "test-31",
|
||||
|
@ -1579,7 +1579,7 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
|
|||
name: "test-35",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx"}],"imagePullSecrets": [{"name": "my-registry-secret"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "somepod"}], "imagePullSecrets": [{"name": "cred"}]}}`),
|
||||
nilErr: true,
|
||||
nilErr: false,
|
||||
},
|
||||
{
|
||||
name: "test-36",
|
||||
|
@ -1605,7 +1605,7 @@ func Test_global_anchor(t *testing.T) {
|
|||
name: "check global anchor_skip",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:v1", "imagePullPolicy": "Always"}]}}`),
|
||||
nilErr: true,
|
||||
nilErr: false,
|
||||
},
|
||||
{
|
||||
name: "check global anchor_apply",
|
||||
|
@ -1631,7 +1631,7 @@ func testMatchPattern(t *testing.T, testCase struct {
|
|||
err = json.Unmarshal(testCase.resource, &resource)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err, _ = MatchPattern(log.Log, resource, pattern)
|
||||
err = MatchPattern(log.Log, resource, pattern)
|
||||
if testCase.nilErr {
|
||||
assert.NilError(t, err, fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
|
||||
} else {
|
||||
|
|
|
@ -3,13 +3,14 @@ package engine
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
gojmespath "github.com/jmespath/go-jmespath"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
|
@ -92,7 +93,8 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
|
|||
ctx.JSONContext.Checkpoint()
|
||||
defer ctx.JSONContext.Restore()
|
||||
|
||||
for _, rule := range ctx.Policy.Spec.Rules {
|
||||
for i := range ctx.Policy.Spec.Rules {
|
||||
rule := &ctx.Policy.Spec.Rules[i]
|
||||
if !rule.HasValidate() {
|
||||
continue
|
||||
}
|
||||
|
@ -106,7 +108,7 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
|
|||
ctx.JSONContext.Reset()
|
||||
startTime := time.Now()
|
||||
|
||||
ruleResp := processValidationRule(log, ctx, &rule)
|
||||
ruleResp := processValidationRule(log, ctx, rule)
|
||||
if ruleResp != nil {
|
||||
addRuleResponse(log, resp, ruleResp, startTime)
|
||||
}
|
||||
|
@ -278,13 +280,17 @@ func addElementToContext(ctx *PolicyContext, e interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
jsonData := map[string]interface{}{
|
||||
"element": data,
|
||||
}
|
||||
|
||||
if err := ctx.JSONContext.AddJSONObject(jsonData); err != nil {
|
||||
return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
|
||||
}
|
||||
|
||||
u := unstructured.Unstructured{}
|
||||
u.SetUnstructuredContent(data)
|
||||
ctx.NewResource = u
|
||||
|
||||
if err := ctx.JSONContext.AddResourceAsObject(e); err != nil {
|
||||
return errors.Wrapf(err, "failed to add resource (%v) to JSON context", e)
|
||||
}
|
||||
ctx.Element = u
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -375,12 +381,17 @@ func (v *validator) getDenyMessage(deny bool) string {
|
|||
}
|
||||
|
||||
func (v *validator) validateResourceWithRule() *response.RuleResponse {
|
||||
if reflect.DeepEqual(v.ctx.OldResource, unstructured.Unstructured{}) {
|
||||
if !isEmptyUnstructured(&v.ctx.Element) {
|
||||
resp := v.validatePatterns(v.ctx.Element)
|
||||
return resp
|
||||
}
|
||||
|
||||
if !isEmptyUnstructured(&v.ctx.OldResource) {
|
||||
resp := v.validatePatterns(v.ctx.NewResource)
|
||||
return resp
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(v.ctx.NewResource, unstructured.Unstructured{}) {
|
||||
if isEmptyUnstructured(&v.ctx.NewResource) {
|
||||
v.log.V(3).Info("skipping validation on deleted resource")
|
||||
return nil
|
||||
}
|
||||
|
@ -395,15 +406,27 @@ func (v *validator) validateResourceWithRule() *response.RuleResponse {
|
|||
return newResp
|
||||
}
|
||||
|
||||
func isEmptyUnstructured(u *unstructured.Unstructured) bool {
|
||||
if u == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(*u, unstructured.Unstructured{}) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
|
||||
func matches(logger logr.Logger, rule kyverno.Rule, ctx *PolicyContext) bool {
|
||||
err := MatchesResourceDescription(ctx.NewResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
|
||||
func matches(logger logr.Logger, rule *kyverno.Rule, ctx *PolicyContext) bool {
|
||||
err := MatchesResourceDescription(ctx.NewResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
|
||||
err := MatchesResourceDescription(ctx.OldResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
|
||||
err := MatchesResourceDescription(ctx.OldResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
@ -437,14 +460,20 @@ func isSameRuleResponse(r1 *response.RuleResponse, r2 *response.RuleResponse) bo
|
|||
func (v *validator) validatePatterns(resource unstructured.Unstructured) *response.RuleResponse {
|
||||
if v.pattern != nil {
|
||||
if err := validate.MatchPattern(v.log, resource.Object, v.pattern); err != nil {
|
||||
|
||||
if pe, ok := err.(*validate.PatternError); ok {
|
||||
v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error())
|
||||
|
||||
if pe.Skip {
|
||||
return ruleResponse(v.rule, pe.Error(), response.RuleStatusSkip)
|
||||
}
|
||||
|
||||
if pe.Path == "" {
|
||||
return ruleResponse(v.rule, v.buildErrorMessage(err, ""), response.RuleStatusError)
|
||||
}
|
||||
|
||||
return ruleResponse(v.rule, v.buildErrorMessage(err, pe.Path), response.RuleStatusFail)
|
||||
} else {
|
||||
return ruleResponse(v.rule, v.buildErrorMessage(err, pe.Path), response.RuleStatusError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,9 +554,9 @@ func (v *validator) buildErrorMessage(err error, path string) string {
|
|||
return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error())
|
||||
}
|
||||
|
||||
msgRaw, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.rule.Validation.Message)
|
||||
if err != nil {
|
||||
v.log.Info("failed to substitute variables in message: %v", err)
|
||||
msgRaw, sErr := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.rule.Validation.Message)
|
||||
if sErr != nil {
|
||||
v.log.Info("failed to substitute variables in message: %v", sErr)
|
||||
}
|
||||
|
||||
msg := msgRaw.(string)
|
||||
|
|
|
@ -2514,7 +2514,7 @@ func Test_foreach_container_deny_fail(t *testing.T) {
|
|||
"list": "request.object.spec.template.spec.containers",
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ regex_match('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
{"key": "{{ regex_match('{{element.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2550,7 +2550,7 @@ func Test_foreach_container_deny_success(t *testing.T) {
|
|||
"list": "request.object.spec.template.spec.containers",
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ regex_match('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
{"key": "{{ regex_match('{{element.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2623,14 +2623,14 @@ func Test_foreach_context_preconditions(t *testing.T) {
|
|||
"context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}],
|
||||
"preconditions": { "all": [
|
||||
{
|
||||
"key": "{{request.object.name}}",
|
||||
"key": "{{element.name}}",
|
||||
"operator": "In",
|
||||
"value": ["podvalid"]
|
||||
}
|
||||
]},
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ request.object.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ request.object.name }} }}"}
|
||||
{"key": "{{ element.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ element.name }} }}"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2687,14 +2687,14 @@ func Test_foreach_context_preconditions_fail(t *testing.T) {
|
|||
"context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}],
|
||||
"preconditions": { "all": [
|
||||
{
|
||||
"key": "{{request.object.name}}",
|
||||
"key": "{{element.name}}",
|
||||
"operator": "In",
|
||||
"value": ["podvalid", "podinvalid"]
|
||||
}
|
||||
]},
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ request.object.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ request.object.name }} }}"}
|
||||
{"key": "{{ element.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ element.name }} }}"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func Evaluate(log logr.Logger, ctx context.EvalInterface, condition kyverno.Cond
|
|||
return handle.Evaluate(condition.Key, condition.Value)
|
||||
}
|
||||
|
||||
//EvaluateConditions evalues all the conditions present in a slice, in a backwards compatible way
|
||||
//EvaluateConditions evaluates all the conditions present in a slice, in a backwards compatible way
|
||||
func EvaluateConditions(log logr.Logger, ctx context.EvalInterface, conditions interface{}) bool {
|
||||
switch typedConditions := conditions.(type) {
|
||||
case kyverno.AnyAllConditions:
|
||||
|
@ -28,6 +28,16 @@ func EvaluateConditions(log logr.Logger, ctx context.EvalInterface, conditions i
|
|||
return false
|
||||
}
|
||||
|
||||
func EvaluateAnyAllConditions(log logr.Logger, ctx context.EvalInterface, conditions []*kyverno.AnyAllConditions) bool {
|
||||
for _, c := range conditions {
|
||||
if !evaluateAnyAllConditions(log, ctx, *c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//evaluateAnyAllConditions evaluates multiple conditions as a logical AND (all) or OR (any) operation depending on the conditions
|
||||
func evaluateAnyAllConditions(log logr.Logger, ctx context.EvalInterface, conditions kyverno.AnyAllConditions) bool {
|
||||
anyConditions, allConditions := conditions.AnyConditions, conditions.AllConditions
|
||||
|
|
|
@ -312,6 +312,8 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
|
|||
return data.Element, nil
|
||||
}
|
||||
|
||||
isDeleteRequest := isDeleteRequest(ctx)
|
||||
|
||||
vars := RegexVariables.FindAllString(value, -1)
|
||||
for len(vars) > 0 {
|
||||
originalPattern := value
|
||||
|
@ -323,8 +325,7 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
|
|||
variable = strings.Replace(variable, "@", fmt.Sprintf("request.object.%s", getJMESPath(data.Path)), -1)
|
||||
}
|
||||
|
||||
operation, err := ctx.Query("request.operation")
|
||||
if err == nil && operation == "DELETE" {
|
||||
if isDeleteRequest {
|
||||
variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
|
||||
}
|
||||
|
||||
|
@ -360,6 +361,19 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
|
|||
})
|
||||
}
|
||||
|
||||
func isDeleteRequest(ctx context.EvalInterface) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
operation, err := ctx.Query("request.operation")
|
||||
if err == nil && operation == "DELETE" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getJMESPath converts path to JMES format
|
||||
func getJMESPath(rawPath string) string {
|
||||
tokens := strings.Split(rawPath, "/")[3:] // skip empty element and two non-resource (like mutate.overlay)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
kyvernoutils "github.com/kyverno/kyverno/pkg/utils"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
@ -100,6 +101,22 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
|
|||
return nil, err
|
||||
}
|
||||
|
||||
requestString := gr.Spec.Context.AdmissionRequestInfo.AdmissionRequest
|
||||
var request v1beta1.AdmissionRequest
|
||||
err = json.Unmarshal([]byte(requestString), &request)
|
||||
if err != nil {
|
||||
logger.Error(err, "error parsing the request string")
|
||||
}
|
||||
|
||||
if gr.Spec.Context.AdmissionRequestInfo.Operation == v1beta1.Update {
|
||||
request.Operation = gr.Spec.Context.AdmissionRequestInfo.Operation
|
||||
}
|
||||
|
||||
if err := ctx.AddRequest(&request); err != nil {
|
||||
logger.Error(err, "failed to load request in context")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceRaw, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal resource")
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"reflect"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -230,6 +231,7 @@ func (c *Controller) updateGenericResource(old, cur interface{}) {
|
|||
|
||||
// re-evaluate the GR as the resource was updated
|
||||
for _, gr := range grs {
|
||||
gr.Spec.Context.AdmissionRequestInfo.Operation = v1beta1.Update
|
||||
c.enqueueGenerateRequest(gr)
|
||||
}
|
||||
}
|
||||
|
@ -286,6 +288,7 @@ func (c *Controller) updatePolicy(old, cur interface{}) {
|
|||
|
||||
// re-evaluate the GR as the policy was updated
|
||||
for _, gr := range grs {
|
||||
gr.Spec.Context.AdmissionRequestInfo.Operation = v1beta1.Update
|
||||
c.enqueueGenerateRequest(gr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,12 +55,12 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
|
|||
// elementary values
|
||||
case string, float64, int, int64, bool, nil:
|
||||
if !validate.ValidateValueWithPattern(log, resourceElement, patternElement) {
|
||||
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
|
||||
return path, fmt.Errorf("value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
|
||||
}
|
||||
|
||||
default:
|
||||
log.V(4).Info("Pattern contains unknown type", "path", path, "current", fmt.Sprintf("%T", patternElement))
|
||||
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
|
||||
return path, fmt.Errorf("failed at path '%s', pattern contains unknown type", path)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ func (dh Handler) Handle(handler resourceElementHandler, resourceMap map[string]
|
|||
if dh.pattern == "*" && resourceMap[dh.element] != nil {
|
||||
return "", nil
|
||||
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
|
||||
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
|
||||
return dh.path, fmt.Errorf("failed at path %s, field %s is not present", dh.path, dh.element)
|
||||
} else {
|
||||
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -108,7 +108,7 @@ func buildPolicyResults(infos []policyreport.Info) map[string][]*report.PolicyRe
|
|||
|
||||
result.Rule = rule.Name
|
||||
result.Message = rule.Message
|
||||
result.Result = report.PolicyResult(rule.Check)
|
||||
result.Result = report.PolicyResult(rule.Status)
|
||||
result.Source = policyreport.SourceValue
|
||||
result.Timestamp = now
|
||||
results[appname] = append(results[appname], &result)
|
||||
|
|
|
@ -768,20 +768,36 @@ func ProcessValidateEngineResponse(policy *v1.ClusterPolicy, validateResponse *r
|
|||
Message: valResponseRule.Message,
|
||||
}
|
||||
|
||||
if valResponseRule.Status == response.RuleStatusPass {
|
||||
switch valResponseRule.Status {
|
||||
case response.RuleStatusPass:
|
||||
rc.Pass++
|
||||
vrule.Check = report.StatusPass
|
||||
} else {
|
||||
vrule.Status = report.StatusPass
|
||||
|
||||
case response.RuleStatusFail:
|
||||
rc.Fail++
|
||||
vrule.Status = report.StatusFail
|
||||
if !policyReport {
|
||||
if printCount < 1 {
|
||||
fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
|
||||
printCount++
|
||||
}
|
||||
|
||||
fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name, valResponseRule.Message)
|
||||
}
|
||||
rc.Fail++
|
||||
vrule.Check = report.StatusFail
|
||||
|
||||
case response.RuleStatusError:
|
||||
rc.Error++
|
||||
vrule.Status = report.StatusError
|
||||
|
||||
case response.RuleStatusWarn:
|
||||
rc.Warn++
|
||||
vrule.Status = report.StatusWarn
|
||||
|
||||
case response.RuleStatusSkip:
|
||||
rc.Skip++
|
||||
vrule.Status = report.StatusSkip
|
||||
}
|
||||
|
||||
violatedRules = append(violatedRules, vrule)
|
||||
continue
|
||||
}
|
||||
|
@ -793,7 +809,7 @@ func ProcessValidateEngineResponse(policy *v1.ClusterPolicy, validateResponse *r
|
|||
Name: policyRule.Name,
|
||||
Type: "Validation",
|
||||
Message: policyRule.Validation.Message,
|
||||
Check: report.StatusSkip,
|
||||
Status: report.StatusSkip,
|
||||
}
|
||||
violatedRules = append(violatedRules, vruleSkip)
|
||||
}
|
||||
|
|
|
@ -50,11 +50,13 @@ func Command() *cobra.Command {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = testCommandExecute(dirPath, valuesFile, fileName)
|
||||
if err != nil {
|
||||
log.Log.V(3).Info("a directory is required")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -192,14 +194,16 @@ func testCommandExecute(dirPath []string, valuesFile string, fileName string) (r
|
|||
}
|
||||
|
||||
if len(errors) > 0 && log.Log.V(1).Enabled() {
|
||||
fmt.Printf("ignoring errors: \n")
|
||||
fmt.Printf("test errors: \n")
|
||||
for _, e := range errors {
|
||||
fmt.Printf(" %v \n", e.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if rc.Fail > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
return rc, nil
|
||||
}
|
||||
|
@ -255,6 +259,7 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
|
|||
Name: resourceName,
|
||||
},
|
||||
},
|
||||
Message: buildMessage(resp),
|
||||
}
|
||||
|
||||
for i, test := range testResults {
|
||||
|
@ -297,7 +302,7 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
|
|||
}
|
||||
|
||||
result.Rule = rule.Name
|
||||
result.Result = report.PolicyResult(rule.Check)
|
||||
result.Result = report.PolicyResult(rule.Status)
|
||||
result.Source = policyreport.SourceValue
|
||||
result.Timestamp = now
|
||||
results[resultsKey] = result
|
||||
|
@ -308,6 +313,16 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
|
|||
return results, testResults
|
||||
}
|
||||
|
||||
func buildMessage(resp *response.EngineResponse) string {
|
||||
var bldr strings.Builder
|
||||
for _, ruleResp := range resp.PolicyResponse.Rules {
|
||||
fmt.Fprintf(&bldr, " %s: %s \n", ruleResp.Name, ruleResp.Status.String())
|
||||
fmt.Fprintf(&bldr, " %s \n", ruleResp.Message)
|
||||
}
|
||||
|
||||
return bldr.String()
|
||||
}
|
||||
|
||||
func getPolicyResourceFullPath(path []string, policyResourcePath string, isGit bool) []string {
|
||||
var pol []string
|
||||
if !isGit {
|
||||
|
@ -419,12 +434,13 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
|||
pvInfos = append(pvInfos, info)
|
||||
}
|
||||
}
|
||||
resultsMap, testResults := buildPolicyResults(validateEngineResponses, values.Results, pvInfos)
|
||||
|
||||
resultsMap, testResults := buildPolicyResults(validateEngineResponses, values.Results, pvInfos)
|
||||
resultErr := printTestResult(resultsMap, testResults, rc)
|
||||
if resultErr != nil {
|
||||
return sanitizederror.NewWithError("Unable to genrate result. Error:", resultErr)
|
||||
return sanitizederror.NewWithError("failed to print test result:", resultErr)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -464,17 +480,20 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
|
|||
v.Result = v.Status
|
||||
}
|
||||
if testRes.Result == v.Result {
|
||||
res.Result = boldGreen.Sprintf("Pass")
|
||||
if testRes.Result == report.StatusSkip {
|
||||
res.Result = boldGreen.Sprintf("Pass")
|
||||
rc.Skip++
|
||||
} else {
|
||||
res.Result = boldGreen.Sprintf("Pass")
|
||||
rc.Pass++
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("test failed for policy=%s, rule=%s, resource=%s, expected=%s, received=%s \n",
|
||||
v.Policy, v.Rule, v.Resource, v.Result, testRes.Result)
|
||||
fmt.Printf("%s \n", testRes.Message)
|
||||
res.Result = boldRed.Sprintf("Fail")
|
||||
rc.Fail++
|
||||
}
|
||||
|
||||
table = append(table, res)
|
||||
}
|
||||
printer.BorderTop, printer.BorderBottom, printer.BorderLeft, printer.BorderRight = true, true, true, true
|
||||
|
|
|
@ -21,7 +21,11 @@ type Validation interface {
|
|||
// - Mutate
|
||||
// - Validation
|
||||
// - Generate
|
||||
func validateActions(idx int, rule kyverno.Rule, client *dclient.Client, mock bool) error {
|
||||
func validateActions(idx int, rule *kyverno.Rule, client *dclient.Client, mock bool) error {
|
||||
if rule == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var checker Validation
|
||||
|
||||
// Mutate
|
||||
|
@ -34,7 +38,7 @@ func validateActions(idx int, rule kyverno.Rule, client *dclient.Client, mock bo
|
|||
|
||||
// Validate
|
||||
if rule.HasValidate() {
|
||||
checker = validate.NewValidateFactory(rule.Validation)
|
||||
checker = validate.NewValidateFactory(&rule.Validation)
|
||||
if path, err := checker.Validate(); err != nil {
|
||||
return fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", idx, path, err)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func ValidatePattern(patternElement interface{}, path string, supportedAnchors [
|
|||
//TODO? check operator
|
||||
return "", nil
|
||||
default:
|
||||
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
|
||||
return path, fmt.Errorf("error at '%s', pattern contains unknown type", path)
|
||||
}
|
||||
}
|
||||
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
|
||||
|
|
|
@ -148,7 +148,7 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
|
|||
// - Mutate
|
||||
// - Validate
|
||||
// - Generate
|
||||
if err := validateActions(i, rule, client, mock); err != nil {
|
||||
if err := validateActions(i, &p.Spec.Rules[i], client, mock); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -2,42 +2,43 @@ package validate
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor/common"
|
||||
"github.com/kyverno/kyverno/pkg/policy/common"
|
||||
)
|
||||
|
||||
// Validate provides implementation to validate 'validate' rule
|
||||
// Validate validates a 'validate' rule
|
||||
type Validate struct {
|
||||
// rule to hold 'validate' rule specifications
|
||||
rule kyverno.Validation
|
||||
rule *kyverno.Validation
|
||||
}
|
||||
|
||||
//NewValidateFactory returns a new instance of Mutate validation checker
|
||||
func NewValidateFactory(rule kyverno.Validation) *Validate {
|
||||
func NewValidateFactory(rule *kyverno.Validation) *Validate {
|
||||
m := Validate{
|
||||
rule: rule,
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
//Validate validates the 'validate' rule
|
||||
func (v *Validate) Validate() (string, error) {
|
||||
rule := v.rule
|
||||
if err := v.validateOverlayPattern(); err != nil {
|
||||
if err := v.validateElements(); err != nil {
|
||||
// no need to proceed ahead
|
||||
return "", err
|
||||
}
|
||||
|
||||
if rule.Pattern != nil {
|
||||
if path, err := common.ValidatePattern(rule.Pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil {
|
||||
if v.rule.Pattern != nil {
|
||||
if path, err := common.ValidatePattern(v.rule.Pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil {
|
||||
return fmt.Sprintf("pattern.%s", path), err
|
||||
}
|
||||
}
|
||||
|
||||
if rule.AnyPattern != nil {
|
||||
anyPattern, err := rule.DeserializeAnyPattern()
|
||||
if v.rule.AnyPattern != nil {
|
||||
anyPattern, err := v.rule.DeserializeAnyPattern()
|
||||
if err != nil {
|
||||
return "anyPattern", fmt.Errorf("failed to deserialize anyPattern, expect array: %v", err)
|
||||
}
|
||||
|
@ -47,19 +48,92 @@ func (v *Validate) Validate() (string, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.rule.ForEachValidation != nil {
|
||||
if err := v.validateForEach(v.rule.ForEachValidation); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// validateOverlayPattern checks one of pattern/anyPattern must exist
|
||||
func (v *Validate) validateOverlayPattern() error {
|
||||
rule := v.rule
|
||||
if rule.Pattern == nil && rule.AnyPattern == nil && rule.Deny == nil {
|
||||
return fmt.Errorf("pattern, anyPattern or deny must be specified")
|
||||
func (v *Validate) validateElements() error {
|
||||
count := validationElemCount(v.rule)
|
||||
if count == 0 {
|
||||
return fmt.Errorf("one of pattern, anyPattern, deny, foreach must be specified")
|
||||
}
|
||||
|
||||
if rule.Pattern != nil && rule.AnyPattern != nil {
|
||||
return fmt.Errorf("only one operation allowed per validation rule(pattern or anyPattern)")
|
||||
if count > 1 {
|
||||
return fmt.Errorf("only one of pattern, anyPattern, deny, foreach can be specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validationElemCount(v *kyverno.Validation) int {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
if v.Pattern != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
if v.AnyPattern != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
if v.Deny != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
if v.ForEachValidation != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (v *Validate) validateForEach(foreach *kyverno.ForEachValidation) error {
|
||||
if foreach.List == "" {
|
||||
return fmt.Errorf("foreach.list is required")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(foreach.List, "request.object") {
|
||||
return fmt.Errorf("foreach.list must start with 'request.object' e.g. 'request.object.spec.containers'.")
|
||||
}
|
||||
|
||||
count := foreachElemCount(foreach)
|
||||
if count == 0 {
|
||||
return fmt.Errorf("one of pattern, anyPattern, deny must be specified")
|
||||
}
|
||||
|
||||
if count > 1 {
|
||||
return fmt.Errorf("only one of pattern, anyPattern, deny can be specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func foreachElemCount(foreach *kyverno.ForEachValidation) int {
|
||||
if foreach == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
if foreach.Pattern != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
if foreach.AnyPattern != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
if foreach.Deny != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func Test_Validate_OverlayPattern_Empty(t *testing.T) {
|
|||
err := json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func Test_Validate_OverlayPattern_Nil_PatternAnypattern(t *testing.T) {
|
|||
var validation kyverno.Validation
|
||||
err := json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func Test_Validate_OverlayPattern_Exist_PatternAnypattern(t *testing.T) {
|
|||
var validation kyverno.Validation
|
||||
err := json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ func Test_Validate_OverlayPattern_Valid(t *testing.T) {
|
|||
var validation kyverno.Validation
|
||||
err := json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func Test_Validate_ExistingAnchor_AnchorOnMap(t *testing.T) {
|
|||
var validation kyverno.Validation
|
||||
err := json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ func Test_Validate_ExistingAnchor_AnchorOnString(t *testing.T) {
|
|||
var validation kyverno.Validation
|
||||
err := json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
|
|||
|
||||
err = json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validation)
|
||||
checker := NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
|
|||
} `)
|
||||
err = json.Unmarshal(rawValidation, &validation)
|
||||
assert.NilError(t, err)
|
||||
checker = NewValidateFactory(validation)
|
||||
checker = NewValidateFactory(&validation)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -268,7 +268,7 @@ func Test_Validate_Validate_ValidAnchor(t *testing.T) {
|
|||
err = json.Unmarshal(rawValidate, &validate)
|
||||
assert.NilError(t, err)
|
||||
|
||||
checker := NewValidateFactory(validate)
|
||||
checker := NewValidateFactory(&validate)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ func Test_Validate_Validate_ValidAnchor(t *testing.T) {
|
|||
err = json.Unmarshal(rawValidate, &validate)
|
||||
assert.NilError(t, err)
|
||||
|
||||
checker = NewValidateFactory(validate)
|
||||
checker = NewValidateFactory(&validate)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ func Test_Validate_Validate_Mismatched(t *testing.T) {
|
|||
var validate kyverno.Validation
|
||||
err := json.Unmarshal(rawValidate, &validate)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validate)
|
||||
checker := NewValidateFactory(&validate)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -347,7 +347,7 @@ func Test_Validate_Validate_Unsupported(t *testing.T) {
|
|||
|
||||
err = json.Unmarshal(rawValidate, &validate)
|
||||
assert.NilError(t, err)
|
||||
checker := NewValidateFactory(validate)
|
||||
checker := NewValidateFactory(&validate)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ func Test_Validate_Validate_Unsupported(t *testing.T) {
|
|||
err = json.Unmarshal(rawValidate, &validate)
|
||||
assert.NilError(t, err)
|
||||
|
||||
checker = NewValidateFactory(validate)
|
||||
checker = NewValidateFactory(&validate)
|
||||
if _, err := checker.Validate(); err != nil {
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ func (builder *requestBuilder) buildRCRResult(policy string, resource response.R
|
|||
|
||||
result.Rule = rule.Name
|
||||
result.Message = rule.Message
|
||||
result.Result = report.PolicyResult(rule.Check)
|
||||
result.Result = report.PolicyResult(rule.Status)
|
||||
if result.Result == "fail" && !av.scored {
|
||||
result.Result = "warn"
|
||||
}
|
||||
|
@ -263,15 +263,31 @@ func buildViolatedRules(er *response.EngineResponse) []kyverno.ViolatedRule {
|
|||
Type: rule.Type,
|
||||
Message: rule.Message,
|
||||
}
|
||||
vrule.Check = report.StatusFail
|
||||
if rule.Status == response.RuleStatusPass {
|
||||
vrule.Check = report.StatusPass
|
||||
}
|
||||
|
||||
vrule.Status = toPolicyResult(rule.Status)
|
||||
violatedRules = append(violatedRules, vrule)
|
||||
}
|
||||
|
||||
return violatedRules
|
||||
}
|
||||
|
||||
func toPolicyResult(status response.RuleStatus) string {
|
||||
switch status {
|
||||
case response.RuleStatusPass:
|
||||
return report.StatusPass
|
||||
case response.RuleStatusFail:
|
||||
return report.StatusFail
|
||||
case response.RuleStatusError:
|
||||
return report.StatusError
|
||||
case response.RuleStatusWarn:
|
||||
return report.StatusWarn
|
||||
case response.RuleStatusSkip:
|
||||
return report.StatusSkip
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
const categoryLabel string = "policies.kyverno.io/category"
|
||||
const severityLabel string = "policies.kyverno.io/severity"
|
||||
const scoredLabel string = "policies.kyverno.io/scored"
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gardener/controller-manager-library/pkg/logger"
|
||||
"github.com/go-logr/logr"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
|
@ -107,7 +108,7 @@ func (ws *WebhookServer) handleGenerate(
|
|||
}
|
||||
|
||||
// Adds Generate Request to a channel(queue size 1000) to generators
|
||||
if failedResponse := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil {
|
||||
if failedResponse := applyGenerateRequest(request, ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil {
|
||||
// report failure event
|
||||
for _, failedGR := range failedResponse {
|
||||
events := failedEvents(fmt.Errorf("failed to create Generate Request: %v", failedGR.err), failedGR.gr, new)
|
||||
|
@ -418,11 +419,20 @@ func (ws *WebhookServer) deleteGR(logger logr.Logger, engineResponse *response.E
|
|||
}
|
||||
}
|
||||
|
||||
func applyGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo,
|
||||
func applyGenerateRequest(request *v1beta1.AdmissionRequest, gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo,
|
||||
action v1beta1.Operation, engineResponses ...*response.EngineResponse) (failedGenerateRequest []generateRequestResponse) {
|
||||
|
||||
requestBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
logger.Error(err, "error loading request into context")
|
||||
}
|
||||
admissionRequestInfo := kyverno.AdmissionRequestInfoObject{
|
||||
AdmissionRequest: string(requestBytes),
|
||||
Operation: action,
|
||||
}
|
||||
|
||||
for _, er := range engineResponses {
|
||||
gr := transform(userRequestInfo, er)
|
||||
gr := transform(admissionRequestInfo, userRequestInfo, er)
|
||||
if err := gnGenerator.Apply(gr, action); err != nil {
|
||||
failedGenerateRequest = append(failedGenerateRequest, generateRequestResponse{gr: gr, err: err})
|
||||
}
|
||||
|
@ -431,7 +441,7 @@ func applyGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo
|
|||
return
|
||||
}
|
||||
|
||||
func transform(userRequestInfo kyverno.RequestInfo, er *response.EngineResponse) kyverno.GenerateRequestSpec {
|
||||
func transform(admissionRequestInfo kyverno.AdmissionRequestInfoObject, userRequestInfo kyverno.RequestInfo, er *response.EngineResponse) kyverno.GenerateRequestSpec {
|
||||
gr := kyverno.GenerateRequestSpec{
|
||||
Policy: er.PolicyResponse.Policy.Name,
|
||||
Resource: kyverno.ResourceSpec{
|
||||
|
@ -441,7 +451,8 @@ func transform(userRequestInfo kyverno.RequestInfo, er *response.EngineResponse)
|
|||
APIVersion: er.PolicyResponse.Resource.APIVersion,
|
||||
},
|
||||
Context: kyverno.GenerateRequestContext{
|
||||
UserRequestInfo: userRequestInfo,
|
||||
UserRequestInfo: userRequestInfo,
|
||||
AdmissionRequestInfo: admissionRequestInfo,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ results:
|
|||
# TEST: Deployment with Labels Should Fail
|
||||
- policy: require-common-labels
|
||||
rule: check-for-labels
|
||||
result: fail
|
||||
result: skip
|
||||
resource: deployment-missing-labels
|
||||
|
||||
# TEST: StatefulSet with Labels Should Pass
|
||||
|
|
|
@ -216,7 +216,7 @@ func Test_Mutate(t *testing.T) {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Validating created resource with the expected pattern...")
|
||||
err, _ = validate.MatchPattern(log.Log, actual, expected)
|
||||
err = validate.MatchPattern(log.Log, actual, expected)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Deleting Cluster Policies...")
|
||||
|
|
Loading…
Add table
Reference in a new issue