-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add validation for rancher rbac objects
Problem: Adding the role restricted-admin in rancher requires validation of objects through diferent means to validate the user doing the action Solution: Add webhook validation to GRBs PRTBs and CRTBs. GRBs: Validation is done to ensure the GRB being created by the user will not cause escalated privileges so the user creating the GRB must have at minimum the permissions they are attempting to grant through the GRB PRTBs and CRTBs: The same check is done for both. First validation is only done on the local cluster. Then a subject access review is done for the requester checking that they have '**' access in the local cluster.
- Loading branch information
Showing
11 changed files
with
451 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ | |
/dist | ||
*.swp | ||
.idea | ||
/webhook |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
//go:generate go run pkg/codegen/cleanup/main.go | ||
//go:generate go run pkg/codegen/main.go | ||
package main | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package auth | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
rancherv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" | ||
"github.com/rancher/wrangler/pkg/webhook" | ||
admissionv1 "k8s.io/api/admission/v1" | ||
authenticationv1 "k8s.io/api/authentication/v1" | ||
v1 "k8s.io/api/authorization/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" | ||
"k8s.io/utils/trace" | ||
) | ||
|
||
const adminRole = "admin" | ||
|
||
func NewCRTBalidator(sar authorizationv1.SubjectAccessReviewInterface) webhook.Handler { | ||
return &clusterRoleTemplateBindingValidator{ | ||
sar: sar, | ||
} | ||
} | ||
|
||
type clusterRoleTemplateBindingValidator struct { | ||
sar authorizationv1.SubjectAccessReviewInterface | ||
} | ||
|
||
func (c *clusterRoleTemplateBindingValidator) Admit(response *webhook.Response, request *webhook.Request) error { | ||
listTrace := trace.New("clusterRoleTemplateBindingValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) | ||
defer listTrace.LogIfLong(1 * time.Second) | ||
|
||
crtb, err := crtbObject(request) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if crtb.ClusterName != "local" { | ||
response.Allowed = true | ||
return nil | ||
} | ||
|
||
return adminAccessCheck(c.sar, response, request) | ||
} | ||
|
||
func crtbObject(request *webhook.Request) (*rancherv3.ClusterRoleTemplateBinding, error) { | ||
var crtb runtime.Object | ||
var err error | ||
if request.Operation == admissionv1.Delete { | ||
crtb, err = request.DecodeOldObject() | ||
} else { | ||
crtb, err = request.DecodeObject() | ||
} | ||
return crtb.(*rancherv3.ClusterRoleTemplateBinding), err | ||
} | ||
|
||
func toExtra(extra map[string]authenticationv1.ExtraValue) map[string]v1.ExtraValue { | ||
result := map[string]v1.ExtraValue{} | ||
for k, v := range extra { | ||
result[k] = v1.ExtraValue(v) | ||
} | ||
return result | ||
} | ||
|
||
// adminAccessCheck checks that the user submitting the request has ** access in the local cluster | ||
func adminAccessCheck(sar authorizationv1.SubjectAccessReviewInterface, response *webhook.Response, request *webhook.Request) error { | ||
resp, err := sar.Create(request.Context, &v1.SubjectAccessReview{ | ||
Spec: v1.SubjectAccessReviewSpec{ | ||
ResourceAttributes: &v1.ResourceAttributes{ | ||
Group: "*", | ||
Verb: "*", | ||
Resource: "*", | ||
}, | ||
User: request.UserInfo.Username, | ||
Groups: request.UserInfo.Groups, | ||
Extra: toExtra(request.UserInfo.Extra), | ||
UID: request.UserInfo.UID, | ||
}, | ||
}, metav1.CreateOptions{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if resp.Status.Allowed { | ||
response.Allowed = true | ||
} else { | ||
response.Result = &metav1.Status{ | ||
Status: "Failure", | ||
Message: resp.Status.Reason, | ||
Reason: metav1.StatusReasonUnauthorized, | ||
Code: http.StatusUnauthorized, | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package auth | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
rancherv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" | ||
"github.com/rancher/webhook/pkg/authentication" | ||
v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" | ||
"github.com/rancher/wrangler-api/pkg/generated/controllers/rbac" | ||
"github.com/rancher/wrangler/pkg/webhook" | ||
admissionv1 "k8s.io/api/admission/v1" | ||
authenticationv1 "k8s.io/api/authentication/v1" | ||
rbacv1 "k8s.io/api/rbac/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apiserver/pkg/authentication/user" | ||
k8srequest "k8s.io/apiserver/pkg/endpoints/request" | ||
"k8s.io/kubernetes/pkg/registry/rbac/validation" | ||
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation" | ||
"k8s.io/utils/trace" | ||
) | ||
|
||
func NewGRBValidator(grClient v3.GlobalRoleClient, r rbac.Interface) (webhook.Handler, error) { | ||
rbacRestGetter := authentication.RBACRestGetter{ | ||
Interface: r, | ||
} | ||
|
||
ruleResolver := rbacregistryvalidation.NewDefaultRuleResolver(rbacRestGetter, rbacRestGetter, rbacRestGetter, rbacRestGetter) | ||
|
||
return &globalRoleBindingValidator{ | ||
globalRoleClient: grClient, | ||
ruleSolver: ruleResolver, | ||
}, nil | ||
|
||
} | ||
|
||
type globalRoleBindingValidator struct { | ||
globalRoleClient v3.GlobalRoleClient | ||
ruleSolver validation.AuthorizationRuleResolver | ||
} | ||
|
||
func (grbv *globalRoleBindingValidator) Admit(response *webhook.Response, request *webhook.Request) error { | ||
listTrace := trace.New("globalRoleBindingValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) | ||
defer listTrace.LogIfLong(1 * time.Second) | ||
|
||
newGRB, err := grbObject(request) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Pull the global role to get the rules | ||
globalRole, err := grbv.globalRoleClient.Get(newGRB.GlobalRoleName, metav1.GetOptions{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
userInfo := &user.DefaultInfo{ | ||
Name: request.UserInfo.Username, | ||
UID: request.UserInfo.UID, | ||
Groups: request.UserInfo.Groups, | ||
Extra: toExtraString(request.UserInfo.Extra), | ||
} | ||
|
||
if err := grbv.ConfirmNoEscalation(globalRole.Rules, userInfo); err != nil { | ||
response.Result = &metav1.Status{ | ||
Status: "Failure", | ||
Message: err.Error(), | ||
Reason: metav1.StatusReasonUnauthorized, | ||
Code: http.StatusUnauthorized, | ||
} | ||
return nil | ||
} | ||
response.Allowed = true | ||
return nil | ||
} | ||
|
||
// ConfirmNoEscalation checks that the user attempting to create the GRB has all the permissions they are attempting | ||
// to grant through the GRB | ||
func (grbv *globalRoleBindingValidator) ConfirmNoEscalation(rules []rbacv1.PolicyRule, userInfo *user.DefaultInfo) error { | ||
globaleCtx := k8srequest.WithNamespace(k8srequest.WithUser(context.Background(), userInfo), "") | ||
if err := rbacregistryvalidation.ConfirmNoEscalation(globaleCtx, grbv.ruleSolver, rules); err != nil { | ||
return fmt.Errorf("failed to validate user: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func grbObject(request *webhook.Request) (*rancherv3.GlobalRoleBinding, error) { | ||
var grb runtime.Object | ||
var err error | ||
if request.Operation == admissionv1.Delete { | ||
grb, err = request.DecodeOldObject() | ||
} else { | ||
grb, err = request.DecodeObject() | ||
} | ||
return grb.(*rancherv3.GlobalRoleBinding), err | ||
} | ||
|
||
func toExtraString(extra map[string]authenticationv1.ExtraValue) map[string][]string { | ||
result := make(map[string][]string) | ||
for k, v := range extra { | ||
result[k] = v | ||
} | ||
return result | ||
} |
Oops, something went wrong.