mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
feat: support authorizer variable in CEL expressions (#8024)
* feat: support authorizer variable in CEL expressions Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * feat: add the auth reason Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * feat: add kuttl tests Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * fix lint issue Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * fix kuttl test Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * fix: add helpers Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> --------- Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
parent
7a3a3194eb
commit
b495c6d112
27 changed files with 291 additions and 30 deletions
|
@ -23,7 +23,7 @@ type CanIOptions interface {
|
|||
// - group version resource is determined from the kind using the discovery client REST mapper
|
||||
// - If disallowed, the reason and evaluationError is available in the logs
|
||||
// - each can generates a SubjectAccessReview resource and response is evaluated for permissions
|
||||
RunAccessCheck(context.Context) (bool, error)
|
||||
RunAccessCheck(context.Context) (bool, string, error)
|
||||
}
|
||||
|
||||
type canIOptions struct {
|
||||
|
@ -55,30 +55,30 @@ func NewCanI(discovery Discovery, sarClient authorizationv1client.SubjectAccessR
|
|||
// - group version resource is determined from the kind using the discovery client REST mapper
|
||||
// - If disallowed, the reason and evaluationError is available in the logs
|
||||
// - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions
|
||||
func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, error) {
|
||||
func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, string, error) {
|
||||
// get GroupVersionResource from RESTMapper
|
||||
// get GVR from kind
|
||||
apiVersion, kind := kubeutils.GetKindFromGVK(o.gvk)
|
||||
gv, err := schema.ParseGroupVersion(apiVersion)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse group/version %s", apiVersion)
|
||||
return false, "", fmt.Errorf("failed to parse group/version %s", apiVersion)
|
||||
}
|
||||
gvr, err := o.discovery.GetGVRFromGVK(gv.WithKind(kind))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get GVR for kind %s", o.gvk)
|
||||
return false, "", fmt.Errorf("failed to get GVR for kind %s", o.gvk)
|
||||
}
|
||||
if gvr.Empty() {
|
||||
// cannot find GVR
|
||||
return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.gvk)
|
||||
return false, "", fmt.Errorf("failed to get the Group Version Resource for kind %s", o.gvk)
|
||||
}
|
||||
logger := logger.WithValues("kind", kind, "namespace", o.namespace, "gvr", gvr.String(), "verb", o.verb)
|
||||
result, err := o.checker.Check(ctx, gvr.Group, gvr.Version, gvr.Resource, o.subresource, o.namespace, o.verb)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to check permissions")
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
if !result.Allowed {
|
||||
logger.Info("disallowed operation", "reason", result.Reason, "evaluationError", result.EvaluationError)
|
||||
}
|
||||
return result.Allowed, nil
|
||||
return result.Allowed, result.Reason, nil
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ func TestCanIOptions_DiscoveryError(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := NewCanI(tt.fields.discovery, nil, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "", "admin")
|
||||
got, err := o.RunAccessCheck(context.TODO())
|
||||
got, _, err := o.RunAccessCheck(context.TODO())
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
@ -117,7 +117,7 @@ func TestCanIOptions_SsarError(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := NewCanI(tt.fields.discovery, tt.fields.sarClient, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "", "admin")
|
||||
got, err := o.RunAccessCheck(context.TODO())
|
||||
got, _, err := o.RunAccessCheck(context.TODO())
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
@ -174,7 +174,7 @@ func TestCanIOptions_RunAccessCheck(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := NewCanI(tt.fields.client.Discovery(), tt.fields.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), tt.fields.kind, tt.fields.namespace, tt.fields.verb, "", "admin")
|
||||
got, err := o.RunAccessCheck(context.TODO())
|
||||
got, _, err := o.RunAccessCheck(context.TODO())
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
|
|
@ -65,11 +65,11 @@ func (a *dclientAdapter) IsNamespaced(group, version, kind string) (bool, error)
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (a *dclientAdapter) CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, error) {
|
||||
func (a *dclientAdapter) CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, string, error) {
|
||||
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, verb, subresource, user)
|
||||
ok, err := canI.RunAccessCheck(ctx)
|
||||
ok, reason, err := canI.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, reason, err
|
||||
}
|
||||
return ok, nil
|
||||
return ok, reason, nil
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ type RawClient interface {
|
|||
}
|
||||
|
||||
type AuthClient interface {
|
||||
CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, error)
|
||||
CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, string, error)
|
||||
}
|
||||
|
||||
type ResourceClient interface {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/kyverno/kyverno/pkg/engine/handlers"
|
||||
"github.com/kyverno/kyverno/pkg/engine/internal"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
celutils "github.com/kyverno/kyverno/pkg/utils/cel"
|
||||
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||
|
@ -57,6 +58,10 @@ func (h validateCELHandler) Process(
|
|||
gvk := resource.GroupVersionKind()
|
||||
namespaceName := resource.GetNamespace()
|
||||
resourceName := resource.GetName()
|
||||
resourceKind, _ := policyContext.ResourceKind()
|
||||
policyKind := policyContext.Policy().GetKind()
|
||||
policyName := policyContext.Policy().GetName()
|
||||
|
||||
object := resource.DeepCopyObject()
|
||||
// in case of update request, set the oldObject to the current resource before it gets updated
|
||||
var oldObject runtime.Object
|
||||
|
@ -76,7 +81,8 @@ func (h validateCELHandler) Process(
|
|||
validations := rule.Validation.CEL.Expressions
|
||||
auditAnnotations := rule.Validation.CEL.AuditAnnotations
|
||||
|
||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
|
||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
|
||||
expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
|
||||
// compile CEL expressions
|
||||
compiler, err := celutils.NewCompiler(validations, auditAnnotations, matchConditions, variables)
|
||||
if err != nil {
|
||||
|
@ -84,12 +90,12 @@ func (h validateCELHandler) Process(
|
|||
}
|
||||
compiler.CompileVariables(optionalVars)
|
||||
filter := compiler.CompileValidateExpressions(optionalVars)
|
||||
messageExpressionfilter := compiler.CompileMessageExpressions(optionalVars)
|
||||
messageExpressionfilter := compiler.CompileMessageExpressions(expressionOptionalVars)
|
||||
auditAnnotationFilter := compiler.CompileAuditAnnotationsExpressions(optionalVars)
|
||||
matchConditionFilter := compiler.CompileMatchExpressions(optionalVars)
|
||||
|
||||
// newMatcher will be used to check if the incoming resource matches the CEL preconditions
|
||||
newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, "", "", "")
|
||||
newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, policyKind, "", policyName)
|
||||
// newValidator will be used to validate CEL expressions against the incoming object
|
||||
validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, nil)
|
||||
|
||||
|
@ -108,8 +114,11 @@ func (h validateCELHandler) Process(
|
|||
}
|
||||
}
|
||||
|
||||
admissionAttributes := admission.NewAttributesRecord(object, oldObject, gvk, namespaceName, resourceName, gvr, "", admission.Operation(policyContext.Operation()), nil, false, nil)
|
||||
requestInfo := policyContext.AdmissionInfo()
|
||||
userInfo := internal.NewUser(requestInfo.AdmissionUserInfo.Username, requestInfo.AdmissionUserInfo.UID, requestInfo.AdmissionUserInfo.Groups)
|
||||
admissionAttributes := admission.NewAttributesRecord(object, oldObject, gvk, namespaceName, resourceName, gvr, "", admission.Operation(policyContext.Operation()), nil, false, &userInfo)
|
||||
versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil)
|
||||
authorizer := internal.NewAuthorizer(h.client, resourceKind)
|
||||
// validate the incoming object against the rule
|
||||
var validationResults []validatingadmissionpolicy.ValidateResult
|
||||
if hasParam {
|
||||
|
@ -124,10 +133,10 @@ func (h validateCELHandler) Process(
|
|||
}
|
||||
|
||||
for _, param := range params {
|
||||
validationResults = append(validationResults, validator.Validate(ctx, gvr, versionedAttr, param, namespace, celconfig.RuntimeCELCostBudget, nil))
|
||||
validationResults = append(validationResults, validator.Validate(ctx, gvr, versionedAttr, param, namespace, celconfig.RuntimeCELCostBudget, &authorizer))
|
||||
}
|
||||
} else {
|
||||
validationResults = append(validationResults, validator.Validate(ctx, gvr, versionedAttr, nil, namespace, celconfig.RuntimeCELCostBudget, nil))
|
||||
validationResults = append(validationResults, validator.Validate(ctx, gvr, versionedAttr, nil, namespace, celconfig.RuntimeCELCostBudget, &authorizer))
|
||||
}
|
||||
|
||||
for _, validationResult := range validationResults {
|
||||
|
|
|
@ -171,7 +171,8 @@ func (h validateManifestHandler) verifyManifest(
|
|||
}
|
||||
|
||||
func (h validateManifestHandler) checkDryRunPermission(ctx context.Context, kind, namespace string) (bool, error) {
|
||||
return h.client.CanI(ctx, kind, namespace, "create", "", config.KyvernoServiceAccountName())
|
||||
ok, _, err := h.client.CanI(ctx, kind, namespace, "create", "", config.KyvernoServiceAccountName())
|
||||
return ok, err
|
||||
}
|
||||
|
||||
func verifyManifestAttestorSet(resource unstructured.Unstructured, attestorSet kyvernov1.AttestorSet, vo *k8smanifest.VerifyResourceOption, path string, uid string, logger logr.Logger) (bool, string, error) {
|
||||
|
|
73
pkg/engine/internal/auth.go
Normal file
73
pkg/engine/internal/auth.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// Authorizer implements authorizer.Authorizer interface. It is intended to be used in validate.cel subrules.
|
||||
type Authorizer struct {
|
||||
client engineapi.Client
|
||||
resourceKind schema.GroupVersionKind
|
||||
}
|
||||
|
||||
func (a *Authorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
ok, reason, err := a.client.CanI(ctx,
|
||||
a.resourceKind.Kind,
|
||||
attributes.GetNamespace(),
|
||||
attributes.GetVerb(),
|
||||
attributes.GetSubresource(),
|
||||
attributes.GetUser().GetName(),
|
||||
)
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, reason, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return authorizer.DecisionAllow, reason, nil
|
||||
} else {
|
||||
return authorizer.DecisionDeny, reason, nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewAuthorizer(client engineapi.Client, resourceKind schema.GroupVersionKind) Authorizer {
|
||||
return Authorizer{
|
||||
client: client,
|
||||
resourceKind: resourceKind,
|
||||
}
|
||||
}
|
||||
|
||||
// User implements user.Info interface. It is intended to be used in validate.cel subrules.
|
||||
type User struct {
|
||||
name string
|
||||
uid string
|
||||
groups []string
|
||||
extra map[string][]string
|
||||
}
|
||||
|
||||
func (u *User) GetName() string {
|
||||
return u.name
|
||||
}
|
||||
|
||||
func (u *User) GetUID() string {
|
||||
return u.uid
|
||||
}
|
||||
|
||||
func (u *User) GetGroups() []string {
|
||||
return u.groups
|
||||
}
|
||||
|
||||
func (u *User) GetExtra() map[string][]string {
|
||||
return u.extra
|
||||
}
|
||||
|
||||
func NewUser(name, uid string, groups []string) User {
|
||||
return User{
|
||||
name: name,
|
||||
uid: uid,
|
||||
groups: groups,
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ func NewAuth(client dclient.Interface, user string, log logr.Logger) *Auth {
|
|||
// CanICreate returns 'true' if self can 'create' resource
|
||||
func (a *Auth) CanICreate(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
|
||||
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "create", "", a.user)
|
||||
ok, err := canI.RunAccessCheck(ctx)
|
||||
ok, _, err := canI.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func (a *Auth) CanICreate(ctx context.Context, gvk, namespace, subresource strin
|
|||
// CanIUpdate returns 'true' if self can 'update' resource
|
||||
func (a *Auth) CanIUpdate(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
|
||||
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "update", "", a.user)
|
||||
ok, err := canI.RunAccessCheck(ctx)
|
||||
ok, _, err := canI.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func (a *Auth) CanIUpdate(ctx context.Context, gvk, namespace, subresource strin
|
|||
// CanIDelete returns 'true' if self can 'delete' resource
|
||||
func (a *Auth) CanIDelete(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
|
||||
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "delete", "", a.user)
|
||||
ok, err := canI.RunAccessCheck(ctx)
|
||||
ok, _, err := canI.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ func (a *Auth) CanIDelete(ctx context.Context, gvk, namespace, subresource strin
|
|||
// CanIGet returns 'true' if self can 'get' resource
|
||||
func (a *Auth) CanIGet(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
|
||||
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "get", "", a.user)
|
||||
ok, err := canI.RunAccessCheck(ctx)
|
||||
ok, _, err := canI.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -23,10 +23,12 @@ func newAuthChecker(client dclient.Interface, user string) AuthChecker {
|
|||
|
||||
func (a *authChecker) CanIUpdate(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
|
||||
checker := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "update", subresource, a.user)
|
||||
return checker.RunAccessCheck(ctx)
|
||||
ok, _, err := checker.RunAccessCheck(ctx)
|
||||
return ok, err
|
||||
}
|
||||
|
||||
func (a *authChecker) CanIGet(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
|
||||
checker := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "get", subresource, a.user)
|
||||
return checker.RunAccessCheck(ctx)
|
||||
ok, _, err := checker.RunAccessCheck(ctx)
|
||||
return ok, err
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov
|
|||
kinds := sets.New(spec.MatchResources.GetKinds()...)
|
||||
for kind := range kinds {
|
||||
checker := auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, "delete", "", config.KyvernoUserName(config.KyvernoServiceAccountName()))
|
||||
allowedDeletion, err := checker.RunAccessCheck(ctx)
|
||||
allowedDeletion, _, err := checker.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov
|
|||
}
|
||||
|
||||
checker = auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, "list", "", config.KyvernoUserName(config.KyvernoServiceAccountName()))
|
||||
allowedList, err := checker.RunAccessCheck(ctx)
|
||||
allowedList, _, err := checker.RunAccessCheck(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- serviceaccount.yaml
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- rbac.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy.yaml
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
commands:
|
||||
- command: kubectl apply -f ./pod.yaml --as=system:serviceaccount:default:test-account
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: webserver
|
||||
spec:
|
||||
containers:
|
||||
- name: webserver
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-port
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: host-port
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
cel:
|
||||
expressions:
|
||||
- expression: "authorizer.serviceAccount('default', 'test-account').group('').resource('pods').namespace('default').check('delete').allowed()"
|
||||
message: "The user isn't allowed to delete pods in the 'default' namespace."
|
||||
- expression: "object.spec.containers.all(container, !has(container.ports) || container.ports.all(port, !has(port.hostPort) || port.hostPort == 0))"
|
||||
message: "The fields spec.containers[*].ports[*].hostPort must either be unset or set to `0`"
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: role
|
||||
namespace: default
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- pods
|
||||
verbs: ["create", "update", "get", "list", "patch", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: rolebinding
|
||||
namespace: default
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
subjects:
|
||||
- namespace: default
|
||||
kind: ServiceAccount
|
||||
name: test-account
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test-account
|
||||
namespace: default
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- serviceaccount.yaml
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- rbac.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy.yaml
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
commands:
|
||||
- script: |
|
||||
if kubectl apply -f ./deployment.yaml --as=system:serviceaccount:default:test-account-1
|
||||
then
|
||||
echo "Test failed. Deployment shouldn't be created."
|
||||
exit 1
|
||||
else
|
||||
echo "Test succeeded. Deployment isn't created as expected."
|
||||
exit 0
|
||||
fi
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-test-1
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: container2
|
||||
image: nginx
|
|
@ -0,0 +1,21 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: check-deployment-replicas-1
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: deployment-replicas
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment
|
||||
validate:
|
||||
cel:
|
||||
expressions:
|
||||
- expression: "authorizer.serviceAccount('default', 'test-account-1').group('apps').resource('deployments').namespace('default').check('delete').allowed()"
|
||||
message: "The user isn't allowed to delete deployments in the 'default' namespace."
|
||||
- expression: "object.spec.replicas <= 3"
|
||||
message: "Deployment spec.replicas must be less than 3."
|
|
@ -0,0 +1,25 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: role-1
|
||||
namespace: default
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments
|
||||
verbs: ["create", "update", "get", "list", "patch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: rolebinding-1
|
||||
namespace: default
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role-1
|
||||
subjects:
|
||||
- namespace: default
|
||||
kind: ServiceAccount
|
||||
name: test-account-1
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test-account-1
|
||||
namespace: default
|
|
@ -2,6 +2,8 @@ apiVersion: kyverno.io/v1
|
|||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-port
|
||||
annotations:
|
||||
pod-policies.kyverno.io/autogen-controllers: none
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
|
|
Loading…
Reference in a new issue