mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
add support for roles, cluster roles and subjects (#3188)
* add support for roles, cluster roles and subjects in kyverno cli Signed-off-by: Tathagata Paul <tathagatapaul7@gmail.com> Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com> Co-authored-by: Sambhav Kothari <sambhavs.email@gmail.com>
This commit is contained in:
parent
f11cec73a8
commit
10cf0f2344
9 changed files with 199 additions and 11 deletions
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
|
||||
|
@ -106,7 +107,7 @@ func Command() *cobra.Command {
|
|||
var cmd *cobra.Command
|
||||
var resourcePaths []string
|
||||
var cluster, policyReport, stdin, registryAccess bool
|
||||
var mutateLogPath, variablesString, valuesFile, namespace string
|
||||
var mutateLogPath, variablesString, valuesFile, namespace, userInfoPath string
|
||||
cmd = &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "applies policies on resources",
|
||||
|
@ -121,7 +122,7 @@ func Command() *cobra.Command {
|
|||
}
|
||||
}()
|
||||
|
||||
rc, resources, skipInvalidPolicies, pvInfos, err := applyCommandHelper(resourcePaths, cluster, policyReport, mutateLogPath, variablesString, valuesFile, namespace, policyPaths, stdin, registryAccess)
|
||||
rc, resources, skipInvalidPolicies, pvInfos, err := applyCommandHelper(resourcePaths, userInfoPath, cluster, policyReport, mutateLogPath, variablesString, valuesFile, namespace, policyPaths, stdin, registryAccess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -134,6 +135,7 @@ func Command() *cobra.Command {
|
|||
cmd.Flags().BoolVarP(&cluster, "cluster", "c", false, "Checks if policies should be applied to cluster in the current context")
|
||||
cmd.Flags().StringVarP(&mutateLogPath, "output", "o", "", "Prints the mutated resources in provided file/directory")
|
||||
// currently `set` flag supports variable for single policy applied on single resource
|
||||
cmd.Flags().StringVarP(&userInfoPath, "userinfo", "u", "", "Admission Info including Roles, Cluster Roles and Subjects")
|
||||
cmd.Flags().StringVarP(&variablesString, "set", "s", "", "Variables that are required")
|
||||
cmd.Flags().StringVarP(&valuesFile, "values-file", "f", "", "File containing values for policy variables")
|
||||
cmd.Flags().BoolVarP(&policyReport, "policy-report", "", false, "Generates policy report when passed (default policyviolation r")
|
||||
|
@ -143,7 +145,7 @@ func Command() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool, mutateLogPath string,
|
||||
func applyCommandHelper(resourcePaths []string, userInfoPath string, cluster bool, policyReport bool, mutateLogPath string,
|
||||
variablesString string, valuesFile string, namespace string, policyPaths []string, stdin bool, registryAccess bool) (rc *common.ResultCounts, resources []*unstructured.Unstructured, skipInvalidPolicies SkippedInvalidPolicies, pvInfos []policyreport.Info, err error) {
|
||||
store.SetMock(true)
|
||||
store.SetRegistryAccess(registryAccess)
|
||||
|
@ -243,6 +245,16 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
|
|||
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("currently `set` flag supports variable for single policy applied on single resource ", nil)
|
||||
}
|
||||
|
||||
// get the user info as request info from a different file
|
||||
var userInfo v1.RequestInfo
|
||||
if userInfoPath != "" {
|
||||
userInfo, err = common.GetUserInfoFromPath(fs, userInfoPath, false, "")
|
||||
if err != nil {
|
||||
fmt.Printf("Error: failed to load request info\nCause: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if variablesString != "" {
|
||||
variables = common.SetInStoreContext(mutatedPolicies, variables)
|
||||
}
|
||||
|
@ -300,7 +312,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
|
|||
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.GetName(), resource.GetName()), err)
|
||||
}
|
||||
|
||||
_, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc, true)
|
||||
_, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, userInfo, policyReport, namespaceSelectorMap, stdin, rc, true)
|
||||
if err != nil {
|
||||
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func Test_Apply(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
_, _, _, info, _ := applyCommandHelper(tc.ResourcePaths, false, true, "", "", "", "", tc.PolicyPaths, false, false)
|
||||
_, _, _, info, _ := applyCommandHelper(tc.ResourcePaths, "", false, true, "", "", "", "", tc.PolicyPaths, false, false)
|
||||
resps := buildPolicyReports(info)
|
||||
for i, resp := range resps {
|
||||
compareSummary(tc.expectedPolicyReports[i].Summary, resp.UnstructuredContent()["summary"].(map[string]interface{}))
|
||||
|
|
|
@ -442,7 +442,7 @@ func MutatePolicies(policies []v1.PolicyInterface) ([]v1.PolicyInterface, error)
|
|||
|
||||
// ApplyPolicyOnResource - function to apply policy on resource
|
||||
func ApplyPolicyOnResource(policy v1.PolicyInterface, resource *unstructured.Unstructured,
|
||||
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool,
|
||||
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, userInfo v1.RequestInfo, policyReport bool,
|
||||
namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts,
|
||||
printPatchResource bool) ([]*response.EngineResponse, policyreport.Info, error) {
|
||||
|
||||
|
@ -568,7 +568,7 @@ OuterLoop:
|
|||
var info policyreport.Info
|
||||
var validateResponse *response.EngineResponse
|
||||
if policyHasValidate {
|
||||
policyCtx := &engine.PolicyContext{Policy: policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx, NamespaceLabels: namespaceLabels}
|
||||
policyCtx := &engine.PolicyContext{Policy: policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx, NamespaceLabels: namespaceLabels, AdmissionInfo: userInfo}
|
||||
validateResponse = engine.Validate(policyCtx)
|
||||
info = ProcessValidateEngineResponse(policy, validateResponse, resPath, rc, policyReport)
|
||||
}
|
||||
|
@ -754,6 +754,7 @@ func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []str
|
|||
|
||||
func ProcessValidateEngineResponse(policy v1.PolicyInterface, validateResponse *response.EngineResponse, resPath string, rc *ResultCounts, policyReport bool) policyreport.Info {
|
||||
var violatedRules []v1.ViolatedRule
|
||||
|
||||
printCount := 0
|
||||
for _, policyRule := range autogen.ComputeRules(policy) {
|
||||
ruleFoundInEngineResponse := false
|
||||
|
@ -1047,3 +1048,52 @@ func GetPatchedResourceFromPath(fs billy.Filesystem, path string, isGit bool, po
|
|||
|
||||
return patchedResource, nil
|
||||
}
|
||||
|
||||
//GetUserInfoFromPath - get the request info as user info from a given path
|
||||
func GetUserInfoFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (v1.RequestInfo, error) {
|
||||
userInfo := &v1.RequestInfo{}
|
||||
|
||||
if isGit {
|
||||
filep, err := fs.Open(filepath.Join(policyResourcePath, path))
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to open userInfo file: %s. \nerror: %s", path, err)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(filep)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: failed to read file %s: %v", filep.Name(), err.Error())
|
||||
}
|
||||
userInfoBytes, err := yaml.ToJSON(bytes)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to convert to JSON: %v", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(userInfoBytes, userInfo); err != nil {
|
||||
fmt.Printf("failed to decode yaml: %v", err)
|
||||
}
|
||||
} else {
|
||||
var errors []error
|
||||
bytes, err := ioutil.ReadFile(filepath.Join(policyResourcePath, path))
|
||||
if err != nil {
|
||||
errors = append(errors, sanitizederror.NewWithError("unable to read yaml", err))
|
||||
}
|
||||
userInfoBytes, err := yaml.ToJSON(bytes)
|
||||
if err != nil {
|
||||
errors = append(errors, sanitizederror.NewWithError("failed to convert json", err))
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(userInfoBytes, userInfo); err != nil {
|
||||
errors = append(errors, sanitizederror.NewWithError("failed to decode yaml", err))
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return *userInfo, sanitizederror.NewWithErrors("failed to read file", errors)
|
||||
}
|
||||
|
||||
if len(errors) > 0 && log.Log.V(1).Enabled() {
|
||||
fmt.Printf("ignoring errors: \n")
|
||||
for _, e := range errors {
|
||||
fmt.Printf(" %v \n", e.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return *userInfo, nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package common
|
|||
import (
|
||||
"testing"
|
||||
|
||||
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/toggle"
|
||||
ut "github.com/kyverno/kyverno/pkg/utils"
|
||||
"gotest.tools/assert"
|
||||
|
@ -99,7 +100,7 @@ func Test_NamespaceSelector(t *testing.T) {
|
|||
for _, tc := range testcases {
|
||||
policyArray, _ := ut.GetPolicy(tc.policy)
|
||||
resourceArray, _ := GetResource(tc.resource)
|
||||
ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, rc, false)
|
||||
ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, v1.RequestInfo{}, false, tc.namespaceSelectorMap, false, rc, false)
|
||||
assert.Equal(t, int64(rc.Pass), int64(tc.result.Pass))
|
||||
assert.Equal(t, int64(rc.Fail), int64(tc.result.Fail))
|
||||
// TODO: autogen rules seem to not be present when autogen internals is disabled
|
||||
|
|
|
@ -236,6 +236,7 @@ type Test struct {
|
|||
Policies []string `json:"policies"`
|
||||
Resources []string `json:"resources"`
|
||||
Variables string `json:"variables"`
|
||||
UserInfo string `json:"userinfo"`
|
||||
Results []TestResults `json:"results"`
|
||||
}
|
||||
|
||||
|
@ -410,7 +411,6 @@ func testCommandExecute(dirPath []string, fileName string, gitBranch string, tes
|
|||
errors = append(errors, sanitizederror.NewWithError("failed to convert to JSON", err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := applyPoliciesFromPath(fs, policyBytes, true, policyresoucePath, rc, openAPIController, tf); err != nil {
|
||||
return rc, sanitizederror.NewWithError("failed to apply test command", err)
|
||||
}
|
||||
|
@ -717,8 +717,8 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool,
|
|||
var variablesString string
|
||||
var pvInfos []policyreport.Info
|
||||
var resultCounts common.ResultCounts
|
||||
store.SetMock(true)
|
||||
|
||||
store.SetMock(true)
|
||||
if err := json.Unmarshal(policyBytes, values); err != nil {
|
||||
return sanitizederror.NewWithError("failed to decode yaml", err)
|
||||
}
|
||||
|
@ -739,6 +739,8 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool,
|
|||
|
||||
fmt.Printf("\nExecuting %s...", values.Name)
|
||||
valuesFile := values.Variables
|
||||
userInfoFile := values.UserInfo
|
||||
|
||||
variables, globalValMap, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, values.Variables, fs, isGit, policyResourcePath)
|
||||
if err != nil {
|
||||
if !sanitizederror.IsErrorSanitized(err) {
|
||||
|
@ -747,6 +749,16 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool,
|
|||
return err
|
||||
}
|
||||
|
||||
// get the user info as request info from a different file
|
||||
var userInfo v1.RequestInfo
|
||||
if userInfoFile != "" {
|
||||
userInfo, err = common.GetUserInfoFromPath(fs, userInfoFile, isGit, policyResourcePath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: failed to load request info\nCause: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
policyFullPath := getFullPath(values.Policies, policyResourcePath, isGit)
|
||||
resourceFullPath := getFullPath(values.Resources, policyResourcePath, isGit)
|
||||
|
||||
|
@ -857,7 +869,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool,
|
|||
return sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.GetName(), resource.GetName()), err)
|
||||
}
|
||||
|
||||
ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, &resultCounts, false)
|
||||
ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, userInfo, true, namespaceSelectorMap, false, &resultCounts, false)
|
||||
if err != nil {
|
||||
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
|
||||
}
|
||||
|
|
37
test/cli/test/admission_user_info/disallow_latest_tag.yaml
Normal file
37
test/cli/test/admission_user_info/disallow_latest_tag.yaml
Normal file
|
@ -0,0 +1,37 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
annotations:
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/description: >-
|
||||
The ':latest' tag is mutable and can lead to unexpected errors if the
|
||||
image changes. A best practice is to use an immutable tag that maps to
|
||||
a specific version of an application pod.
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: require-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
clusterRoles:
|
||||
- cluster-admin
|
||||
validate:
|
||||
message: "An image tag is required."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "*:*"
|
||||
- name: validate-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Using a mutable image tag e.g. 'latest' is not allowed."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
38
test/cli/test/admission_user_info/kyverno-test.yaml
Normal file
38
test/cli/test/admission_user_info/kyverno-test.yaml
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: admission-user-info
|
||||
policies:
|
||||
- disallow_latest_tag.yaml
|
||||
resources:
|
||||
- resource.yaml
|
||||
userinfo: user_info.yaml
|
||||
|
||||
results:
|
||||
- policy: disallow-latest-tag
|
||||
rule: require-image-tag
|
||||
resource: myapp-pod1
|
||||
kind: Pod
|
||||
result: pass
|
||||
- policy: disallow-latest-tag
|
||||
rule: require-image-tag
|
||||
resource: myapp-pod2
|
||||
kind: Pod
|
||||
result: pass
|
||||
- policy: disallow-latest-tag
|
||||
rule: require-image-tag
|
||||
resource: myapp-pod3
|
||||
kind: Pod
|
||||
result: pass
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: myapp-pod1
|
||||
kind: Pod
|
||||
result: pass
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: myapp-pod2
|
||||
kind: Pod
|
||||
result: pass
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: myapp-pod3
|
||||
kind: Pod
|
||||
result: pass
|
34
test/cli/test/admission_user_info/resource.yaml
Normal file
34
test/cli/test/admission_user_info/resource.yaml
Normal file
|
@ -0,0 +1,34 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod1
|
||||
labels:
|
||||
app: myapp1
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.12
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod2
|
||||
labels:
|
||||
app: myapp2
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.12
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod3
|
||||
labels:
|
||||
app: myapp3
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: ngnix:1.12
|
4
test/cli/test/admission_user_info/user_info.yaml
Normal file
4
test/cli/test/admission_user_info/user_info.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
clusterRoles:
|
||||
- cluster-admin
|
||||
userInfo:
|
||||
username: molybdenum@somecorp.com
|
Loading…
Add table
Reference in a new issue