1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 10:28:36 +00:00

feat: support validatingadmissionpolicybindings in CLI apply command (#9468)

* feat: support validatingadmissionpolicybindings in CLI apply command

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix linter issue

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2024-01-23 13:47:38 +02:00 committed by GitHub
parent 91a7a9d7e5
commit d47684c0d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 516 additions and 167 deletions

View file

@ -148,11 +148,11 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err
}
rc, resources1, skipInvalidPolicies, responses1, policies, validatingAdmissionPolicies, err := c.loadPolicies(skipInvalidPolicies)
rc, resources1, skipInvalidPolicies, responses1, policies, vaps, vapBindings, err := c.loadPolicies(skipInvalidPolicies)
if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err
}
resources, err := c.loadResources(out, policies, validatingAdmissionPolicies, dClient)
resources, err := c.loadResources(out, policies, vaps, dClient)
if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err
}
@ -161,7 +161,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
for _, policy := range policies {
policyRulesCount += len(autogen.ComputeRules(policy))
}
policyRulesCount += len(validatingAdmissionPolicies)
policyRulesCount += len(vaps)
fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources))
}
@ -179,7 +179,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err
}
responses2, err := c.applyValidatingAdmissionPolicytoResource(variables, validatingAdmissionPolicies, resources1, rc, dClient, &skipInvalidPolicies)
responses2, err := c.applyValidatingAdmissionPolicytoResource(vaps, vapBindings, resources1, rc, dClient)
if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err
}
@ -198,20 +198,21 @@ func (c *ApplyCommandConfig) getMutateLogPathIsDir(skipInvalidPolicies SkippedIn
}
func (c *ApplyCommandConfig) applyValidatingAdmissionPolicytoResource(
variables *variables.Variables,
validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy,
vaps []v1alpha1.ValidatingAdmissionPolicy,
vapBindings []v1alpha1.ValidatingAdmissionPolicyBinding,
resources []*unstructured.Unstructured,
rc *processor.ResultCounts,
dClient dclient.Interface,
skipInvalidPolicies *SkippedInvalidPolicies,
) ([]engineapi.EngineResponse, error) {
var responses []engineapi.EngineResponse
for _, resource := range resources {
processor := processor.ValidatingAdmissionPolicyProcessor{
Policies: validatingAdmissionPolicies,
Policies: vaps,
Bindings: vapBindings,
Resource: resource,
PolicyReport: c.PolicyReport,
Rc: rc,
Client: dClient,
}
ers, err := processor.ApplyPolicyOnResource()
if err != nil {
@ -283,18 +284,19 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
return &rc, resources, responses, nil
}
func (c *ApplyCommandConfig) loadResources(out io.Writer, policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, error) {
resources, err := common.GetResourceAccordingToResourcePath(out, nil, c.ResourcePaths, c.Cluster, policies, validatingAdmissionPolicies, dClient, c.Namespace, c.PolicyReport, "")
func (c *ApplyCommandConfig) loadResources(out io.Writer, policies []kyvernov1.PolicyInterface, vap []v1alpha1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, error) {
resources, err := common.GetResourceAccordingToResourcePath(out, nil, c.ResourcePaths, c.Cluster, policies, vap, dClient, c.Namespace, c.PolicyReport, "")
if err != nil {
return resources, fmt.Errorf("failed to load resources (%w)", err)
}
return resources, nil
}
func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, []kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, []kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
// load policies
var policies []kyvernov1.PolicyInterface
var validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy
var vaps []v1alpha1.ValidatingAdmissionPolicy
var vapBindings []v1alpha1.ValidatingAdmissionPolicyBinding
for _, path := range c.PolicyPaths {
isGit := source.IsGit(path)
@ -302,13 +304,13 @@ func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPoli
if isGit {
gitSourceURL, err := url.Parse(path)
if err != nil {
return nil, nil, skipInvalidPolicies, nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err)
return nil, nil, skipInvalidPolicies, nil, nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err)
}
pathElems := strings.Split(gitSourceURL.Path[1:], "/")
if len(pathElems) <= 1 {
err := fmt.Errorf("invalid URL path %s - expected https://<any_git_source_domain>/:owner/:repository/:branch (without --git-branch flag) OR https://<any_git_source_domain>/:owner/:repository/:directory (with --git-branch flag)", gitSourceURL.Path)
return nil, nil, skipInvalidPolicies, nil, nil, nil, fmt.Errorf("failed to parse URL (%w)", err)
return nil, nil, skipInvalidPolicies, nil, nil, nil, nil, fmt.Errorf("failed to parse URL (%w)", err)
}
gitSourceURL.Path = strings.Join([]string{pathElems[0], pathElems[1]}, "/")
repoURL := gitSourceURL.String()
@ -317,31 +319,33 @@ func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPoli
fs := memfs.New()
if _, err := gitutils.Clone(repoURL, fs, c.GitBranch); err != nil {
log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", err)
return nil, nil, skipInvalidPolicies, nil, nil, nil, fmt.Errorf("failed to clone repository (%w)", err)
return nil, nil, skipInvalidPolicies, nil, nil, nil, nil, fmt.Errorf("failed to clone repository (%w)", err)
}
policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls)
if err != nil {
return nil, nil, skipInvalidPolicies, nil, nil, nil, fmt.Errorf("failed to list YAMLs in repository (%w)", err)
return nil, nil, skipInvalidPolicies, nil, nil, nil, nil, fmt.Errorf("failed to list YAMLs in repository (%w)", err)
}
for _, policyYaml := range policyYamls {
policiesFromFile, admissionPoliciesFromFile, err := policy.Load(fs, "", policyYaml)
policiesFromFile, vapsFromFile, vapBindingsFromFile, err := policy.Load(fs, "", policyYaml)
if err != nil {
continue
}
policies = append(policies, policiesFromFile...)
validatingAdmissionPolicies = append(validatingAdmissionPolicies, admissionPoliciesFromFile...)
vaps = append(vaps, vapsFromFile...)
vapBindings = append(vapBindings, vapBindingsFromFile...)
}
} else {
policiesFromFile, admissionPoliciesFromFile, err := policy.Load(nil, "", path)
policiesFromFile, vapsFromFile, vapBindingsFromFile, err := policy.Load(nil, "", path)
if err != nil {
return nil, nil, skipInvalidPolicies, nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err)
return nil, nil, skipInvalidPolicies, nil, nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err)
}
policies = append(policies, policiesFromFile...)
validatingAdmissionPolicies = append(validatingAdmissionPolicies, admissionPoliciesFromFile...)
vaps = append(vaps, vapsFromFile...)
vapBindings = append(vapBindings, vapBindingsFromFile...)
}
}
return nil, nil, skipInvalidPolicies, nil, policies, validatingAdmissionPolicies, nil
return nil, nil, skipInvalidPolicies, nil, policies, vaps, vapBindings, nil
}
func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, dclient.Interface) {

View file

@ -61,7 +61,7 @@ func (o options) execute(out io.Writer, dirs ...string) error {
}
func (o options) processFile(out io.Writer, path string) {
policies, vaps, err := policy.LoadWithLoader(policy.KubectlValidateLoader, nil, "", path)
policies, vaps, vapBindings, err := policy.LoadWithLoader(policy.KubectlValidateLoader, nil, "", path)
if err != nil {
return
}
@ -169,6 +169,16 @@ func (o options) processFile(out io.Writer, path string) {
yamlBytes = append(yamlBytes, []byte("---\n")...)
yamlBytes = append(yamlBytes, finalBytes...)
}
for _, vapBinding := range vapBindings {
finalBytes, err := yaml.Marshal(vapBinding)
if err != nil {
fmt.Fprintf(out, " ERROR: converting to yaml: %s", err)
fmt.Fprintln(out)
return
}
yamlBytes = append(yamlBytes, []byte("---\n")...)
yamlBytes = append(yamlBytes, finalBytes...)
}
if err := os.WriteFile(path, yamlBytes, os.ModePerm); err != nil {
fmt.Fprintf(out, " ERROR: saving file (%s): %s", path, err)
fmt.Fprintln(out)

View file

@ -86,7 +86,7 @@ func (o options) execute(ctx context.Context, dir string, keychain authn.Keychai
if err != nil {
return fmt.Errorf("reading layer blob: %v", err)
}
policies, _, err := yamlutils.GetPolicy(layerBytes)
policies, _, _, err := yamlutils.GetPolicy(layerBytes)
if err != nil {
return fmt.Errorf("unmarshaling layer blob: %v", err)
}

View file

@ -35,7 +35,7 @@ func (o options) validate(policy string) error {
}
func (o options) execute(ctx context.Context, dir string, keychain authn.Keychain) error {
policies, _, err := policy.Load(nil, "", dir)
policies, _, _, err := policy.Load(nil, "", dir)
if err != nil {
return fmt.Errorf("unable to read policy file or directory %s (%w)", dir, err)
}

View file

@ -57,7 +57,7 @@ func runTest(out io.Writer, testCase test.TestCase, registryAccess bool, auditWa
// policies
fmt.Fprintln(out, " Loading policies", "...")
policyFullPath := path.GetFullPaths(testCase.Test.Policies, testDir, isGit)
policies, validatingAdmissionPolicies, err := policy.Load(testCase.Fs, testDir, policyFullPath...)
policies, validatingAdmissionPolicies, _, err := policy.Load(testCase.Fs, testDir, policyFullPath...)
if err != nil {
return nil, fmt.Errorf("Error: failed to load policies (%s)", err)
}

View file

@ -35,11 +35,13 @@ var (
policyV2 = schema.GroupVersion(kyvernov2beta1.GroupVersion).WithKind("Policy")
clusterPolicyV1 = schema.GroupVersion(kyvernov1.GroupVersion).WithKind("ClusterPolicy")
clusterPolicyV2 = schema.GroupVersion(kyvernov2beta1.GroupVersion).WithKind("ClusterPolicy")
vapV1Alpha1 = v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicy")
vapV1alpha1 = v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicy")
vapV1Beta1 = v1beta1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicy")
vapBidningV1alpha1 = v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicyBinding")
vapBidningV1beta1 = v1beta1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicyBinding")
LegacyLoader = yamlutils.GetPolicy
KubectlValidateLoader = kubectlValidateLoader
defaultLoader = func(bytes []byte) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
defaultLoader = func(bytes []byte) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
if experimental.UseKubectlValidate() {
return KubectlValidateLoader(bytes)
} else {
@ -48,159 +50,174 @@ var (
}
)
type loader = func([]byte) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error)
type loader = func([]byte) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error)
func Load(fs billy.Filesystem, resourcePath string, paths ...string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func Load(fs billy.Filesystem, resourcePath string, paths ...string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
return LoadWithLoader(nil, fs, resourcePath, paths...)
}
func LoadWithLoader(loader loader, fs billy.Filesystem, resourcePath string, paths ...string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func LoadWithLoader(loader loader, fs billy.Filesystem, resourcePath string, paths ...string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
if loader == nil {
loader = defaultLoader
}
var pols []kyvernov1.PolicyInterface
var vaps []v1alpha1.ValidatingAdmissionPolicy
var vapBindings []v1alpha1.ValidatingAdmissionPolicyBinding
for _, path := range paths {
if source.IsStdin(path) {
p, v, err := stdinLoad(loader)
p, v, b, err := stdinLoad(loader)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
pols = append(pols, p...)
vaps = append(vaps, v...)
vapBindings = append(vapBindings, b...)
} else if fs != nil {
p, v, err := gitLoad(loader, fs, filepath.Join(resourcePath, path))
p, v, b, err := gitLoad(loader, fs, filepath.Join(resourcePath, path))
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
pols = append(pols, p...)
vaps = append(vaps, v...)
vapBindings = append(vapBindings, b...)
} else if source.IsHttp(path) {
p, v, err := httpLoad(loader, path)
p, v, b, err := httpLoad(loader, path)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
pols = append(pols, p...)
vaps = append(vaps, v...)
vapBindings = append(vapBindings, b...)
} else {
p, v, err := fsLoad(loader, path)
p, v, b, err := fsLoad(loader, path)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
pols = append(pols, p...)
vaps = append(vaps, v...)
vapBindings = append(vapBindings, b...)
}
}
return pols, vaps, nil
return pols, vaps, vapBindings, nil
}
func kubectlValidateLoader(content []byte) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func kubectlValidateLoader(content []byte) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
documents, err := extyaml.SplitDocuments(content)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
var policies []kyvernov1.PolicyInterface
var vaps []v1alpha1.ValidatingAdmissionPolicy
var vapBindings []v1alpha1.ValidatingAdmissionPolicyBinding
for _, document := range documents {
gvk, untyped, err := factory.Load(document)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
switch gvk {
case policyV1, policyV2:
typed, err := convert.To[kyvernov1.Policy](untyped)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
policies = append(policies, typed)
case clusterPolicyV1, clusterPolicyV2:
typed, err := convert.To[kyvernov1.ClusterPolicy](untyped)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
policies = append(policies, typed)
case vapV1Alpha1, vapV1Beta1:
case vapV1alpha1, vapV1Beta1:
typed, err := convert.To[v1alpha1.ValidatingAdmissionPolicy](untyped)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
vaps = append(vaps, *typed)
case vapBidningV1alpha1, vapBidningV1beta1:
typed, err := convert.To[v1alpha1.ValidatingAdmissionPolicyBinding](untyped)
if err != nil {
return nil, nil, nil, err
}
vapBindings = append(vapBindings, *typed)
default:
return nil, nil, fmt.Errorf("policy type not supported %s", gvk)
return nil, nil, nil, fmt.Errorf("policy type not supported %s", gvk)
}
}
return policies, vaps, nil
return policies, vaps, vapBindings, nil
}
func fsLoad(loader loader, path string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func fsLoad(loader loader, path string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
var pols []kyvernov1.PolicyInterface
var vaps []v1alpha1.ValidatingAdmissionPolicy
var vapBindings []v1alpha1.ValidatingAdmissionPolicyBinding
fi, err := os.Stat(filepath.Clean(path))
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if fi.IsDir() {
files, err := os.ReadDir(path)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
for _, file := range files {
p, v, err := fsLoad(loader, filepath.Join(path, file.Name()))
p, v, b, err := fsLoad(loader, filepath.Join(path, file.Name()))
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
pols = append(pols, p...)
vaps = append(vaps, v...)
vapBindings = append(vapBindings, b...)
}
} else if git.IsYaml(fi) {
fileBytes, err := os.ReadFile(filepath.Clean(path)) // #nosec G304
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
p, v, err := loader(fileBytes)
p, v, b, err := loader(fileBytes)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
pols = append(pols, p...)
vaps = append(vaps, v...)
vapBindings = append(vapBindings, b...)
}
return pols, vaps, nil
return pols, vaps, vapBindings, nil
}
func httpLoad(loader loader, path string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func httpLoad(loader loader, path string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
// We accept here that a random URL might be called based on user provided input.
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, path, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
return nil, nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
return nil, nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
return nil, nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
}
fileBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
return nil, nil, nil, fmt.Errorf("failed to process %v: %v", path, err)
}
return loader(fileBytes)
}
func gitLoad(loader loader, fs billy.Filesystem, path string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func gitLoad(loader loader, fs billy.Filesystem, path string) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
file, err := fs.Open(path)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
fileBytes, err := io.ReadAll(file)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
return loader(fileBytes)
}
func stdinLoad(loader loader) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func stdinLoad(loader loader) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
policyStr := ""
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {

View file

@ -31,7 +31,7 @@ func TestLoad(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := Load(tt.fs, tt.resourcePath, tt.paths...)
_, _, _, err := Load(tt.fs, tt.resourcePath, tt.paths...)
if (err != nil) != tt.wantErr {
t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr)
return
@ -87,7 +87,7 @@ func TestLoadWithKubectlValidate(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
policies, vaps, err := LoadWithLoader(KubectlValidateLoader, tt.fs, tt.resourcePath, tt.paths...)
policies, vaps, _, err := LoadWithLoader(KubectlValidateLoader, tt.fs, tt.resourcePath, tt.paths...)
if (err != nil) != tt.wantErr {
t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr)
return

View file

@ -11,7 +11,7 @@ import (
func TestExtractVariables(t *testing.T) {
loadPolicy := func(path string) kyvernov1.PolicyInterface {
t.Helper()
policies, _, err := Load(nil, "", path)
policies, _, _, err := Load(nil, "", path)
assert.NoError(t, err)
assert.Equal(t, len(policies), 1)
return policies[0]

View file

@ -105,7 +105,7 @@ func Test_NamespaceSelector(t *testing.T) {
}
rc := &ResultCounts{}
for _, tc := range testcases {
policyArray, _, _ := yamlutils.GetPolicy(tc.policy)
policyArray, _, _, _ := yamlutils.GetPolicy(tc.policy)
resourceArray, _ := resource.GetUnstructuredResources(tc.resource)
processor := PolicyProcessor{
Store: &store.Store{},

View file

@ -1,6 +1,7 @@
package processor
import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
"k8s.io/api/admissionregistration/v1alpha1"
@ -9,15 +10,23 @@ import (
type ValidatingAdmissionPolicyProcessor struct {
Policies []v1alpha1.ValidatingAdmissionPolicy
Bindings []v1alpha1.ValidatingAdmissionPolicyBinding
Resource *unstructured.Unstructured
PolicyReport bool
Rc *ResultCounts
Client dclient.Interface
}
func (p *ValidatingAdmissionPolicyProcessor) ApplyPolicyOnResource() ([]engineapi.EngineResponse, error) {
var responses []engineapi.EngineResponse
for _, policy := range p.Policies {
response := validatingadmissionpolicy.Validate(policy, *p.Resource)
policyData := validatingadmissionpolicy.NewPolicyData(policy)
for _, binding := range p.Bindings {
if binding.Spec.PolicyName == policy.Name {
policyData.AddBinding(binding)
}
}
response, _ := validatingadmissionpolicy.Validate(policyData, *p.Resource, p.Client)
responses = append(responses, response)
p.Rc.addValidatingAdmissionResponse(policy, response)
}

View file

@ -15,7 +15,7 @@ import (
)
func TestComputeClusterPolicyReports(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
policies, _, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
@ -49,7 +49,7 @@ func TestComputeClusterPolicyReports(t *testing.T) {
}
func TestComputePolicyReports(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/pol-pod-requirements.yaml")
policies, _, _, err := policy.Load(nil, "", "../_testdata/policies/pol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
@ -84,7 +84,7 @@ func TestComputePolicyReports(t *testing.T) {
}
func TestComputePolicyReportResultsPerPolicyOld(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
policies, _, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
@ -162,7 +162,7 @@ func TestMergeClusterReport(t *testing.T) {
}
func TestComputePolicyReportResult(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
policies, _, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]

View file

@ -569,7 +569,7 @@ kA==
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
policies, _, err := yamlutils.GetPolicy([]byte(test.policy))
policies, _, _, err := yamlutils.GetPolicy([]byte(test.policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))
rules := computeRules(policies[0])
@ -580,7 +580,7 @@ kA==
func Test_PodSecurityWithNoExceptions(t *testing.T) {
policy := []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"pod-security"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"restricted","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"validate":{"podSecurity":{"level":"restricted","version":"v1.24"}}}]}}`)
policies, _, err := yamlutils.GetPolicy([]byte(policy))
policies, _, _, err := yamlutils.GetPolicy([]byte(policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))
@ -628,7 +628,7 @@ func Test_ValidateWithCELExpressions(t *testing.T) {
}
}
`)
policies, _, err := yamlutils.GetPolicy([]byte(policy))
policies, _, _, err := yamlutils.GetPolicy([]byte(policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))

View file

@ -361,7 +361,7 @@ func (c *controller) reconcileReport(
}
}
if full || reevaluate || actual[reportutils.PolicyLabel(policy)] != policy.GetResourceVersion() {
scanner := utils.NewScanner(logger, c.engine, c.config, c.jp)
scanner := utils.NewScanner(logger, c.engine, c.config, c.jp, c.client)
for _, result := range scanner.ScanResource(ctx, *target, nsLabels, policy) {
if result.Error != nil {
return result.Error

View file

@ -6,6 +6,7 @@ import (
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -20,6 +21,7 @@ type scanner struct {
engine engineapi.Engine
config config.Configuration
jp jmespath.Interface
client dclient.Interface
}
type ScanResult struct {
@ -36,12 +38,14 @@ func NewScanner(
engine engineapi.Engine,
config config.Configuration,
jp jmespath.Interface,
client dclient.Interface,
) Scanner {
return &scanner{
logger: logger,
engine: engine,
config: config,
jp: jp,
client: client,
}
}
@ -74,7 +78,11 @@ func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstru
}
} else {
pol := policy.AsValidatingAdmissionPolicy()
res := validatingadmissionpolicy.Validate(*pol, resource)
policyData := validatingadmissionpolicy.NewPolicyData(*pol)
res, err := validatingadmissionpolicy.Validate(policyData, resource, s.client)
if err != nil {
errors = append(errors, err)
}
response = &res
}
results[&policies[i]] = ScanResult{response, multierr.Combine(errors...)}

View file

@ -477,7 +477,7 @@ func Test_Can_Generate_ValidatingAdmissionPolicy(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
policies, _, err := yamlutils.GetPolicy([]byte(test.policy))
policies, _, _, err := yamlutils.GetPolicy([]byte(test.policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))
out, _ := canGenerateVAP(policies[0].GetSpec())

View file

@ -15,58 +15,71 @@ import (
)
// GetPolicy extracts policies from YAML bytes
func GetPolicy(bytes []byte) (policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, err error) {
func GetPolicy(bytes []byte) (policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, validatingAdmissionPolicyBindings []v1alpha1.ValidatingAdmissionPolicyBinding, err error) {
documents, err := extyaml.SplitDocuments(bytes)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
for _, thisPolicyBytes := range documents {
policyBytes, err := yaml.ToJSON(thisPolicyBytes)
if err != nil {
return nil, nil, fmt.Errorf("failed to convert to JSON: %v", err)
return nil, nil, nil, fmt.Errorf("failed to convert to JSON: %v", err)
}
us := &unstructured.Unstructured{}
if err := json.Unmarshal(policyBytes, us); err != nil {
return nil, nil, fmt.Errorf("failed to decode policy: %v", err)
return nil, nil, nil, fmt.Errorf("failed to decode policy: %v", err)
}
if us.IsList() {
list, err := us.ToList()
if err != nil {
return nil, nil, fmt.Errorf("failed to decode policy list: %v", err)
return nil, nil, nil, fmt.Errorf("failed to decode policy list: %v", err)
}
for i := range list.Items {
item := list.Items[i]
if policies, validatingAdmissionPolicies, err = addPolicy(policies, validatingAdmissionPolicies, &item); err != nil {
return nil, nil, err
if policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, err = addPolicy(policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, &item); err != nil {
return nil, nil, nil, err
}
}
} else {
if policies, validatingAdmissionPolicies, err = addPolicy(policies, validatingAdmissionPolicies, us); err != nil {
return nil, nil, err
if policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, err = addPolicy(policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, us); err != nil {
return nil, nil, nil, err
}
}
}
return policies, validatingAdmissionPolicies, nil
return policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, err
}
func addPolicy(policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, us *unstructured.Unstructured) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
func addPolicy(policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, validatingAdmissionPolicyBindings []v1alpha1.ValidatingAdmissionPolicyBinding, us *unstructured.Unstructured) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, []v1alpha1.ValidatingAdmissionPolicyBinding, error) {
kind := us.GetKind()
if strings.Compare(kind, "ValidatingAdmissionPolicy") == 0 {
validatingAdmissionPolicy := v1alpha1.ValidatingAdmissionPolicy{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructuredWithValidation(us.Object, &validatingAdmissionPolicy, true); err != nil {
return policies, nil, fmt.Errorf("failed to decode policy: %v", err)
return policies, nil, validatingAdmissionPolicyBindings, fmt.Errorf("failed to decode policy: %v", err)
}
if validatingAdmissionPolicy.Kind == "" {
log.V(3).Info("skipping file as ValidatingAdmissionPolicy.Kind not found")
return policies, validatingAdmissionPolicies, nil
return policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, nil
}
validatingAdmissionPolicies = append(validatingAdmissionPolicies, validatingAdmissionPolicy)
} else if strings.Compare(kind, "ValidatingAdmissionPolicyBinding") == 0 {
validatingAdmissionPolicyBinding := v1alpha1.ValidatingAdmissionPolicyBinding{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructuredWithValidation(us.Object, &validatingAdmissionPolicyBinding, true); err != nil {
return policies, validatingAdmissionPolicies, nil, fmt.Errorf("failed to decode policy: %v", err)
}
if validatingAdmissionPolicyBinding.Kind == "" {
log.V(3).Info("skipping file as ValidatingAdmissionPolicyBinding.Kind not found")
return policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, nil
}
validatingAdmissionPolicyBindings = append(validatingAdmissionPolicyBindings, validatingAdmissionPolicyBinding)
} else {
var policy kyvernov1.PolicyInterface
if us.GetKind() == "ClusterPolicy" {
@ -74,19 +87,19 @@ func addPolicy(policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies
} else if us.GetKind() == "Policy" {
policy = &kyvernov1.Policy{}
} else {
return policies, validatingAdmissionPolicies, nil
return policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, nil
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructuredWithValidation(us.Object, policy, true); err != nil {
return nil, validatingAdmissionPolicies, fmt.Errorf("failed to decode policy: %v", err)
return nil, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, fmt.Errorf("failed to decode policy: %v", err)
}
if policy.GetKind() == "" {
log.V(3).Info("skipping file as policy.TypeMeta.Kind not found")
return policies, validatingAdmissionPolicies, nil
return policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, nil
}
if policy.GetKind() != "ClusterPolicy" && policy.GetKind() != "Policy" {
return nil, validatingAdmissionPolicies, fmt.Errorf("resource %s/%s is not a Policy or a ClusterPolicy", policy.GetKind(), policy.GetName())
return nil, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, fmt.Errorf("resource %s/%s is not a Policy or a ClusterPolicy", policy.GetKind(), policy.GetName())
}
if policy.GetKind() == "Policy" {
@ -99,5 +112,5 @@ func addPolicy(policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies
policies = append(policies, policy)
}
return policies, validatingAdmissionPolicies, nil
return policies, validatingAdmissionPolicies, validatingAdmissionPolicyBindings, nil
}

View file

@ -15,11 +15,12 @@ func TestGetPolicy(t *testing.T) {
namespace string
}
tests := []struct {
name string
args args
wantPolicies []policy
validatingAdmissionPolicies []policy
wantErr bool
name string
args args
wantPolicies []policy
vaps []policy
vapBindings []policy
wantErr bool
}{{
name: "policy",
args: args{
@ -317,7 +318,7 @@ spec:
validations:
- expression: "object.spec.replicas <= 5"
`),
}, validatingAdmissionPolicies: []policy{
}, vaps: []policy{
{"ValidatingAdmissionPolicy", ""},
},
wantErr: false,
@ -371,7 +372,7 @@ spec:
}, wantPolicies: []policy{
{"Policy", "ns-1"},
},
validatingAdmissionPolicies: []policy{
vaps: []policy{
{"ValidatingAdmissionPolicy", ""},
},
wantErr: false,
@ -424,14 +425,71 @@ spec:
}, wantPolicies: []policy{
{"ClusterPolicy", ""},
},
validatingAdmissionPolicies: []policy{
vaps: []policy{
{"ValidatingAdmissionPolicy", ""},
},
wantErr: false,
}, {
name: "ValidatingAdmissionPolicyBinding",
args: args{
[]byte(`
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-test.example.com"
spec:
policyName: "demo-policy.example.com"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: test
`),
}, vapBindings: []policy{
{"ValidatingAdmissionPolicyBinding", ""},
},
wantErr: false,
}, {
name: "ValidatingAdmissionPolicy and its binding",
args: args{
[]byte(`
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
---
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-test.example.com"
spec:
policyName: "demo-policy.example.com"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: test
`),
}, vaps: []policy{
{"ValidatingAdmissionPolicy", ""},
}, vapBindings: []policy{
{"ValidatingAdmissionPolicyBinding", ""},
},
wantErr: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPolicies, gotValidatingAdmissionPolicies, err := GetPolicy(tt.args.bytes)
gotPolicies, gotValidatingAdmissionPolicies, gotBindings, err := GetPolicy(tt.args.bytes)
if tt.wantErr {
assert.Error(t, err)
} else {
@ -443,12 +501,17 @@ spec:
}
}
if assert.Equal(t, len(tt.validatingAdmissionPolicies), len(gotValidatingAdmissionPolicies)) {
for i := range tt.validatingAdmissionPolicies {
assert.Equal(t, tt.validatingAdmissionPolicies[i].kind, gotValidatingAdmissionPolicies[i].Kind)
if assert.Equal(t, len(tt.vaps), len(gotValidatingAdmissionPolicies)) {
for i := range tt.vaps {
assert.Equal(t, tt.vaps[i].kind, gotValidatingAdmissionPolicies[i].Kind)
}
}
if assert.Equal(t, len(tt.vapBindings), len(gotBindings)) {
for i := range tt.vapBindings {
assert.Equal(t, tt.vapBindings[i].kind, gotBindings[i].Kind)
}
}
}
})
}

View file

@ -0,0 +1,68 @@
package validatingadmissionpolicy
import (
"context"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"k8s.io/api/admissionregistration/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
corev1listers "k8s.io/client-go/listers/core/v1"
)
// Everything someone might need to validate a single ValidatingPolicyDefinition
// against all of its registered bindings.
type PolicyData struct {
definition v1alpha1.ValidatingAdmissionPolicy
bindings []v1alpha1.ValidatingAdmissionPolicyBinding
}
func (p *PolicyData) AddBinding(binding v1alpha1.ValidatingAdmissionPolicyBinding) {
p.bindings = append(p.bindings, binding)
}
func (p *PolicyData) GetDefinition() v1alpha1.ValidatingAdmissionPolicy {
return p.definition
}
func (p *PolicyData) GetBindings() []v1alpha1.ValidatingAdmissionPolicyBinding {
return p.bindings
}
func NewPolicyData(policy v1alpha1.ValidatingAdmissionPolicy) PolicyData {
return PolicyData{
definition: policy,
}
}
type CustomNamespaceLister struct {
dClient dclient.Interface
}
func (c *CustomNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) {
var namespaces []*corev1.Namespace
namespace, err := c.dClient.GetKubeClient().CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, ns := range namespace.Items {
nsCopy := ns
namespaces = append(namespaces, &nsCopy)
}
return namespaces, nil
}
func (c *CustomNamespaceLister) Get(name string) (*corev1.Namespace, error) {
namespace, err := c.dClient.GetKubeClient().CoreV1().Namespaces().Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return namespace, nil
}
func NewCustomNamespaceLister(dClient dclient.Interface) corev1listers.NamespaceLister {
return &CustomNamespaceLister{
dClient: dClient,
}
}

View file

@ -0,0 +1,54 @@
package validatingadmissionpolicy
import (
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/api/admissionregistration/v1beta1"
)
func convertRules(v1alpha1rules []v1alpha1.NamedRuleWithOperations) []v1beta1.NamedRuleWithOperations {
var v1beta1rules []v1beta1.NamedRuleWithOperations
for _, r := range v1alpha1rules {
v1beta1rules = append(v1beta1rules, v1beta1.NamedRuleWithOperations(r))
}
return v1beta1rules
}
func convertValidations(v1alpha1validations []v1alpha1.Validation) []v1beta1.Validation {
var v1beta1validations []v1beta1.Validation
for _, v := range v1alpha1validations {
v1beta1validations = append(v1beta1validations, v1beta1.Validation(v))
}
return v1beta1validations
}
func convertAuditAnnotations(v1alpha1auditanns []v1alpha1.AuditAnnotation) []v1beta1.AuditAnnotation {
var v1beta1auditanns []v1beta1.AuditAnnotation
for _, a := range v1alpha1auditanns {
v1beta1auditanns = append(v1beta1auditanns, v1beta1.AuditAnnotation(a))
}
return v1beta1auditanns
}
func convertMatchConditions(v1alpha1conditions []v1alpha1.MatchCondition) []v1beta1.MatchCondition {
var v1beta1conditions []v1beta1.MatchCondition
for _, m := range v1alpha1conditions {
v1beta1conditions = append(v1beta1conditions, v1beta1.MatchCondition(m))
}
return v1beta1conditions
}
func convertVariables(v1alpha1variables []v1alpha1.Variable) []v1beta1.Variable {
var v1beta1variables []v1beta1.Variable
for _, v := range v1alpha1variables {
v1beta1variables = append(v1beta1variables, v1beta1.Variable(v))
}
return v1beta1variables
}
func convertValidationActions(v1alpha1actions []v1alpha1.ValidationAction) []v1beta1.ValidationAction {
var v1beta1actions []v1beta1.ValidationAction
for _, a := range v1alpha1actions {
v1beta1actions = append(v1beta1actions, v1beta1.ValidationAction(a))
}
return v1beta1actions
}

View file

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
celutils "github.com/kyverno/kyverno/pkg/utils/cel"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
@ -13,11 +14,15 @@ import (
"golang.org/x/text/language"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/cel"
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching"
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
celconfig "k8s.io/apiserver/pkg/apis/cel"
)
@ -60,18 +65,143 @@ func GetKinds(policy v1alpha1.ValidatingAdmissionPolicy) []string {
return kindList
}
func Validate(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.Unstructured) engineapi.EngineResponse {
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
logger.V(3).Info("applying policy on resource", "policy", policy.GetName(), "resource", resPath)
func Validate(policyData PolicyData, resource unstructured.Unstructured, client dclient.Interface) (engineapi.EngineResponse, error) {
var (
gvr schema.GroupVersionResource
a admission.Attributes
err error
)
policy := policyData.definition
bindings := policyData.bindings
engineResponse := engineapi.NewEngineResponse(resource, engineapi.NewValidatingAdmissionPolicy(policy), nil)
if client != nil {
nsLister := NewCustomNamespaceLister(client)
matcher := validatingadmissionpolicy.NewMatcher(matching.NewMatcher(nsLister, client.GetKubeClient()))
// convert policy from v1alpha1 to v1beta1
var namespaceSelector, objectSelector metav1.LabelSelector
if policy.Spec.MatchConstraints.NamespaceSelector != nil {
namespaceSelector = *policy.Spec.MatchConstraints.NamespaceSelector
}
if policy.Spec.MatchConstraints.ObjectSelector != nil {
objectSelector = *policy.Spec.MatchConstraints.ObjectSelector
}
v1beta1policy := &v1beta1.ValidatingAdmissionPolicy{
Spec: v1beta1.ValidatingAdmissionPolicySpec{
FailurePolicy: (*v1beta1.FailurePolicyType)(policy.Spec.FailurePolicy),
ParamKind: (*v1beta1.ParamKind)(policy.Spec.ParamKind),
MatchConstraints: &v1beta1.MatchResources{
NamespaceSelector: &namespaceSelector,
ObjectSelector: &objectSelector,
ResourceRules: convertRules(policy.Spec.MatchConstraints.ResourceRules),
ExcludeResourceRules: convertRules(policy.Spec.MatchConstraints.ExcludeResourceRules),
MatchPolicy: (*v1beta1.MatchPolicyType)(policy.Spec.MatchConstraints.MatchPolicy),
},
Validations: convertValidations(policy.Spec.Validations),
AuditAnnotations: convertAuditAnnotations(policy.Spec.AuditAnnotations),
MatchConditions: convertMatchConditions(policy.Spec.MatchConditions),
Variables: convertVariables(policy.Spec.Variables),
},
}
// construct admission attributes
gvr, err = client.Discovery().GetGVRFromGVK(resource.GroupVersionKind())
if err != nil {
return engineResponse, err
}
a = admission.NewAttributesRecord(resource.DeepCopyObject(), nil, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName(), gvr, "", admission.Create, nil, false, nil)
// check if policy matches the incoming resource
o := admission.NewObjectInterfacesFromScheme(runtime.NewScheme())
isMatch, _, _, err := matcher.DefinitionMatches(a, o, v1beta1policy)
if err != nil {
return engineResponse, err
}
if !isMatch {
return engineResponse, nil
}
if len(bindings) == 0 {
a = admission.NewAttributesRecord(resource.DeepCopyObject(), nil, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName(), gvr, "", admission.Create, nil, false, nil)
resPath := fmt.Sprintf("%s/%s/%s", a.GetNamespace(), a.GetKind().Kind, a.GetName())
logger.V(3).Info("validate resource %s against policy %s", resPath, policy.GetName())
return validateResource(policy, resource, a)
} else {
for _, binding := range bindings {
// convert policy binding from v1alpha1 to v1beta1
var namespaceSelector, objectSelector, paramSelector metav1.LabelSelector
if binding.Spec.MatchResources.NamespaceSelector != nil {
namespaceSelector = *binding.Spec.MatchResources.NamespaceSelector
}
if binding.Spec.MatchResources.ObjectSelector != nil {
objectSelector = *binding.Spec.MatchResources.ObjectSelector
}
var paramRef v1beta1.ParamRef
if binding.Spec.ParamRef != nil {
paramRef.Name = binding.Spec.ParamRef.Name
paramRef.Namespace = binding.Spec.ParamRef.Namespace
if binding.Spec.ParamRef.Selector != nil {
paramRef.Selector = binding.Spec.ParamRef.Selector
} else {
paramRef.Selector = &paramSelector
}
paramRef.ParameterNotFoundAction = (*v1beta1.ParameterNotFoundActionType)(binding.Spec.ParamRef.ParameterNotFoundAction)
}
v1beta1binding := &v1beta1.ValidatingAdmissionPolicyBinding{
Spec: v1beta1.ValidatingAdmissionPolicyBindingSpec{
PolicyName: binding.Spec.PolicyName,
ParamRef: &paramRef,
MatchResources: &v1beta1.MatchResources{
NamespaceSelector: &namespaceSelector,
ObjectSelector: &objectSelector,
ResourceRules: convertRules(binding.Spec.MatchResources.ResourceRules),
ExcludeResourceRules: convertRules(binding.Spec.MatchResources.ExcludeResourceRules),
MatchPolicy: (*v1beta1.MatchPolicyType)(binding.Spec.MatchResources.MatchPolicy),
},
ValidationActions: convertValidationActions(binding.Spec.ValidationActions),
},
}
isMatch, err := matcher.BindingMatches(a, o, v1beta1binding)
if err != nil {
return engineResponse, err
}
if !isMatch {
continue
}
resPath := fmt.Sprintf("%s/%s/%s", a.GetNamespace(), a.GetKind().Kind, a.GetName())
logger.V(3).Info("validate resource %s against policy %s with binding %s", resPath, policy.GetName(), binding.GetName())
return validateResource(policy, resource, a)
}
}
} else {
a = admission.NewAttributesRecord(resource.DeepCopyObject(), nil, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName(), gvr, "", admission.Create, nil, false, nil)
resPath := fmt.Sprintf("%s/%s/%s", a.GetNamespace(), a.GetKind().Kind, a.GetName())
logger.V(3).Info("validate resource %s against policy %s", resPath, policy.GetName())
return validateResource(policy, resource, a)
}
return engineResponse, nil
}
func validateResource(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.Unstructured, a admission.Attributes) (engineapi.EngineResponse, error) {
startTime := time.Now()
validations := policy.Spec.Validations
auditAnnotations := policy.Spec.AuditAnnotations
matchConditions := policy.Spec.MatchConditions
variables := policy.Spec.Variables
engineResponse := engineapi.NewEngineResponse(resource, engineapi.NewValidatingAdmissionPolicy(policy), nil)
policyResp := engineapi.NewPolicyResponse()
var ruleResp *engineapi.RuleResponse
// compile CEL expressions
compiler, err := celutils.NewCompiler(policy.Spec.Validations, policy.Spec.AuditAnnotations, policy.Spec.MatchConditions, policy.Spec.Variables)
if err != nil {
return engineResponse, err
}
hasParam := policy.Spec.ParamKind != nil
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
compiler.CompileVariables(optionalVars)
var failPolicy admissionregistrationv1.FailurePolicyType
if policy.Spec.FailurePolicy == nil {
@ -87,43 +217,16 @@ func Validate(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.U
matchPolicy = *policy.Spec.MatchConstraints.MatchPolicy
}
engineResponse := engineapi.NewEngineResponse(resource, engineapi.NewValidatingAdmissionPolicy(policy), nil)
policyResp := engineapi.NewPolicyResponse()
var ruleResp *engineapi.RuleResponse
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
// compile CEL expressions
compiler, err := celutils.NewCompiler(validations, auditAnnotations, matchConditions, variables)
if err != nil {
ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, "Error creating composited compiler", err)
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
engineResponse = engineResponse.WithPolicyResponse(policyResp)
return engineResponse
}
compiler.CompileVariables(optionalVars)
filter := compiler.CompileValidateExpressions(optionalVars)
messageExpressionfilter := compiler.CompileMessageExpressions(optionalVars)
auditAnnotationFilter := compiler.CompileAuditAnnotationsExpressions(optionalVars)
matchConditionFilter := compiler.CompileMatchExpressions(optionalVars)
newMatcher := matchconditions.NewMatcher(matchConditionFilter, &failPolicy, "", string(matchPolicy), "")
validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, nil)
admissionAttributes := admission.NewAttributesRecord(
resource.DeepCopyObject(),
nil, resource.GroupVersionKind(),
resource.GetNamespace(),
resource.GetName(),
schema.GroupVersionResource{},
"",
admission.Create,
nil,
false,
nil,
newMatcher := matchconditions.NewMatcher(compiler.CompileMatchExpressions(optionalVars), &failPolicy, "", string(matchPolicy), "")
validator := validatingadmissionpolicy.NewValidator(
compiler.CompileValidateExpressions(optionalVars),
newMatcher,
compiler.CompileAuditAnnotationsExpressions(optionalVars),
compiler.CompileMessageExpressions(optionalVars),
&failPolicy,
)
versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil)
validateResult := validator.Validate(context.TODO(), schema.GroupVersionResource{}, versionedAttr, nil, nil, celconfig.RuntimeCELCostBudget, nil)
versionedAttr, _ := admission.NewVersionedAttributes(a, a.GetKind(), nil)
validateResult := validator.Validate(context.TODO(), a.GetResource(), versionedAttr, nil, nil, celconfig.RuntimeCELCostBudget, nil)
isPass := true
for _, policyDecision := range validateResult.Decisions {
@ -144,5 +247,5 @@ func Validate(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.U
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
engineResponse = engineResponse.WithPolicyResponse(policyResp)
return engineResponse
return engineResponse, nil
}

View file

@ -127,7 +127,7 @@ spec:
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, policy, _ := yamlutils.GetPolicy(tt.policy)
_, policy, _, _ := yamlutils.GetPolicy(tt.policy)
kinds := GetKinds(policy[0])
if !reflect.DeepEqual(kinds, tt.wantKinds) {
t.Errorf("Expected %v, got %v", tt.wantKinds, kinds)

View file

@ -54,7 +54,7 @@ func TestNotAllowedVars_MatchSection(t *testing.T) {
}
`)
policy, _, err := yamlutils.GetPolicy(policyWithVarInMatch)
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInMatch)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -106,7 +106,7 @@ func TestNotAllowedVars_ExcludeSection(t *testing.T) {
}
`)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -159,7 +159,7 @@ func TestNotAllowedVars_ExcludeSection_PositiveCase(t *testing.T) {
}
`)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -193,7 +193,7 @@ func TestNotAllowedVars_JSONPatchPath(t *testing.T) {
}
}`)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -238,7 +238,7 @@ func TestNotAllowedVars_JSONPatchPath_ContextRootPositive(t *testing.T) {
}
}`)
policy, _, err := yamlutils.GetPolicy(policyManifest)
policy, _, _, err := yamlutils.GetPolicy(policyManifest)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -281,7 +281,7 @@ func TestNotAllowedVars_JSONPatchPath_ContextSubPositive(t *testing.T) {
}
}`)
policy, _, err := yamlutils.GetPolicy(policyManifest)
policy, _, _, err := yamlutils.GetPolicy(policyManifest)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -315,7 +315,7 @@ func TestNotAllowedVars_JSONPatchPath_PositiveCase(t *testing.T) {
}
}`)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -347,7 +347,7 @@ spec:
policyJSON, err := yaml.ToJSON(policyYAML)
assert.NilError(t, err)
policy, _, err := yamlutils.GetPolicy(policyJSON)
policy, _, _, err := yamlutils.GetPolicy(policyJSON)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -435,7 +435,7 @@ func TestNotAllowedVars_VariableFormats(t *testing.T) {
value: "foo.com"
`, tc.input))
policy, _, err := yamlutils.GetPolicy(policyYAML)
policy, _, _, err := yamlutils.GetPolicy(policyYAML)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -481,7 +481,7 @@ spec:
policyJSON, err := yaml.ToJSON(policyYAML)
assert.NilError(t, err)
policy, _, err := yamlutils.GetPolicy(policyJSON)
policy, _, _, err := yamlutils.GetPolicy(policyJSON)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)