1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 10:55:05 +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:
Tathagata Paul 2022-04-12 09:30:49 +05:30 committed by GitHub
parent f11cec73a8
commit 10cf0f2344
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 11 deletions

View file

@ -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)
}

View file

@ -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{}))

View file

@ -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
}

View file

@ -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

View file

@ -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)
}

View 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"

View 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

View 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

View file

@ -0,0 +1,4 @@
clusterRoles:
- cluster-admin
userInfo:
username: molybdenum@somecorp.com