package vpol
import (
"github.com/kyverno/api/api/policies.kyverno.io/v1beta1"
"github.com/kyverno/kyverno/pkg/cel/compiler"
vpolcompiler "github.com/kyverno/kyverno/pkg/cel/policies/vpol/compiler"
"github.com/kyverno/kyverno/pkg/toggle"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func Validate(vpol v1beta1.ValidatingPolicyLike) ([]string, error) {
warnings := make([]string, 0)
err := make(field.ErrorList, 0)
spec := vpol.GetValidatingPolicySpec()
if spec == nil {
err = append(err, field.Required(field.NewPath("spec"), "spec must not be nil"))
warnings := make([]string, 0, len(err.ToAggregate().Errors()))
for _, e := range err.ToAggregate().Errors() {
warnings = append(warnings, e.Error())
}
return warnings, err.ToAggregate()
}
c := vpolcompiler.NewCompiler()
_, errList := c.Compile(vpol, nil)
if errList != nil {
err = errList
}
if spec.MatchConstraints == nil || len(spec.MatchConstraints.ResourceRules) == 0 {
err = append(err, field.Required(field.NewPath("spec").Child("matchConstraints"), "a matchConstraints with at least one resource rule is required"))
}
if vpol.GetNamespace() != "" {
exprs := vpolExpressions(spec)
if compiler.ExpressionsUseGlobalContext(exprs...) {
err = append(err, field.Forbidden(field.NewPath("spec"), "globalContext.* is not allowed in namespaced policies"))
}
if !toggle.AllowHTTPInNamespacedPolicies.Enabled() && compiler.ExpressionsUseHTTP(exprs...) {
err = append(err, field.Forbidden(field.NewPath("spec"), "http.* is not allowed in namespaced policies; set --allowHTTPInNamespacedPolicies to enable"))
}
}
if len(err) == 0 {
return warnings, nil
}
for _, e := range err.ToAggregate().Errors() {
warnings = append(warnings, e.Error())
}
return warnings, err.ToAggregate()
}
func vpolExpressions(spec *v1beta1.ValidatingPolicySpec) []string {
exprs := make([]string, 0, len(spec.Variables)+len(spec.MatchConditions)+len(spec.Validations)*2+len(spec.AuditAnnotations))
for _, v := range spec.Variables {
exprs = append(exprs, v.Expression)
}
for _, mc := range spec.MatchConditions {
exprs = append(exprs, mc.Expression)
}
for _, val := range spec.Validations {
exprs = append(exprs, val.Expression, val.MessageExpression)
}
for _, aa := range spec.AuditAnnotations {
exprs = append(exprs, aa.ValueExpression)
}
return exprs
}
package anchor
import (
"regexp"
"strings"
)
type AnchorType string
const (
Condition AnchorType = ""
Global AnchorType = "<"
Negation AnchorType = "X"
AddIfNotPresent AnchorType = "+"
Equality AnchorType = "="
Existence AnchorType = "^"
)
var regex = regexp.MustCompile(`^(?P<modifier>[+<=X^])?\((?P<key>.+)\)$`)
// Anchor interface
type Anchor interface {
// Type returns the anchor type
Type() AnchorType
// Key returns the anchor key
Key() string
// String returns the anchor string
String() string
}
type anchor struct {
modifier AnchorType
key string
}
// Parse parses a string, returns nil if not an anchor
func Parse(str string) Anchor {
str = strings.TrimSpace(str)
values := regex.FindStringSubmatch(str)
if len(values) == 0 {
return nil
}
return New(AnchorType(values[1]), values[2])
}
// New creates an anchor
func New(modifier AnchorType, key string) Anchor {
if key == "" {
return nil
}
return anchor{
modifier: modifier,
key: key,
}
}
// String returns the anchor string.
// Will return an empty string if key is empty.
func String(modifier AnchorType, key string) string {
if key == "" {
return ""
}
return string(modifier) + "(" + key + ")"
}
func (a anchor) Type() AnchorType {
return a.modifier
}
func (a anchor) Key() string {
return a.key
}
func (a anchor) String() string {
return String(a.modifier, a.key)
}
// IsOneOf returns checks if anchor is one of the given types
func IsOneOf(a Anchor, types ...AnchorType) bool {
if a != nil {
for _, t := range types {
if t == a.Type() {
return true
}
}
}
return false
}
// ContainsCondition returns true if anchor is either condition anchor or global condition anchor
func ContainsCondition(a Anchor) bool {
return IsOneOf(a, Condition, Global)
}
// IsCondition checks for condition anchor
func IsCondition(a Anchor) bool {
return IsOneOf(a, Condition)
}
// IsGlobal checks for global condition anchor
func IsGlobal(a Anchor) bool {
return IsOneOf(a, Global)
}
// IsNegation checks for negation anchor
func IsNegation(a Anchor) bool {
return IsOneOf(a, Negation)
}
// IsAddIfNotPresent checks for addition anchor
func IsAddIfNotPresent(a Anchor) bool {
return IsOneOf(a, AddIfNotPresent)
}
// IsEquality checks for equality anchor
func IsEquality(a Anchor) bool {
return IsOneOf(a, Equality)
}
// IsExistence checks for existence anchor
func IsExistence(a Anchor) bool {
return IsOneOf(a, Existence)
}
package anchor
// AnchorMap - contains map of anchors
type AnchorMap struct {
// anchorMap - for each anchor key in the patterns it will maintain information if the key exists in the resource
// if anchor key of the pattern exists in the resource then (key)=true else (key)=false
anchorMap map[string]bool
// AnchorError - used in validate to break execution of the recursion when if condition fails
AnchorError validateAnchorError
}
// NewAnchorMap -initialize anchorMap
func NewAnchorMap() *AnchorMap {
return &AnchorMap{anchorMap: map[string]bool{}}
}
// KeysAreMissing - if any of the anchor key doesn't exists in the resource then it will return true
// if any of (key)=false then return KeysAreMissing() as true
// if all the keys exists in the pattern exists in resource then return KeysAreMissing() as false
func (ac *AnchorMap) KeysAreMissing() bool {
for k, v := range ac.anchorMap {
if !v {
// Negations should not be present in the resource so they count as missing.
if a := Parse(k); IsNegation(a) {
continue
}
return true
}
}
return false
}
// CheckAnchorInResource checks if condition anchor key has values
func (ac *AnchorMap) CheckAnchorInResource(pattern map[string]interface{}, resource interface{}) {
for key := range pattern {
if a := Parse(key); IsCondition(a) || IsExistence(a) || IsNegation(a) {
val, ok := ac.anchorMap[key]
if !ok {
ac.anchorMap[key] = false
} else if ok && val {
continue
}
if resourceHasValueForKey(resource, a.Key()) {
ac.anchorMap[key] = true
}
}
}
}
package anchor
import (
"fmt"
)
// anchorError is the const specification of anchor errors
type anchorError int
const (
// conditionalAnchorErr refers to condition violation
conditionalAnchorErr anchorError = iota
// globalAnchorErr refers to global condition violation
globalAnchorErr
// negationAnchorErr refers to negation violation
negationAnchorErr
)
const (
// negationAnchorErrMsg - the error message for negation anchor error
negationAnchorErrMsg = "negation anchor matched in resource"
// conditionalAnchorErrMsg - the error message for conditional anchor error
conditionalAnchorErrMsg = "conditional anchor mismatch"
// globalAnchorErrMsg - the error message for global anchor error
globalAnchorErrMsg = "global anchor mismatch"
)
// validateAnchorError represents the error type of validation anchors
type validateAnchorError struct {
err anchorError
message string
}
// Error implements error interface
func (e validateAnchorError) Error() string {
return e.message
}
// newNegationAnchorError returns a new instance of validateAnchorError
func newValidateAnchorError(err anchorError, prefix, msg string) validateAnchorError {
return validateAnchorError{
err: err,
message: fmt.Sprintf("%s: %s", prefix, msg),
}
}
// newNegationAnchorError returns a new instance of NegationAnchorError
func newNegationAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(negationAnchorErr, negationAnchorErrMsg, msg)
}
// newConditionalAnchorError returns a new instance of ConditionalAnchorError
func newConditionalAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(conditionalAnchorErr, conditionalAnchorErrMsg, msg)
}
// newGlobalAnchorError returns a new instance of GlobalAnchorError
func newGlobalAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(globalAnchorErr, globalAnchorErrMsg, msg)
}
// isError checks if error matches the given error type
func isError(err error, code anchorError) bool {
if err == nil {
return false
}
if t, ok := err.(validateAnchorError); ok && t.err == code {
return true
}
if t, ok := err.(*validateAnchorError); ok && t != nil && t.err == code {
return true
}
switch u := err.(type) {
case interface{ Unwrap() error }:
return isError(u.Unwrap(), code)
case interface{ Unwrap() []error }:
for _, e := range u.Unwrap() {
if isError(e, code) {
return true
}
}
}
return false
}
// IsNegationAnchorError checks if error is a negation anchor error
func IsNegationAnchorError(err error) bool {
return isError(err, negationAnchorErr)
}
// IsConditionalAnchorError checks if error is a conditional anchor error
func IsConditionalAnchorError(err error) bool {
return isError(err, conditionalAnchorErr)
}
// IsGlobalAnchorError checks if error is a global anchor error
func IsGlobalAnchorError(err error) bool {
return isError(err, globalAnchorErr)
}
package anchor
import (
"fmt"
"strconv"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/logging"
)
type resourceElementHandler = func(
log logr.Logger,
resourceElement interface{},
patternElement interface{},
originPattern interface{},
path string,
ac *AnchorMap,
) (string, error)
// ValidationHandler for element processes
type ValidationHandler interface {
Handle(
handler resourceElementHandler,
resourceMap map[string]interface{},
originPattern interface{},
ac *AnchorMap,
) (string, error)
}
// CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
if anchor := Parse(element); anchor != nil {
switch {
case IsCondition(anchor):
return newConditionAnchorHandler(anchor, pattern, path)
case IsGlobal(anchor):
return newGlobalAnchorHandler(anchor, pattern, path)
case IsExistence(anchor):
return newExistenceHandler(anchor, pattern, path)
case IsEquality(anchor):
return newEqualityHandler(anchor, pattern, path)
case IsNegation(anchor):
return newNegationHandler(anchor, pattern, path)
}
}
return newDefaultHandler(element, pattern, path)
}
// negationHandler provides handler for check if the tag in anchor is not defined
type negationHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newNegationHandler returns instance of negation handler
func newNegationHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return negationHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle process negation handler
func (nh negationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := nh.anchor.Key()
currentPath := nh.path + anchorKey + "/"
// 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
ac.AnchorError = newNegationAnchorError(fmt.Sprintf("%s is not allowed", currentPath))
return currentPath, ac.AnchorError
}
// key is not defined in the resource
return "", nil
}
// equalityHandler provides handler for non anchor element
type equalityHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newEqualityHandler returens instance of equality handler
func newEqualityHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return equalityHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed equality anchor
func (eh equalityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := eh.anchor.Key()
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, eh.pattern, originPattern, currentPath, ac)
if err != nil {
return returnPath, err
}
return "", nil
}
return "", nil
}
// defaultHandler provides handler for non anchor element
type defaultHandler struct {
element string
pattern interface{}
path string
}
// newDefaultHandler returns handler for non anchor elements
func newDefaultHandler(element string, pattern interface{}, path string) ValidationHandler {
return defaultHandler{
element: element,
pattern: pattern,
path: path,
}
}
// Handle process non anchor element
func (dh defaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
currentPath := dh.path + dh.element + "/"
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, fmt.Errorf("%s/%s not found", dh.path, dh.element)
} else {
path, err := handler(logging.GlobalLogger(), resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
if err != nil {
return path, err
}
}
return "", nil
}
// conditionAnchorHandler provides handler for condition anchor
type conditionAnchorHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newConditionAnchorHandler returns an instance of condition acnhor handler
func newConditionAnchorHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return conditionAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed condition anchor
func (ch conditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := ch.anchor.Key()
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, ch.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = newConditionalAnchorError(err.Error())
return returnPath, ac.AnchorError
}
return "", nil
} else {
msg := "conditional anchor key doesn't exist in the resource"
return currentPath, newConditionalAnchorError(msg)
}
}
// globalAnchorHandler provides handler for global condition anchor
type globalAnchorHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newGlobalAnchorHandler returns an instance of condition acnhor handler
func newGlobalAnchorHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return globalAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed global condition anchor
func (gh globalAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := gh.anchor.Key()
currentPath := gh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, gh.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = newGlobalAnchorError(err.Error())
return returnPath, ac.AnchorError
}
return "", nil
}
return "", nil
}
// existenceHandler provides handlers to process exitence anchor handler
type existenceHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newExistenceHandler returns existence handler
func newExistenceHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return existenceHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processes the existence anchor handler
func (eh existenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
// skip is used by existence anchor to not process further if condition is not satisfied
anchorKey := eh.anchor.Key()
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// Existence anchor can only exist on resource value type of list
switch typedResource := value.(type) {
case []interface{}:
typedPattern, ok := eh.pattern.([]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of list to compare against resource", eh.pattern)
}
// loop all item in the pattern array
errorPath := ""
var err error
for _, patternMap := range typedPattern {
typedPatternMap, ok := patternMap.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
}
errorPath, err = validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
if err != nil {
return errorPath, err
}
}
return errorPath, err
default:
return currentPath, fmt.Errorf("invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value)
}
}
return "", nil
}
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *AnchorMap) (string, error) {
// the idea is all the element in the pattern array should be present at least once in the resource list
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := handler(logging.GlobalLogger(), resourceElement, patternMap, originPattern, currentPath, ac)
if err == nil {
// condition is satisfied, dont check further
return "", nil
}
}
// none of the existence checks worked, so thats a failure sceanario
return path, fmt.Errorf("existence anchor validation failed at path %s", path)
}
package anchor
import (
"path"
"strings"
)
// GetAnchorsResourcesFromMap returns maps of anchors and resources
func GetAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if a := Parse(key); IsCondition(a) || IsExistence(a) || IsEquality(a) || IsNegation(a) {
anchors[key] = value
} else {
resources[key] = value
}
}
return anchors, resources
}
// RemoveAnchorsFromPath removes all anchor from path string
func RemoveAnchorsFromPath(str string) string {
parts := strings.Split(str, "/")
if parts[0] == "" {
parts = parts[1:]
}
for i, part := range parts {
if a := Parse(part); a != nil {
parts[i] = a.Key()
} else {
parts[i] = part
}
}
newPath := path.Join(parts...)
if path.IsAbs(str) {
newPath = "/" + newPath
}
return newPath
}
// resourceHasValueForKey checks if a resource has value for a given key
func resourceHasValueForKey(resource interface{}, key string) bool {
switch typed := resource.(type) {
case map[string]interface{}:
if _, ok := typed[key]; ok {
return true
}
return false
case []interface{}:
for _, value := range typed {
if resourceHasValueForKey(value, key) {
return true
}
}
return false
default:
return false
}
}
package api
import (
"fmt"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// EngineResponse engine response to the action
type EngineResponse struct {
// Resource is the original resource
Resource unstructured.Unstructured
// Policy is the original policy
policy GenericPolicy
// namespaceLabels given by policy context
namespaceLabels map[string]string
// PatchedResource is the resource patched with the engine action changes
PatchedResource unstructured.Unstructured
// PolicyResponse contains the engine policy response
PolicyResponse PolicyResponse
// stats contains engine statistics
stats ExecutionStats
}
func NewEngineResponse(
resource unstructured.Unstructured,
policy GenericPolicy,
namespaceLabels map[string]string,
) EngineResponse {
return EngineResponse{
Resource: resource,
policy: policy,
namespaceLabels: namespaceLabels,
PatchedResource: resource,
}
}
func (er EngineResponse) WithPolicy(policy GenericPolicy) EngineResponse {
er.policy = policy
return er
}
func (er EngineResponse) WithPolicyResponse(policyResponse PolicyResponse) EngineResponse {
er.PolicyResponse = policyResponse
return er
}
func (r EngineResponse) WithStats(stats ExecutionStats) EngineResponse {
r.stats = stats
return r
}
func (er EngineResponse) WithPatchedResource(patchedResource unstructured.Unstructured) EngineResponse {
er.PatchedResource = patchedResource
return er
}
func (er EngineResponse) WithNamespaceLabels(namespaceLabels map[string]string) EngineResponse {
er.namespaceLabels = namespaceLabels
return er
}
func (er EngineResponse) WithWarning() EngineResponse {
er.PolicyResponse.emitWarning = true
return er
}
func (er *EngineResponse) NamespaceLabels() map[string]string {
return er.namespaceLabels
}
func (er *EngineResponse) Policy() GenericPolicy {
return er.policy
}
// IsOneOf checks if any rule has status in a given list
func (er EngineResponse) IsOneOf(status ...RuleStatus) bool {
for _, r := range er.PolicyResponse.Rules {
if r.HasStatus(status...) {
return true
}
}
return false
}
// IsSuccessful checks if any rule has failed or produced an error during execution
func (er EngineResponse) IsSuccessful() bool {
return !er.IsOneOf(RuleStatusFail, RuleStatusError)
}
// IsSkipped checks if any rule has skipped resource or not.
func (er EngineResponse) IsSkipped() bool {
return er.IsOneOf(RuleStatusSkip)
}
// IsFailed checks if any rule created a policy violation
func (er EngineResponse) IsFailed() bool {
return er.IsOneOf(RuleStatusFail)
}
// IsError checks if any rule resulted in a processing error
func (er EngineResponse) IsError() bool {
return er.IsOneOf(RuleStatusError)
}
// IsEmpty checks if any rule results are present
func (er EngineResponse) IsEmpty() bool {
return len(er.PolicyResponse.Rules) == 0
}
// IsNil checks if rule is an empty rule
func (er EngineResponse) IsNil() bool {
return datautils.DeepEqual(er, EngineResponse{})
}
// EmitsWarning checks if policy emits warnings
func (er EngineResponse) EmitsWarning() bool {
return er.PolicyResponse.emitWarning
}
// GetPatches returns all the patches joined
func (er EngineResponse) GetPatches() []jsonpatch.JsonPatchOperation {
originalBytes, err := er.Resource.MarshalJSON()
if err != nil {
return nil
}
patchedBytes, err := er.PatchedResource.MarshalJSON()
if err != nil {
return nil
}
patches, err := jsonpatch.CreatePatch(originalBytes, patchedBytes)
if err != nil {
return nil
}
return patches
}
// GetFailedRules returns failed rules
func (er EngineResponse) GetFailedRules() []string {
return er.getRules(func(rule RuleResponse) bool { return rule.HasStatus(RuleStatusFail, RuleStatusError) })
}
// GetFailedRulesWithErrors returns failed rules with corresponding error messages
func (er EngineResponse) GetFailedRulesWithErrors() []string {
return er.getRulesWithErrors(func(rule RuleResponse) bool { return rule.HasStatus(RuleStatusFail, RuleStatusError) })
}
// GetSuccessRules returns success rules
func (er EngineResponse) GetSuccessRules() []string {
return er.getRules(func(rule RuleResponse) bool { return rule.HasStatus(RuleStatusPass) })
}
// GetResourceSpec returns resourceSpec of er
func (er EngineResponse) GetResourceSpec() ResourceSpec {
if er.PatchedResource.Object == nil {
return ResourceSpec{
Kind: er.Resource.GetKind(),
APIVersion: er.Resource.GetAPIVersion(),
Namespace: er.Resource.GetNamespace(),
Name: er.Resource.GetName(),
UID: string(er.Resource.GetUID()),
}
}
return ResourceSpec{
Kind: er.PatchedResource.GetKind(),
APIVersion: er.PatchedResource.GetAPIVersion(),
Namespace: er.PatchedResource.GetNamespace(),
Name: er.PatchedResource.GetName(),
UID: string(er.PatchedResource.GetUID()),
}
}
func (er EngineResponse) getRules(predicate func(RuleResponse) bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
if predicate(r) {
rules = append(rules, r.Name())
}
}
return rules
}
func (er EngineResponse) getRulesWithErrors(predicate func(RuleResponse) bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
if predicate(r) {
rules = append(rules, fmt.Sprintf("%s: %s", r.Name(), r.Message()))
}
}
return rules
}
package api
import (
policiesv1beta1 "github.com/kyverno/api/api/policies.kyverno.io/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GenericException abstracts the exception type (CEL PolicyException)
type GenericException interface {
metav1.Object
// GetAPIVersion returns policy API version
GetAPIVersion() string
// GetKind returns policy kind
GetKind() string
// AsCELException returns the CEL policy exception
AsCELException() *policiesv1beta1.PolicyException
}
type genericException struct {
metav1.Object
CELPolicyException *policiesv1beta1.PolicyException
}
func (p *genericException) AsCELException() *policiesv1beta1.PolicyException {
return p.CELPolicyException
}
func (p *genericException) GetAPIVersion() string {
switch {
case p.CELPolicyException != nil:
return policiesv1beta1.GroupVersion.String()
}
return ""
}
func (p *genericException) GetKind() string {
switch {
case p.CELPolicyException != nil:
return "CELPolicyException"
}
return ""
}
func NewCELPolicyException(polex *policiesv1beta1.PolicyException) GenericException {
return &genericException{
Object: polex,
CELPolicyException: polex,
}
}
package api
import (
"encoding/json"
"fmt"
"strings"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/kyverno"
"gomodules.xyz/jsonpatch/v2"
)
type ImageVerificationMetadataStatus string
const (
ImageVerificationPass ImageVerificationMetadataStatus = "pass"
ImageVerificationFail ImageVerificationMetadataStatus = "fail"
ImageVerificationSkip ImageVerificationMetadataStatus = "skip"
)
type ImageVerificationMetadata struct {
Data map[string]ImageVerificationMetadataStatus
Policies map[string]map[string]map[string]ImageVerificationMetadataStatus
}
type scopedImageVerificationMetadata struct {
Version int `json:"version"`
Policies map[string]map[string]map[string]ImageVerificationMetadataStatus `json:"policies"`
}
func imageVerificationPolicyKey(namespace, name string) string {
return namespace + "/" + name
}
func (ivm *ImageVerificationMetadata) Add(image string, verified ImageVerificationMetadataStatus) {
if ivm.Data == nil {
ivm.Data = make(map[string]ImageVerificationMetadataStatus)
}
ivm.Data[image] = verified
}
func (ivm *ImageVerificationMetadata) AddScoped(namespace, policy, rule, image string, verified ImageVerificationMetadataStatus) {
if ivm.Policies == nil {
ivm.Policies = make(map[string]map[string]map[string]ImageVerificationMetadataStatus)
}
policyKey := imageVerificationPolicyKey(namespace, policy)
if ivm.Policies[policyKey] == nil {
ivm.Policies[policyKey] = make(map[string]map[string]ImageVerificationMetadataStatus)
}
if ivm.Policies[policyKey][rule] == nil {
ivm.Policies[policyKey][rule] = make(map[string]ImageVerificationMetadataStatus)
}
ivm.Policies[policyKey][rule][image] = verified
}
func (ivm *ImageVerificationMetadata) IsVerified(image string) bool {
if ivm.Data == nil {
return false
}
verified, ok := ivm.Data[image]
if !ok {
return false
}
return verified == ImageVerificationPass || verified == ImageVerificationSkip
}
func (ivm *ImageVerificationMetadata) ImageVerificationStatus(image string) ImageVerificationMetadataStatus {
if verified, ok := ivm.Data[image]; ok {
return verified
}
return ImageVerificationFail
}
func (ivm *ImageVerificationMetadata) ScopedImageVerificationStatus(namespace, policy, rule, image string) ImageVerificationMetadataStatus {
if rules := ivm.Policies[imageVerificationPolicyKey(namespace, policy)]; rules != nil {
if images := rules[rule]; images != nil {
if verified, ok := images[image]; ok {
return verified
}
}
}
return ImageVerificationFail
}
func ParseImageMetadata(jsonData string) (*ImageVerificationMetadata, error) {
var scoped scopedImageVerificationMetadata
if err := json.Unmarshal([]byte(jsonData), &scoped); err == nil && scoped.Version == 1 && scoped.Policies != nil {
return &ImageVerificationMetadata{Policies: scoped.Policies}, nil
}
var data map[string]ImageVerificationMetadataStatus
if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
return nil, err
}
return &ImageVerificationMetadata{
Data: data,
}, nil
}
func (ivm *ImageVerificationMetadata) Patches(hasAnnotations bool, log logr.Logger) ([]jsonpatch.JsonPatchOperation, error) {
if data, err := json.Marshal(ivm.Data); err != nil {
return nil, fmt.Errorf("failed to marshal metadata value: %v: %w", data, err)
} else {
var patches []jsonpatch.JsonPatchOperation
if !hasAnnotations {
patch := jsonpatch.JsonPatchOperation{
Operation: "add",
Path: "/metadata/annotations",
Value: map[string]string{},
}
log.V(4).Info("adding annotation patch", "patch", patch)
patches = append(patches, patch)
}
patch := jsonpatch.JsonPatchOperation{
Operation: "add",
Path: makeAnnotationKeyForJSONPatch(),
Value: string(data),
}
log.V(4).Info("adding image verification patch", "patch", patch)
patches = append(patches, patch)
if ivm.Policies != nil {
scopedData, err := json.Marshal(scopedImageVerificationMetadata{Version: 1, Policies: ivm.Policies})
if err != nil {
return nil, fmt.Errorf("failed to marshal scoped metadata value: %v: %w", scopedData, err)
}
scopedPatch := jsonpatch.JsonPatchOperation{
Operation: "add",
Path: makeScopedAnnotationKeyForJSONPatch(),
Value: string(scopedData),
}
log.V(4).Info("adding scoped image verification patch", "patch", scopedPatch)
patches = append(patches, scopedPatch)
}
return patches, nil
}
}
func (ivm *ImageVerificationMetadata) Merge(other ImageVerificationMetadata) {
for k, v := range other.Data {
ivm.Add(k, v)
}
for policy, rules := range other.Policies {
for rule, images := range rules {
for image, status := range images {
if ivm.Policies == nil {
ivm.Policies = make(map[string]map[string]map[string]ImageVerificationMetadataStatus)
}
if ivm.Policies[policy] == nil {
ivm.Policies[policy] = make(map[string]map[string]ImageVerificationMetadataStatus)
}
if ivm.Policies[policy][rule] == nil {
ivm.Policies[policy][rule] = make(map[string]ImageVerificationMetadataStatus)
}
ivm.Policies[policy][rule][image] = status
}
}
}
}
func (ivm *ImageVerificationMetadata) IsEmpty() bool {
return len(ivm.Data) == 0 && len(ivm.Policies) == 0
}
func makeAnnotationKeyForJSONPatch() string {
return "/metadata/annotations/" + strings.ReplaceAll(kyverno.AnnotationImageVerify, "/", "~1")
}
func makeScopedAnnotationKeyForJSONPatch() string {
return "/metadata/annotations/" + strings.ReplaceAll(kyverno.AnnotationImageVerifyScoped, "/", "~1")
}
package api
import (
policiesv1beta1 "github.com/kyverno/api/api/policies.kyverno.io/v1beta1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// Everything someone might need to validate a single ValidatingAdmissionPolicy
// against all of its registered bindings.
type ValidatingAdmissionPolicyData struct {
definition *admissionregistrationv1.ValidatingAdmissionPolicy
bindings []admissionregistrationv1.ValidatingAdmissionPolicyBinding
params []runtime.Object
}
func (p *ValidatingAdmissionPolicyData) AddBinding(binding admissionregistrationv1.ValidatingAdmissionPolicyBinding) {
p.bindings = append(p.bindings, binding)
}
func (p *ValidatingAdmissionPolicyData) AddParam(param runtime.Object) {
p.params = append(p.params, param)
}
func (p *ValidatingAdmissionPolicyData) GetDefinition() *admissionregistrationv1.ValidatingAdmissionPolicy {
return p.definition
}
func (p *ValidatingAdmissionPolicyData) GetBindings() []admissionregistrationv1.ValidatingAdmissionPolicyBinding {
return p.bindings
}
func (p *ValidatingAdmissionPolicyData) GetParams() []runtime.Object {
return p.params
}
func NewValidatingAdmissionPolicyData(
policy *admissionregistrationv1.ValidatingAdmissionPolicy,
bindings ...admissionregistrationv1.ValidatingAdmissionPolicyBinding,
) *ValidatingAdmissionPolicyData {
return &ValidatingAdmissionPolicyData{
definition: policy,
bindings: bindings,
}
}
// MutatingPolicyData holds a MAP and its associated MAPBs
type MutatingAdmissionPolicyData struct {
definition *admissionregistrationv1beta1.MutatingAdmissionPolicy
bindings []admissionregistrationv1beta1.MutatingAdmissionPolicyBinding
params []runtime.Object
}
// AddBinding appends a MAPB to the policy data
func (m *MutatingAdmissionPolicyData) AddBinding(b admissionregistrationv1beta1.MutatingAdmissionPolicyBinding) {
m.bindings = append(m.bindings, b)
}
func (m *MutatingAdmissionPolicyData) AddParam(p runtime.Object) {
m.params = append(m.params, p)
}
func (p *MutatingAdmissionPolicyData) GetDefinition() *admissionregistrationv1beta1.MutatingAdmissionPolicy {
return p.definition
}
func (p *MutatingAdmissionPolicyData) GetBindings() []admissionregistrationv1beta1.MutatingAdmissionPolicyBinding {
return p.bindings
}
func (p *MutatingAdmissionPolicyData) GetParams() []runtime.Object {
return p.params
}
// NewMutatingPolicyData initializes a MAP wrapper with no bindings
func NewMutatingAdmissionPolicyData(
policy *admissionregistrationv1beta1.MutatingAdmissionPolicy,
bindings ...admissionregistrationv1beta1.MutatingAdmissionPolicyBinding,
) *MutatingAdmissionPolicyData {
return &MutatingAdmissionPolicyData{
definition: policy,
bindings: bindings,
}
}
// GenericPolicy abstracts the policy type (ValidatingPolicy, ValidatingAdmissionPolicy and MutatingAdmissionPolicy)
// It is intended to be used in EngineResponse
type GenericPolicy interface {
metav1.Object
// GetAPIVersion returns policy API version
GetAPIVersion() string
// GetKind returns policy kind
GetKind() string
// IsNamespaced indicates if the policy is namespace scoped
IsNamespaced() bool
// AsObject returns the raw underlying policy
AsObject() any
// AsValidatingAdmissionPolicy returns the validating admission policy with its bindings
AsValidatingAdmissionPolicy() *ValidatingAdmissionPolicyData
// AsValidatingPolicyLike returns the validating policy
AsValidatingPolicyLike() policiesv1beta1.ValidatingPolicyLike
// AsValidatingPolicy returns the validating policy
AsValidatingPolicy() *policiesv1beta1.ValidatingPolicy
// AsNamespacedValidatingPolicy returns the namespaced validating policy
AsNamespacedValidatingPolicy() *policiesv1beta1.NamespacedValidatingPolicy
// AsImageValidatingPolicyLike returns the imageverificationpolicy
AsImageValidatingPolicyLike() policiesv1beta1.ImageValidatingPolicyLike
// AsImageValidatingPolicy returns the imageverificationpolicy
AsImageValidatingPolicy() *policiesv1beta1.ImageValidatingPolicy
// AsNamespacedImageValidatingPolicy returns the namespaced imageverificationpolicy
AsNamespacedImageValidatingPolicy() *policiesv1beta1.NamespacedImageValidatingPolicy
// AsMutatingAdmissionPolicy returns the mutatingadmission policy
AsMutatingAdmissionPolicy() *MutatingAdmissionPolicyData
// AsMutatingPolicyLike returns the mutating policy
AsMutatingPolicyLike() policiesv1beta1.MutatingPolicyLike
// AsMutatingPolicy returns the mutating policy
AsMutatingPolicy() *policiesv1beta1.MutatingPolicy
// AsNamespacedMutatingPolicy returns the namespaced mutating policy
AsNamespacedMutatingPolicy() *policiesv1beta1.NamespacedMutatingPolicy
// AsGeneratingPolicyLike returns the generating policy
AsGeneratingPolicyLike() policiesv1beta1.GeneratingPolicyLike
// AsGeneratingPolicy returns the generating policy
AsGeneratingPolicy() *policiesv1beta1.GeneratingPolicy
// AsNamespacedGeneratingPolicy returns the namespaced generating policy
AsNamespacedGeneratingPolicy() *policiesv1beta1.NamespacedGeneratingPolicy
// AsDeletingPolicy returns the deleting policy
AsDeletingPolicy() policiesv1beta1.DeletingPolicyLike
}
type genericPolicy struct {
metav1.Object
ValidatingAdmissionPolicy *ValidatingAdmissionPolicyData
MutatingAdmissionPolicy *MutatingAdmissionPolicyData
ValidatingPolicy *policiesv1beta1.ValidatingPolicy
NamespacedValidatingPolicy *policiesv1beta1.NamespacedValidatingPolicy
ImageValidatingPolicy *policiesv1beta1.ImageValidatingPolicy
NamespacedImageValidatingPolicy *policiesv1beta1.NamespacedImageValidatingPolicy
MutatingPolicy *policiesv1beta1.MutatingPolicy
NamespacedMutatingPolicy *policiesv1beta1.NamespacedMutatingPolicy
GeneratingPolicy *policiesv1beta1.GeneratingPolicy
NamespacedGeneratingPolicy *policiesv1beta1.NamespacedGeneratingPolicy
DeletingPolicy policiesv1beta1.DeletingPolicyLike
// originalAPIVersion tracks the original API version for converted policies
originalAPIVersion string
}
func (p *genericPolicy) AsObject() any {
return p.Object
}
func (p *genericPolicy) AsValidatingAdmissionPolicy() *ValidatingAdmissionPolicyData {
return p.ValidatingAdmissionPolicy
}
func (p *genericPolicy) AsMutatingAdmissionPolicy() *MutatingAdmissionPolicyData {
return p.MutatingAdmissionPolicy
}
func (p *genericPolicy) AsValidatingPolicyLike() policiesv1beta1.ValidatingPolicyLike {
if v := p.AsValidatingPolicy(); v != nil {
return v
}
if v := p.AsNamespacedValidatingPolicy(); v != nil {
return v
}
return nil
}
func (p *genericPolicy) AsValidatingPolicy() *policiesv1beta1.ValidatingPolicy {
return p.ValidatingPolicy
}
func (p *genericPolicy) AsNamespacedValidatingPolicy() *policiesv1beta1.NamespacedValidatingPolicy {
return p.NamespacedValidatingPolicy
}
func (p *genericPolicy) AsImageValidatingPolicyLike() policiesv1beta1.ImageValidatingPolicyLike {
if v := p.AsImageValidatingPolicy(); v != nil {
return v
}
if v := p.AsNamespacedImageValidatingPolicy(); v != nil {
return v
}
return nil
}
func (p *genericPolicy) AsImageValidatingPolicy() *policiesv1beta1.ImageValidatingPolicy {
return p.ImageValidatingPolicy
}
func (p *genericPolicy) AsNamespacedImageValidatingPolicy() *policiesv1beta1.NamespacedImageValidatingPolicy {
return p.NamespacedImageValidatingPolicy
}
func (p *genericPolicy) AsMutatingPolicyLike() policiesv1beta1.MutatingPolicyLike {
if m := p.AsMutatingPolicy(); m != nil {
return m
}
if m := p.AsNamespacedMutatingPolicy(); m != nil {
return m
}
return nil
}
func (p *genericPolicy) AsMutatingPolicy() *policiesv1beta1.MutatingPolicy {
return p.MutatingPolicy
}
func (p *genericPolicy) AsNamespacedMutatingPolicy() *policiesv1beta1.NamespacedMutatingPolicy {
return p.NamespacedMutatingPolicy
}
func (p *genericPolicy) AsGeneratingPolicyLike() policiesv1beta1.GeneratingPolicyLike {
if g := p.AsGeneratingPolicy(); g != nil {
return g
}
if g := p.AsNamespacedGeneratingPolicy(); g != nil {
return g
}
return nil
}
func (p *genericPolicy) AsGeneratingPolicy() *policiesv1beta1.GeneratingPolicy {
return p.GeneratingPolicy
}
func (p *genericPolicy) AsNamespacedGeneratingPolicy() *policiesv1beta1.NamespacedGeneratingPolicy {
return p.NamespacedGeneratingPolicy
}
func (p *genericPolicy) AsDeletingPolicy() policiesv1beta1.DeletingPolicyLike {
return p.DeletingPolicy
}
func (p *genericPolicy) GetAPIVersion() string {
switch {
case p.ValidatingAdmissionPolicy != nil:
return admissionregistrationv1.SchemeGroupVersion.String()
case p.MutatingAdmissionPolicy != nil:
if p.originalAPIVersion != "" {
return p.originalAPIVersion
}
return admissionregistrationv1beta1.SchemeGroupVersion.String()
case p.ValidatingPolicy != nil:
if apiVersion := p.ValidatingPolicy.APIVersion; apiVersion != "" {
return apiVersion
}
return policiesv1beta1.GroupVersion.String()
case p.NamespacedValidatingPolicy != nil:
if apiVersion := p.NamespacedValidatingPolicy.APIVersion; apiVersion != "" {
return apiVersion
}
return policiesv1beta1.GroupVersion.String()
case p.ImageValidatingPolicy != nil:
return policiesv1beta1.GroupVersion.String()
case p.NamespacedImageValidatingPolicy != nil:
return policiesv1beta1.GroupVersion.String()
case p.MutatingPolicy != nil:
if apiVersion := p.MutatingPolicy.APIVersion; apiVersion != "" {
return apiVersion
}
return policiesv1beta1.GroupVersion.String()
case p.NamespacedMutatingPolicy != nil:
if apiVersion := p.NamespacedMutatingPolicy.APIVersion; apiVersion != "" {
return apiVersion
}
return policiesv1beta1.GroupVersion.String()
case p.GeneratingPolicy != nil:
return policiesv1beta1.GroupVersion.String()
case p.NamespacedGeneratingPolicy != nil:
return policiesv1beta1.GroupVersion.String()
case p.DeletingPolicy != nil:
return policiesv1beta1.GroupVersion.String()
}
return ""
}
func (p *genericPolicy) GetKind() string {
switch {
case p.ValidatingAdmissionPolicy != nil:
return "ValidatingAdmissionPolicy"
case p.MutatingAdmissionPolicy != nil:
return "MutatingAdmissionPolicy"
case p.ValidatingPolicy != nil:
return p.ValidatingPolicy.GetKind()
case p.NamespacedValidatingPolicy != nil:
return p.NamespacedValidatingPolicy.GetKind()
case p.ImageValidatingPolicy != nil:
return p.ImageValidatingPolicy.GetKind()
case p.NamespacedImageValidatingPolicy != nil:
return p.NamespacedImageValidatingPolicy.GetKind()
case p.MutatingPolicy != nil:
return p.MutatingPolicy.GetKind()
case p.NamespacedMutatingPolicy != nil:
return p.NamespacedMutatingPolicy.GetKind()
case p.GeneratingPolicy != nil:
return "GeneratingPolicy"
case p.NamespacedGeneratingPolicy != nil:
return "NamespacedGeneratingPolicy"
case p.DeletingPolicy != nil:
return p.DeletingPolicy.GetKind()
}
return ""
}
func (p *genericPolicy) IsNamespaced() bool {
switch {
case p.NamespacedValidatingPolicy != nil:
return true
case p.NamespacedImageValidatingPolicy != nil:
return true
case p.NamespacedMutatingPolicy != nil:
return true
case p.NamespacedGeneratingPolicy != nil:
return true
case p.DeletingPolicy != nil:
return p.DeletingPolicy.GetNamespace() != ""
}
return false
}
func NewValidatingAdmissionPolicy(pol *admissionregistrationv1.ValidatingAdmissionPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
ValidatingAdmissionPolicy: NewValidatingAdmissionPolicyData(pol),
}
}
func NewValidatingAdmissionPolicyWithBindings(pol *admissionregistrationv1.ValidatingAdmissionPolicy, bindings ...admissionregistrationv1.ValidatingAdmissionPolicyBinding) GenericPolicy {
return &genericPolicy{
Object: pol,
ValidatingAdmissionPolicy: NewValidatingAdmissionPolicyData(pol, bindings...),
}
}
func NewMutatingAdmissionPolicy(pol *admissionregistrationv1beta1.MutatingAdmissionPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
MutatingAdmissionPolicy: NewMutatingAdmissionPolicyData(pol),
}
}
func NewMutatingAdmissionPolicyAlpha(pol *admissionregistrationv1alpha1.MutatingAdmissionPolicy) GenericPolicy {
v1beta1Pol := &admissionregistrationv1beta1.MutatingAdmissionPolicy{
TypeMeta: metav1.TypeMeta{
APIVersion: admissionregistrationv1beta1.SchemeGroupVersion.String(),
Kind: "MutatingAdmissionPolicy",
},
ObjectMeta: pol.ObjectMeta,
Spec: admissionregistrationv1beta1.MutatingAdmissionPolicySpec{
ParamKind: convertParamKind(pol.Spec.ParamKind),
MatchConstraints: convertMatchConstraints(pol.Spec.MatchConstraints),
Variables: convertVariables(pol.Spec.Variables),
Mutations: convertMutations(pol.Spec.Mutations),
FailurePolicy: (*admissionregistrationv1beta1.FailurePolicyType)(pol.Spec.FailurePolicy),
MatchConditions: convertMatchConditions(pol.Spec.MatchConditions),
ReinvocationPolicy: pol.Spec.ReinvocationPolicy,
},
}
return &genericPolicy{
Object: v1beta1Pol,
MutatingAdmissionPolicy: NewMutatingAdmissionPolicyData(v1beta1Pol),
originalAPIVersion: admissionregistrationv1alpha1.SchemeGroupVersion.String(),
}
}
func convertParamKind(paramKind *admissionregistrationv1alpha1.ParamKind) *admissionregistrationv1beta1.ParamKind {
if paramKind == nil {
return nil
}
return &admissionregistrationv1beta1.ParamKind{
APIVersion: paramKind.APIVersion,
Kind: paramKind.Kind,
}
}
func convertMatchConstraints(matchConstraints *admissionregistrationv1alpha1.MatchResources) *admissionregistrationv1beta1.MatchResources {
if matchConstraints == nil {
return nil
}
return &admissionregistrationv1beta1.MatchResources{
ResourceRules: convertResourceRules(matchConstraints.ResourceRules),
ExcludeResourceRules: convertResourceRules(matchConstraints.ExcludeResourceRules),
MatchPolicy: (*admissionregistrationv1beta1.MatchPolicyType)(matchConstraints.MatchPolicy),
}
}
func convertResourceRules(rules []admissionregistrationv1alpha1.NamedRuleWithOperations) []admissionregistrationv1beta1.NamedRuleWithOperations {
result := make([]admissionregistrationv1beta1.NamedRuleWithOperations, len(rules))
for i, r := range rules {
result[i] = admissionregistrationv1beta1.NamedRuleWithOperations{
ResourceNames: r.ResourceNames,
RuleWithOperations: admissionregistrationv1beta1.RuleWithOperations{
Operations: convertOperations(r.Operations),
Rule: admissionregistrationv1beta1.Rule{
APIGroups: r.APIGroups,
APIVersions: r.APIVersions,
Resources: r.Resources,
Scope: r.Scope,
},
},
}
}
return result
}
func convertOperations(ops []admissionregistrationv1alpha1.OperationType) []admissionregistrationv1beta1.OperationType {
result := make([]admissionregistrationv1beta1.OperationType, len(ops))
copy(result, ops)
return result
}
func convertVariables(vars []admissionregistrationv1alpha1.Variable) []admissionregistrationv1beta1.Variable {
result := make([]admissionregistrationv1beta1.Variable, len(vars))
for i, v := range vars {
result[i] = admissionregistrationv1beta1.Variable{
Name: v.Name,
Expression: v.Expression,
}
}
return result
}
func convertMutations(mutations []admissionregistrationv1alpha1.Mutation) []admissionregistrationv1beta1.Mutation {
result := make([]admissionregistrationv1beta1.Mutation, len(mutations))
for i, m := range mutations {
result[i] = admissionregistrationv1beta1.Mutation{
PatchType: admissionregistrationv1beta1.PatchType(m.PatchType),
ApplyConfiguration: convertApplyConfiguration(m.ApplyConfiguration),
JSONPatch: convertJSONPatch(m.JSONPatch),
}
}
return result
}
func convertApplyConfiguration(applyConfig *admissionregistrationv1alpha1.ApplyConfiguration) *admissionregistrationv1beta1.ApplyConfiguration {
if applyConfig == nil {
return nil
}
return &admissionregistrationv1beta1.ApplyConfiguration{
Expression: applyConfig.Expression,
}
}
func convertJSONPatch(jsonPatch *admissionregistrationv1alpha1.JSONPatch) *admissionregistrationv1beta1.JSONPatch {
if jsonPatch == nil {
return nil
}
return &admissionregistrationv1beta1.JSONPatch{
Expression: jsonPatch.Expression,
}
}
func convertMatchConditions(conditions []admissionregistrationv1alpha1.MatchCondition) []admissionregistrationv1beta1.MatchCondition {
result := make([]admissionregistrationv1beta1.MatchCondition, len(conditions))
for i, c := range conditions {
result[i] = admissionregistrationv1beta1.MatchCondition{
Name: c.Name,
Expression: c.Expression,
}
}
return result
}
func ConvertMutatingAdmissionPolicyBindingsAlpha(bindings []admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding) []admissionregistrationv1beta1.MutatingAdmissionPolicyBinding {
result := make([]admissionregistrationv1beta1.MutatingAdmissionPolicyBinding, len(bindings))
for i, binding := range bindings {
result[i] = admissionregistrationv1beta1.MutatingAdmissionPolicyBinding{
TypeMeta: metav1.TypeMeta{
APIVersion: admissionregistrationv1beta1.SchemeGroupVersion.String(),
Kind: "MutatingAdmissionPolicyBinding",
},
ObjectMeta: binding.ObjectMeta,
Spec: admissionregistrationv1beta1.MutatingAdmissionPolicyBindingSpec{
PolicyName: binding.Spec.PolicyName,
ParamRef: convertParamRef(binding.Spec.ParamRef),
MatchResources: convertMatchResourcesForBinding(binding.Spec.MatchResources),
},
}
}
return result
}
func convertParamRef(paramRef *admissionregistrationv1alpha1.ParamRef) *admissionregistrationv1beta1.ParamRef {
if paramRef == nil {
return nil
}
return &admissionregistrationv1beta1.ParamRef{
Name: paramRef.Name,
Namespace: paramRef.Namespace,
Selector: paramRef.Selector,
ParameterNotFoundAction: (*admissionregistrationv1beta1.ParameterNotFoundActionType)(paramRef.ParameterNotFoundAction),
}
}
func convertMatchResourcesForBinding(matchResources *admissionregistrationv1alpha1.MatchResources) *admissionregistrationv1beta1.MatchResources {
if matchResources == nil {
return nil
}
return &admissionregistrationv1beta1.MatchResources{
ResourceRules: convertResourceRules(matchResources.ResourceRules),
ExcludeResourceRules: convertResourceRules(matchResources.ExcludeResourceRules),
MatchPolicy: (*admissionregistrationv1beta1.MatchPolicyType)(matchResources.MatchPolicy),
}
}
func NewMutatingAdmissionPolicyWithBindings(pol *admissionregistrationv1beta1.MutatingAdmissionPolicy, bindings ...admissionregistrationv1beta1.MutatingAdmissionPolicyBinding) GenericPolicy {
return &genericPolicy{
Object: pol,
MutatingAdmissionPolicy: NewMutatingAdmissionPolicyData(pol, bindings...),
}
}
func NewValidatingPolicy(pol *policiesv1beta1.ValidatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
ValidatingPolicy: pol,
}
}
func NewNamespacedValidatingPolicy(pol *policiesv1beta1.NamespacedValidatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
NamespacedValidatingPolicy: pol,
}
}
func NewValidatingPolicyFromLike(pol policiesv1beta1.ValidatingPolicyLike) GenericPolicy {
switch typed := pol.(type) {
case *policiesv1beta1.ValidatingPolicy:
return NewValidatingPolicy(typed)
case *policiesv1beta1.NamespacedValidatingPolicy:
return NewNamespacedValidatingPolicy(typed)
default:
return nil
}
}
func NewImageValidatingPolicy(pol *policiesv1beta1.ImageValidatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
ImageValidatingPolicy: pol,
}
}
func NewNamespacedImageValidatingPolicy(pol *policiesv1beta1.NamespacedImageValidatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
NamespacedImageValidatingPolicy: pol,
}
}
func NewImageValidatingPolicyFromLike(pol policiesv1beta1.ImageValidatingPolicyLike) GenericPolicy {
switch typed := pol.(type) {
case *policiesv1beta1.ImageValidatingPolicy:
return NewImageValidatingPolicy(typed)
case *policiesv1beta1.NamespacedImageValidatingPolicy:
return NewNamespacedImageValidatingPolicy(typed)
default:
return nil
}
}
func NewMutatingPolicy(pol *policiesv1beta1.MutatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
MutatingPolicy: pol,
}
}
func NewNamespacedMutatingPolicy(pol *policiesv1beta1.NamespacedMutatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
NamespacedMutatingPolicy: pol,
}
}
func NewMutatingPolicyFromLike(pol policiesv1beta1.MutatingPolicyLike) GenericPolicy {
switch typed := pol.(type) {
case *policiesv1beta1.MutatingPolicy:
return NewMutatingPolicy(typed)
case *policiesv1beta1.NamespacedMutatingPolicy:
return NewNamespacedMutatingPolicy(typed)
default:
return nil
}
}
func NewGeneratingPolicy(pol *policiesv1beta1.GeneratingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
GeneratingPolicy: pol,
}
}
func NewNamespacedGeneratingPolicy(pol *policiesv1beta1.NamespacedGeneratingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
NamespacedGeneratingPolicy: pol,
}
}
func NewGeneratingPolicyFromLike(pol policiesv1beta1.GeneratingPolicyLike) GenericPolicy {
switch typed := pol.(type) {
case *policiesv1beta1.GeneratingPolicy:
return NewGeneratingPolicy(typed)
case *policiesv1beta1.NamespacedGeneratingPolicy:
return NewNamespacedGeneratingPolicy(typed)
default:
return nil
}
}
func NewDeletingPolicy(pol *policiesv1beta1.DeletingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
DeletingPolicy: pol,
}
}
func NewNamespacedDeletingPolicy(pol *policiesv1beta1.NamespacedDeletingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
DeletingPolicy: pol,
}
}
func NewDeletingPolicyFromLike(pol policiesv1beta1.DeletingPolicyLike) GenericPolicy {
switch typed := pol.(type) {
case *policiesv1beta1.DeletingPolicy:
return NewDeletingPolicy(typed)
case *policiesv1beta1.NamespacedDeletingPolicy:
return NewNamespacedDeletingPolicy(typed)
default:
return nil
}
}
package api
// PolicyResponse policy application response
type PolicyResponse struct {
// stats contains policy statistics
stats PolicyStats
// Rules contains policy rules responses
Rules []RuleResponse
// emitWarning controls if rule responses are returned in warning header
emitWarning bool
}
func (pr *PolicyResponse) Add(stats ExecutionStats, responses ...RuleResponse) {
for _, response := range responses {
pr.Rules = append(pr.Rules, response.WithStats(stats))
status := response.Status()
switch status {
case RuleStatusPass, RuleStatusFail:
pr.stats.rulesAppliedCount++
case RuleStatusError:
pr.stats.rulesErrorCount++
}
}
}
func NewPolicyResponse() PolicyResponse {
return PolicyResponse{}
}
func (pr *PolicyResponse) Stats() PolicyStats {
return pr.stats
}
func (pr *PolicyResponse) RulesAppliedCount() int {
return pr.stats.RulesAppliedCount()
}
func (pr *PolicyResponse) RulesErrorCount() int {
return pr.stats.RulesErrorCount()
}
package api
import (
"context"
"errors"
corev1 "k8s.io/api/core/v1"
)
// NamespacedResourceResolver is an abstract interface used to resolve namespaced resources
// Any implementation might exist, cache based, file based, client based etc...
type NamespacedResourceResolver[T any] interface {
// Get is used to resolve a resource given a namespace and name
Get(
ctx context.Context,
namespace string,
name string,
) (T, error)
}
// ConfigmapResolver is an abstract interface used to resolve configmaps
type ConfigmapResolver = NamespacedResourceResolver[*corev1.ConfigMap]
// namespacedResourceResolverChain represents a chain of NamespacedResourceResolver
type namespacedResourceResolverChain[T any] []NamespacedResourceResolver[T]
// NewNamespacedResourceResolver creates a NamespacedResourceResolver from a NamespacedResourceResolver chain
// It will try to resolve resources by iterating over individual resolvers until one finds the requested resource
func NewNamespacedResourceResolver[T any](resolvers ...NamespacedResourceResolver[T]) (NamespacedResourceResolver[T], error) {
if len(resolvers) == 0 {
return nil, errors.New("no resolvers")
}
for _, resolver := range resolvers {
if resolver == nil {
return nil, errors.New("at least one resolver is nil")
}
}
return namespacedResourceResolverChain[T](resolvers), nil
}
func (chain namespacedResourceResolverChain[T]) Get(ctx context.Context, namespace, name string) (T, error) {
var lastErr error
for _, resolver := range chain {
r, err := resolver.Get(ctx, namespace, name)
if err == nil {
return r, nil
}
lastErr = err
}
var notFound T
return notFound, lastErr
}
package api
// ResourceSpec resource action applied on
type ResourceSpec struct {
Kind string
APIVersion string
Namespace string
Name string
UID string
}
// String implements Stringer interface
func (rs ResourceSpec) String() string {
return rs.Kind + "/" + rs.Namespace + "/" + rs.Name
}
package api
import (
"fmt"
pssutils "github.com/kyverno/kyverno/pkg/pss/utils"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/pod-security-admission/api"
)
// PodSecurityChecks details about pod securty checks
type PodSecurityChecks struct {
// Level is the pod security level
Level api.Level
// Version is the pod security version
Version string
// Checks contains check result details
Checks []pssutils.PSSCheckResult
}
// SkipReason is the reason a rule was skipped. It is internal routing metadata
// and is not persisted to PolicyReport results.
type SkipReason string
const (
// SkipReasonMatchConditions indicates the rule was skipped because
// matchConditions evaluated to false. This mirrors admission-time behavior
// where the webhook is never called when matchConditions don't match.
SkipReasonMatchConditions SkipReason = "matchConditions"
)
// RuleResponse details for each rule application
type RuleResponse struct {
// name is the rule name specified in policy
name string
// ruleType is the rule type (Mutation,Generation,Validation) for Kyverno Policy
ruleType RuleType
// message is the message response from the rule application
message string
// status rule status
status RuleStatus
// stats contains rule statistics
stats ExecutionStats
// generatedResources is the list of resources generated by the generate rules of a policy
generatedResources []*unstructured.Unstructured
// patchedTarget is the patched resource for mutate.targets
patchedTarget *unstructured.Unstructured
// patchedTargetParentResourceGVR is the GVR of the parent resource of the PatchedTarget. This is only populated when PatchedTarget is a subresource.
patchedTargetParentResourceGVR metav1.GroupVersionResource
// patchedTargetSubresourceName is the name of the subresource which is patched, empty if the resource patched is not a subresource.
patchedTargetSubresourceName string
// podSecurityChecks contains pod security checks (only if this is a pod security rule)
podSecurityChecks *PodSecurityChecks
// exceptions are the exceptions applied (if any)
exceptions []GenericException
// vapbinding is the validatingadmissionpolicybinding (if any)
vapBinding *admissionregistrationv1.ValidatingAdmissionPolicyBinding
// mapbinding is the mutatingadmissionpolicybinding (if any)
mapBinding *admissionregistrationv1beta1.MutatingAdmissionPolicyBinding
// emitWarning enable passing rule message as warning to api server warning header
emitWarning bool
// skipReason is the internal reason a rule was skipped, not persisted to reports
skipReason SkipReason
// properties are the additional properties from the rule that will be added to the policy report result
properties map[string]string
// messageExpressionError contains an evaluation error for a CEL message expression, if any
messageExpressionError string
}
func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStatus, properties map[string]string) *RuleResponse {
emitWarn := false
if status == RuleStatusError || status == RuleStatusFail || status == RuleStatusWarn {
emitWarn = true
}
return &RuleResponse{
name: name,
ruleType: ruleType,
message: msg,
status: status,
emitWarning: emitWarn,
properties: properties,
}
}
func RuleError(name string, ruleType RuleType, msg string, err error, properties map[string]string) *RuleResponse {
if err != nil {
return NewRuleResponse(name, ruleType, fmt.Sprintf("%s: %s", msg, err.Error()), RuleStatusError, properties)
}
return NewRuleResponse(name, ruleType, msg, RuleStatusError, properties)
}
func RuleSkip(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusSkip, properties)
}
func RuleWarn(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusWarn, properties)
}
func RulePass(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusPass, properties)
}
func RuleFail(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusFail, properties)
}
func (r RuleResponse) WithExceptions(exceptions []GenericException) *RuleResponse {
r.exceptions = exceptions
return &r
}
func (r RuleResponse) WithVAPBinding(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) *RuleResponse {
r.vapBinding = binding
return &r
}
func (r RuleResponse) WithMAPBinding(binding *admissionregistrationv1beta1.MutatingAdmissionPolicyBinding) *RuleResponse {
r.mapBinding = binding
return &r
}
func (r RuleResponse) WithPodSecurityChecks(checks PodSecurityChecks) *RuleResponse {
r.podSecurityChecks = &checks
return &r
}
func (r RuleResponse) WithPatchedTarget(patchedTarget *unstructured.Unstructured, gvr metav1.GroupVersionResource, subresource string) *RuleResponse {
r.patchedTarget = patchedTarget
r.patchedTargetParentResourceGVR = gvr
r.patchedTargetSubresourceName = subresource
return &r
}
func (r RuleResponse) WithGeneratedResources(resource []*unstructured.Unstructured) *RuleResponse {
r.generatedResources = resource
return &r
}
func (r RuleResponse) WithStats(stats ExecutionStats) RuleResponse {
r.stats = stats
return r
}
func (r RuleResponse) WithEmitWarning(emitWarning bool) *RuleResponse {
r.emitWarning = emitWarning
return &r
}
func (r RuleResponse) WithProperties(m map[string]string) *RuleResponse {
r.properties = m
return &r
}
func (r *RuleResponse) Stats() ExecutionStats {
return r.stats
}
func (r *RuleResponse) Exceptions() []GenericException {
return r.exceptions
}
func (r *RuleResponse) ValidatingAdmissionPolicyBinding() *admissionregistrationv1.ValidatingAdmissionPolicyBinding {
return r.vapBinding
}
func (r *RuleResponse) MutatingAdmissionPolicyBinding() *admissionregistrationv1beta1.MutatingAdmissionPolicyBinding {
return r.mapBinding
}
func (r *RuleResponse) IsException() bool {
return len(r.exceptions) > 0
}
func (r *RuleResponse) PodSecurityChecks() *PodSecurityChecks {
return r.podSecurityChecks
}
func (r *RuleResponse) PatchedTarget() (*unstructured.Unstructured, metav1.GroupVersionResource, string) {
return r.patchedTarget, r.patchedTargetParentResourceGVR, r.patchedTargetSubresourceName
}
func (r *RuleResponse) GeneratedResources() []*unstructured.Unstructured {
return r.generatedResources
}
func (r *RuleResponse) Message() string {
return r.message
}
func (r *RuleResponse) Name() string {
return r.name
}
func (r *RuleResponse) RuleType() RuleType {
return r.ruleType
}
func (r *RuleResponse) Status() RuleStatus {
return r.status
}
func (r *RuleResponse) EmitWarning() bool {
return r.emitWarning
}
func (r *RuleResponse) Properties() map[string]string {
return r.properties
}
// HasStatus checks if rule status is in a given list
func (r *RuleResponse) HasStatus(status ...RuleStatus) bool {
for _, s := range status {
if r.status == s {
return true
}
}
return false
}
func (r RuleResponse) WithSkipReason(reason SkipReason) *RuleResponse {
r.skipReason = reason
return &r
}
func (r *RuleResponse) SkipReason() SkipReason {
return r.skipReason
}
// WithMessageExpressionError sets the messageExpressionError on the RuleResponse.
func (r RuleResponse) WithMessageExpressionError(err error) *RuleResponse {
if err != nil {
r.messageExpressionError = err.Error()
}
return &r
}
// MessageExpressionError returns the messageExpressionError.
func (r *RuleResponse) MessageExpressionError() string {
return r.messageExpressionError
}
// String implements Stringer interface
func (r *RuleResponse) String() string {
return fmt.Sprintf("rule %s (%s): %v", r.name, r.ruleType, r.message)
}
package api
import (
"time"
)
// ExecutionStats stores the statistics for the single policy/rule application
type ExecutionStats struct {
// processingTime is the time required to apply the policy/rule on the resource
processingTime time.Duration
// timestamp of the instant the policy/rule got triggered
timestamp time.Time
}
func NewExecutionStats(startTime, endTime time.Time) ExecutionStats {
return ExecutionStats{
timestamp: startTime,
processingTime: endTime.Sub(startTime),
}
}
func (s ExecutionStats) Time() time.Time {
return s.timestamp
}
func (s ExecutionStats) Timestamp() int64 {
return s.timestamp.Unix()
}
func (s ExecutionStats) ProcessingTime() time.Duration {
return s.processingTime
}
// PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// rulesAppliedCount is the count of rules that were applied successfully
rulesAppliedCount int
// rulesErrorCount is the count of rules that with execution errors
rulesErrorCount int
}
func (ps *PolicyStats) RulesAppliedCount() int {
return ps.rulesAppliedCount
}
func (ps *PolicyStats) RulesErrorCount() int {
return ps.rulesErrorCount
}
package api
import (
"fmt"
"strconv"
"strings"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/logging"
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type ImageInfo struct {
imageutils.ImageInfo
// Pointer is the path to the image object in the resource
Pointer string `json:"jsonPointer"`
ImagePullSecrets []string `json:"imagePullSecrets"`
}
var (
podExtractors = BuildStandardExtractors("spec")
podControllerExtractors = BuildStandardExtractors("spec", "template", "spec")
cronjobControllerExtractors = BuildStandardExtractors("spec", "jobTemplate", "spec", "template", "spec")
registeredExtractors = map[string][]imageExtractor{
"Pod": podExtractors,
"DaemonSet": podControllerExtractors,
"Deployment": podControllerExtractors,
"ReplicaSet": podControllerExtractors,
"ReplicationController": podControllerExtractors,
"StatefulSet": podControllerExtractors,
"CronJob": cronjobControllerExtractors,
"Job": podControllerExtractors,
}
)
type imageExtractor struct {
Fields []string
Key string
Value string
Name string
}
func (i *imageExtractor) ExtractFromResource(resource interface{}, cfg config.Configuration) (map[string]ImageInfo, error) {
imageInfo := map[string]ImageInfo{}
if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, &imageInfo, cfg, []string{}); err != nil {
return nil, err
}
return imageInfo, nil
}
func extract(
obj interface{},
path []string,
keyPath string,
valuePath string,
fields []string,
imageInfos *map[string]ImageInfo,
cfg config.Configuration,
pullSecrets []string,
) error {
if obj == nil {
return nil
}
if len(fields) > 0 && fields[0] == "*" {
switch typedObj := obj.(type) {
case []interface{}:
for i, v := range typedObj {
switch v.(type) {
case map[string]interface{}:
if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], imageInfos, cfg, pullSecrets); err != nil {
return err
}
case []interface{}:
if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields, imageInfos, cfg, pullSecrets); err != nil {
return err
}
}
}
return nil
case map[string]interface{}:
for i, v := range typedObj {
switch v.(type) {
case map[string]interface{}, []interface{}:
if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], imageInfos, cfg, pullSecrets); err != nil {
return err
}
}
}
return nil
}
}
output, ok := obj.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid image config")
}
if secrets, ok := output["imagePullSecrets"].([]interface{}); ok {
for _, secret := range secrets {
if secretMap, ok := secret.(map[string]interface{}); ok {
if secretName, ok := secretMap["name"].(string); ok {
pullSecrets = append(pullSecrets, secretName)
}
}
}
}
if len(fields) == 0 {
pointer := fmt.Sprintf("/%s/%s", strings.Join(path, "/"), valuePath)
key := pointer
if keyPath != "" {
key, ok = output[keyPath].(string)
if !ok {
return fmt.Errorf("invalid key")
}
}
value, ok := output[valuePath].(string)
if !ok || strings.TrimSpace(value) == "" {
// the image may not be present
logging.V(4).Info("image information is not present", "pointer", pointer)
return nil
}
if imageInfo, err := imageutils.GetImageInfo(value, cfg); err != nil {
return fmt.Errorf("invalid image '%s' (%s)", value, err.Error())
} else {
logging.V(4).Info("image information present", "pointer", pointer, "keyPath", keyPath, "output", output, "secrets", pullSecrets)
(*imageInfos)[key] = ImageInfo{*imageInfo, pointer, pullSecrets}
}
return nil
}
currentPath := fields[0]
return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], imageInfos, cfg, pullSecrets)
}
func BuildStandardExtractors(tags ...string) []imageExtractor {
extractors := make([]imageExtractor, 0, 3)
for _, tag := range []string{"initContainers", "containers", "ephemeralContainers"} {
t := make([]string, 0, len(tags)+1+1)
t = append(t, tags...)
t = append(t, tag)
t = append(t, "*")
extractors = append(extractors, imageExtractor{Fields: t, Key: "name", Value: "image", Name: tag})
}
return extractors
}
func lookupImageExtractor(kind string) []imageExtractor {
return registeredExtractors[kind]
}
func ExtractImagesFromResource(resource unstructured.Unstructured, cfg config.Configuration) (map[string]map[string]ImageInfo, error) {
infos := map[string]map[string]ImageInfo{}
// Extract images using existing logic (imagePullSecrets are now part of ImageInfo struct)
extractors := lookupImageExtractor(resource.GetKind())
if extractors != nil && len(extractors) == 0 {
return nil, fmt.Errorf("no extractors found for %s", resource.GetKind())
}
for _, extractor := range extractors {
if infoMap, err := extractor.ExtractFromResource(resource.Object, cfg); err != nil {
return nil, err
} else if len(infoMap) > 0 {
infos[extractor.Name] = infoMap
}
}
return infos, nil
}