mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
Generate Policy Exceptions (#9987)
* add control names and images to PSS results Signed-off-by: Jim Bugwadia <jim@nirmata.com> * remove init Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix tets Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add --generate-exceptions flag Signed-off-by: Jim Bugwadia <jim@nirmata.com> * use controlsJSON Signed-off-by: Jim Bugwadia <jim@nirmata.com> * suppress message `Applying....` Signed-off-by: Jim Bugwadia <jim@nirmata.com> * generate CLI docs and fix lint issues Signed-off-by: Jim Bugwadia <jim@nirmata.com> * revert changes in launch.json Signed-off-by: Jim Bugwadia <jim@nirmata.com> * gen CLI docs Signed-off-by: Jim Bugwadia <jim@nirmata.com> * handle auto-gen rules Signed-off-by: Jim Bugwadia <jim@nirmata.com> * handle auto-gen rules for CronJob Signed-off-by: Jim Bugwadia <jim@nirmata.com> * handle auto-gen rules for CronJob Signed-off-by: Jim Bugwadia <jim@nirmata.com> --------- Signed-off-by: Jim Bugwadia <jim@nirmata.com> Co-authored-by: Frank Jogeleit <frank.jogeleit@web.de>
This commit is contained in:
parent
98a29e1321
commit
be0ad07774
3 changed files with 222 additions and 85 deletions
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/report"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/source"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/userinfo"
|
||||
|
@ -37,7 +36,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const divider = "----------------------------------------------------------------------"
|
||||
|
@ -48,26 +46,28 @@ type SkippedInvalidPolicies struct {
|
|||
}
|
||||
|
||||
type ApplyCommandConfig struct {
|
||||
KubeConfig string
|
||||
Context string
|
||||
Namespace string
|
||||
MutateLogPath string
|
||||
Variables []string
|
||||
ValuesFile string
|
||||
UserInfoPath string
|
||||
Cluster bool
|
||||
PolicyReport bool
|
||||
Stdin bool
|
||||
RegistryAccess bool
|
||||
AuditWarn bool
|
||||
ResourcePaths []string
|
||||
PolicyPaths []string
|
||||
GitBranch string
|
||||
warnExitCode int
|
||||
warnNoPassed bool
|
||||
Exception []string
|
||||
ContinueOnFail bool
|
||||
inlineExceptions bool
|
||||
KubeConfig string
|
||||
Context string
|
||||
Namespace string
|
||||
MutateLogPath string
|
||||
Variables []string
|
||||
ValuesFile string
|
||||
UserInfoPath string
|
||||
Cluster bool
|
||||
PolicyReport bool
|
||||
Stdin bool
|
||||
RegistryAccess bool
|
||||
AuditWarn bool
|
||||
ResourcePaths []string
|
||||
PolicyPaths []string
|
||||
GitBranch string
|
||||
warnExitCode int
|
||||
warnNoPassed bool
|
||||
Exception []string
|
||||
ContinueOnFail bool
|
||||
inlineExceptions bool
|
||||
GenerateExceptions bool
|
||||
GeneratedExceptionTTL time.Duration
|
||||
}
|
||||
|
||||
func Command() *cobra.Command {
|
||||
|
@ -91,6 +91,8 @@ func Command() *cobra.Command {
|
|||
printSkippedAndInvalidPolicies(out, skipInvalidPolicies)
|
||||
if applyCommandConfig.PolicyReport {
|
||||
printReports(out, responses, applyCommandConfig.AuditWarn)
|
||||
} else if applyCommandConfig.GenerateExceptions {
|
||||
printExceptions(out, responses, applyCommandConfig.AuditWarn, applyCommandConfig.GeneratedExceptionTTL)
|
||||
} else if table {
|
||||
printTable(out, detailedResults, applyCommandConfig.AuditWarn, responses...)
|
||||
} else {
|
||||
|
@ -155,6 +157,8 @@ func Command() *cobra.Command {
|
|||
cmd.Flags().StringSliceVarP(&applyCommandConfig.Exception, "exceptions", "", nil, "Policy exception to be considered when evaluating policies against resources")
|
||||
cmd.Flags().BoolVar(&applyCommandConfig.ContinueOnFail, "continue-on-fail", false, "If set to true, will continue to apply policies on the next resource upon failure to apply to the current resource instead of exiting out")
|
||||
cmd.Flags().BoolVarP(&applyCommandConfig.inlineExceptions, "exceptions-with-resources", "", false, "Evaluate policy exceptions from the resources path")
|
||||
cmd.Flags().BoolVarP(&applyCommandConfig.GenerateExceptions, "generate-exceptions", "", false, "Generate policy exceptions for each violation")
|
||||
cmd.Flags().DurationVarP(&applyCommandConfig.GeneratedExceptionTTL, "generated-exception-ttl", "", time.Hour*24*30, "Default TTL for generated exceptions")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -206,7 +210,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
|
|||
return rc, resources1, skipInvalidPolicies, responses1, fmt.Errorf("Error: failed to load exceptions (%s)", err)
|
||||
}
|
||||
}
|
||||
if !c.Stdin && !c.PolicyReport {
|
||||
if !c.Stdin && !c.PolicyReport && !c.GenerateExceptions {
|
||||
var policyRulesCount int
|
||||
for _, policy := range policies {
|
||||
policyRulesCount += len(autogen.ComputeRules(policy, ""))
|
||||
|
@ -476,43 +480,6 @@ func (c *ApplyCommandConfig) checkArguments() (*processor.ResultCounts, []*unstr
|
|||
return nil, nil, skipInvalidPolicies, nil, nil
|
||||
}
|
||||
|
||||
func printSkippedAndInvalidPolicies(out io.Writer, skipInvalidPolicies SkippedInvalidPolicies) {
|
||||
if len(skipInvalidPolicies.skipped) > 0 {
|
||||
fmt.Fprintln(out, divider)
|
||||
fmt.Fprintln(out, "Policies Skipped (as required variables are not provided by the user):")
|
||||
for i, policyName := range skipInvalidPolicies.skipped {
|
||||
fmt.Fprintf(out, "%d. %s\n", i+1, policyName)
|
||||
}
|
||||
fmt.Fprintln(out, divider)
|
||||
}
|
||||
if len(skipInvalidPolicies.invalid) > 0 {
|
||||
fmt.Fprintln(out, divider)
|
||||
fmt.Fprintln(out, "Invalid Policies:")
|
||||
for i, policyName := range skipInvalidPolicies.invalid {
|
||||
fmt.Fprintf(out, "%d. %s\n", i+1, policyName)
|
||||
}
|
||||
fmt.Fprintln(out, divider)
|
||||
}
|
||||
}
|
||||
|
||||
func printReports(out io.Writer, engineResponses []engineapi.EngineResponse, auditWarn bool) {
|
||||
clustered, namespaced := report.ComputePolicyReports(auditWarn, engineResponses...)
|
||||
if len(clustered) > 0 {
|
||||
report := report.MergeClusterReports(clustered)
|
||||
yamlReport, _ := yaml.Marshal(report)
|
||||
fmt.Fprintln(out, string(yamlReport))
|
||||
}
|
||||
for _, r := range namespaced {
|
||||
fmt.Fprintln(out, string("---"))
|
||||
yamlReport, _ := yaml.Marshal(r)
|
||||
fmt.Fprintln(out, string(yamlReport))
|
||||
}
|
||||
}
|
||||
|
||||
func printViolations(out io.Writer, rc *processor.ResultCounts) {
|
||||
fmt.Fprintf(out, "\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", rc.Pass, rc.Fail, rc.Warn, rc.Error, rc.Skip)
|
||||
}
|
||||
|
||||
type WarnExitCodeError struct {
|
||||
ExitCode int
|
||||
}
|
||||
|
|
168
cmd/cli/kubectl-kyverno/commands/apply/print.go
Normal file
168
cmd/cli/kubectl-kyverno/commands/apply/print.go
Normal file
|
@ -0,0 +1,168 @@
|
|||
package apply
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
||||
"github.com/kyverno/kyverno/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/report"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
kyvernoreports "github.com/kyverno/kyverno/pkg/utils/report"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func printSkippedAndInvalidPolicies(out io.Writer, skipInvalidPolicies SkippedInvalidPolicies) {
|
||||
if len(skipInvalidPolicies.skipped) > 0 {
|
||||
fmt.Fprintln(out, divider)
|
||||
fmt.Fprintln(out, "Policies Skipped (as required variables are not provided by the user):")
|
||||
for i, policyName := range skipInvalidPolicies.skipped {
|
||||
fmt.Fprintf(out, "%d. %s\n", i+1, policyName)
|
||||
}
|
||||
fmt.Fprintln(out, divider)
|
||||
}
|
||||
if len(skipInvalidPolicies.invalid) > 0 {
|
||||
fmt.Fprintln(out, divider)
|
||||
fmt.Fprintln(out, "Invalid Policies:")
|
||||
for i, policyName := range skipInvalidPolicies.invalid {
|
||||
fmt.Fprintf(out, "%d. %s\n", i+1, policyName)
|
||||
}
|
||||
fmt.Fprintln(out, divider)
|
||||
}
|
||||
}
|
||||
|
||||
func printReports(out io.Writer, engineResponses []engineapi.EngineResponse, auditWarn bool) {
|
||||
clustered, namespaced := report.ComputePolicyReports(auditWarn, engineResponses...)
|
||||
if len(clustered) > 0 {
|
||||
report := report.MergeClusterReports(clustered)
|
||||
yamlReport, _ := yaml.Marshal(report)
|
||||
fmt.Fprintln(out, string(yamlReport))
|
||||
}
|
||||
for _, r := range namespaced {
|
||||
fmt.Fprintln(out, string("---"))
|
||||
yamlReport, _ := yaml.Marshal(r)
|
||||
fmt.Fprintln(out, string(yamlReport))
|
||||
}
|
||||
}
|
||||
|
||||
func printExceptions(out io.Writer, engineResponses []engineapi.EngineResponse, auditWarn bool, ttl time.Duration) {
|
||||
clustered, _ := report.ComputePolicyReports(auditWarn, engineResponses...)
|
||||
for _, report := range clustered {
|
||||
for _, result := range report.Results {
|
||||
if result.Result == "fail" {
|
||||
if err := printException(out, result, ttl); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printException(out io.Writer, result v1alpha2.PolicyReportResult, ttl time.Duration) error {
|
||||
for _, r := range result.Resources {
|
||||
name := strings.Join([]string{result.Policy, result.Rule, r.Namespace, r.Name}, "-")
|
||||
|
||||
kinds := []string{r.Kind}
|
||||
names := []string{r.Name}
|
||||
rules := []string{result.Rule}
|
||||
if strings.HasPrefix(result.Rule, "autogen-") {
|
||||
if r.Kind == "CronJob" {
|
||||
kinds = append(kinds, "Job")
|
||||
rules = append(rules, strings.ReplaceAll(result.Rule, "autogen-cronjob-", "autogen-"))
|
||||
kinds = append(kinds, "Pod")
|
||||
rules = append(rules, result.Rule[len("autogen-cronjob-"):])
|
||||
} else {
|
||||
kinds = append(kinds, "Pod")
|
||||
rules = append(rules, result.Rule[len("autogen-"):])
|
||||
}
|
||||
names = append(names, r.Name+"-*")
|
||||
}
|
||||
|
||||
exception := kyvernov2beta1.PolicyException{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PolicyException",
|
||||
APIVersion: kyvernov2beta1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: kyvernov2beta1.PolicyExceptionSpec{
|
||||
Match: kyvernov2beta1.MatchResources{
|
||||
All: kyvernov1.ResourceFilters{
|
||||
kyvernov1.ResourceFilter{
|
||||
ResourceDescription: kyvernov1.ResourceDescription{
|
||||
Kinds: kinds,
|
||||
Names: names,
|
||||
Namespaces: []string{r.Namespace},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Exceptions: []kyvernov2beta1.Exception{
|
||||
{
|
||||
PolicyName: result.Policy,
|
||||
RuleNames: rules,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if ttl > 0 {
|
||||
exception.ObjectMeta.Labels = map[string]string{
|
||||
"cleanup.kyverno.io/ttl": ttl.String(),
|
||||
}
|
||||
}
|
||||
|
||||
if controlList, ok := result.Properties["controlsJSON"]; ok {
|
||||
pssList := make([]kyvernov1.PodSecurityStandard, 0)
|
||||
var controls []kyvernoreports.Control
|
||||
err := json.Unmarshal([]byte(controlList), &controls)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshall PSS controls %s", controlList)
|
||||
}
|
||||
for _, c := range controls {
|
||||
pss := kyvernov1.PodSecurityStandard{
|
||||
ControlName: c.Name,
|
||||
}
|
||||
if c.Images != nil {
|
||||
pss.Images = wildcardTagOrDigest(c.Images)
|
||||
}
|
||||
pssList = append(pssList, pss)
|
||||
}
|
||||
exception.Spec.PodSecurity = pssList
|
||||
}
|
||||
|
||||
exceptionYAML, err := yaml.Marshal(exception)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(out, "---\n")
|
||||
fmt.Fprint(out, string(exceptionYAML))
|
||||
fmt.Fprint(out, "\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var regexpTagOrDigest = regexp.MustCompile(":.*|@.*")
|
||||
|
||||
func wildcardTagOrDigest(images []string) []string {
|
||||
for i, s := range images {
|
||||
images[i] = regexpTagOrDigest.ReplaceAllString(s, "*")
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
func printViolations(out io.Writer, rc *processor.ResultCounts) {
|
||||
fmt.Fprintf(out, "\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", rc.Pass, rc.Fail, rc.Warn, rc.Error, rc.Skip)
|
||||
}
|
|
@ -37,31 +37,33 @@ kyverno apply [flags]
|
|||
### Options
|
||||
|
||||
```
|
||||
--audit-warn If set to true, will flag audit policies as warnings instead of failures
|
||||
-c, --cluster Checks if policies should be applied to cluster in the current context
|
||||
--context string The name of the kubeconfig context to use
|
||||
--continue-on-fail If set to true, will continue to apply policies on the next resource upon failure to apply to the current resource instead of exiting out
|
||||
--detailed-results If set to true, display detailed results
|
||||
-e, --exception strings Policy exception to be considered when evaluating policies against resources
|
||||
--exceptions strings Policy exception to be considered when evaluating policies against resources
|
||||
--exceptions-with-resources Evaluate policy exceptions from the resources path
|
||||
-b, --git-branch string test git repository branch
|
||||
-h, --help help for apply
|
||||
--kubeconfig string path to kubeconfig file with authorization and master location information
|
||||
-n, --namespace string Optional Policy parameter passed with cluster flag
|
||||
-o, --output string Prints the mutated resources in provided file/directory
|
||||
-p, --policy-report Generates policy report when passed (default policyviolation)
|
||||
--registry If set to true, access the image registry using local docker credentials to populate external data
|
||||
--remove-color Remove any color from output
|
||||
-r, --resource strings Path to resource files
|
||||
--resources strings Path to resource files
|
||||
-s, --set strings Variables that are required
|
||||
-i, --stdin Optional mutate policy parameter to pipe directly through to kubectl
|
||||
-t, --table Show results in table format
|
||||
-u, --userinfo string Admission Info including Roles, Cluster Roles and Subjects
|
||||
-f, --values-file string File containing values for policy variables
|
||||
--warn-exit-code int Set the exit code for warnings; if failures or errors are found, will exit 1
|
||||
--warn-no-pass Specify if warning exit code should be raised if no objects satisfied a policy; can be used together with --warn-exit-code flag
|
||||
--audit-warn If set to true, will flag audit policies as warnings instead of failures
|
||||
-c, --cluster Checks if policies should be applied to cluster in the current context
|
||||
--context string The name of the kubeconfig context to use
|
||||
--continue-on-fail If set to true, will continue to apply policies on the next resource upon failure to apply to the current resource instead of exiting out
|
||||
--detailed-results If set to true, display detailed results
|
||||
-e, --exception strings Policy exception to be considered when evaluating policies against resources
|
||||
--exceptions strings Policy exception to be considered when evaluating policies against resources
|
||||
--exceptions-with-resources Evaluate policy exceptions from the resources path
|
||||
--generate-exceptions Generate policy exceptions for each violation
|
||||
--generated-exception-ttl duration Default TTL for generated exceptions (default 720h0m0s)
|
||||
-b, --git-branch string test git repository branch
|
||||
-h, --help help for apply
|
||||
--kubeconfig string path to kubeconfig file with authorization and master location information
|
||||
-n, --namespace string Optional Policy parameter passed with cluster flag
|
||||
-o, --output string Prints the mutated resources in provided file/directory
|
||||
-p, --policy-report Generates policy report when passed (default policyviolation)
|
||||
--registry If set to true, access the image registry using local docker credentials to populate external data
|
||||
--remove-color Remove any color from output
|
||||
-r, --resource strings Path to resource files
|
||||
--resources strings Path to resource files
|
||||
-s, --set strings Variables that are required
|
||||
-i, --stdin Optional mutate policy parameter to pipe directly through to kubectl
|
||||
-t, --table Show results in table format
|
||||
-u, --userinfo string Admission Info including Roles, Cluster Roles and Subjects
|
||||
-f, --values-file string File containing values for policy variables
|
||||
--warn-exit-code int Set the exit code for warnings; if failures or errors are found, will exit 1
|
||||
--warn-no-pass Specify if warning exit code should be raised if no objects satisfied a policy; can be used together with --warn-exit-code flag
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
|
Loading…
Reference in a new issue