mirror of https://github.com/rancher/webhook.git
Allowing validators to have multiple admits
Changes the validator interface to allow a given validator to use multiple admit functions
This commit is contained in:
parent
6fea515099
commit
572d5cc9bc
|
|
@ -44,10 +44,12 @@ type WebhookHandler interface {
|
|||
// Operations returns list of operations that this WebhookHandler supports.
|
||||
// Handlers will only be sent request with operations that are contained in the provided list.
|
||||
Operations() []v1.OperationType
|
||||
}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
// The response returned by the WebhookHandler will be forwarded to the kube-api server.
|
||||
// If the WebhookHandler can not accurately evaluate the request it should return an error.
|
||||
// Admitter handles webhook admission requests sent to this webhook.
|
||||
// The response returned by the WebhookHandler will be forwarded to the kube-api server.
|
||||
// If the WebhookHandler can not accurately evaluate the request it should return an error.
|
||||
type Admitter interface {
|
||||
Admit(*Request) (*admissionv1.AdmissionResponse, error)
|
||||
}
|
||||
|
||||
|
|
@ -61,11 +63,17 @@ type ValidatingAdmissionHandler interface {
|
|||
// A default configuration can be made using NewDefaultValidatingWebhook(...)
|
||||
// Most Webhooks implementing ValidatingWebhook will only return one configuration.
|
||||
ValidatingWebhook(clientConfig v1.WebhookClientConfig) []v1.ValidatingWebhook
|
||||
|
||||
// Admitters returns the admitters that this handler will call when evaluating a resource. If any one of these
|
||||
// fails or encounters an error, the failure/error is immediately returned and the rest are short-circuted.
|
||||
Admitters() []Admitter
|
||||
}
|
||||
|
||||
// MutatingAdmissionHandler is a handler used for creating a MutatingAdmission Webhook.
|
||||
type MutatingAdmissionHandler interface {
|
||||
WebhookHandler
|
||||
// Since mutators can change a resource, each MutatingAdmissionHandler can only use 1 admit function.
|
||||
Admitter
|
||||
|
||||
// MutatingWebhook returns a list of configurations to route to this handler.
|
||||
//
|
||||
|
|
@ -170,60 +178,117 @@ func SubPath(gvr schema.GroupVersionResource) string {
|
|||
return gvr.GroupResource().String()
|
||||
}
|
||||
|
||||
// NewHandlerFunc returns a new HandlerFunc that will call the WebhookHandler's admit function.
|
||||
func NewHandlerFunc(handler WebhookHandler) http.HandlerFunc {
|
||||
// NewValidatingHandlerFunc returns a new HandlerFunc that will call the functions returned by the ValidatingAdmissionHandler's AdmitFuncs() call.
|
||||
// If it encounters a failure or an error, it short-circuts and returns immediately.
|
||||
func NewValidatingHandlerFunc(handler ValidatingAdmissionHandler) http.HandlerFunc {
|
||||
return func(responseWriter http.ResponseWriter, req *http.Request) {
|
||||
|
||||
review := &admissionv1.AdmissionReview{}
|
||||
err := json.NewDecoder(req.Body).Decode(review)
|
||||
review, webReq, err := getReviewAndRequestForHandler(req, handler)
|
||||
if err != nil {
|
||||
sendError(responseWriter, review, err)
|
||||
return
|
||||
}
|
||||
|
||||
if review.Request == nil {
|
||||
sendError(responseWriter, review, fmt.Errorf("request is not set: %w", ErrInvalidRequest))
|
||||
return
|
||||
// save the response from the loop so we can return on success
|
||||
var response *admissionv1.AdmissionResponse
|
||||
for _, admitter := range handler.Admitters() {
|
||||
if admitter == nil {
|
||||
continue
|
||||
}
|
||||
response, err = admitter.Admit(webReq)
|
||||
if response == nil {
|
||||
response = &admissionv1.AdmissionResponse{}
|
||||
}
|
||||
logrus.Debugf("admit result: %s %s %s user=%s allowed=%v err=%v", webReq.Operation, webReq.Kind.String(), resourceString(webReq.Namespace, webReq.Name), webReq.UserInfo.Username, response.Allowed, err)
|
||||
// if we get an error or are not allowed, short circuit the admits
|
||||
if err != nil {
|
||||
review.Response = response
|
||||
sendError(responseWriter, review, err)
|
||||
return
|
||||
}
|
||||
if !response.Allowed {
|
||||
sendResponse(responseWriter, review, response)
|
||||
return
|
||||
}
|
||||
}
|
||||
webReq := &Request{
|
||||
AdmissionRequest: *review.Request,
|
||||
Context: req.Context(),
|
||||
}
|
||||
|
||||
// validate that this handler can handle the provided operation.
|
||||
if !canHandleOperation(handler, review.Request.Operation) {
|
||||
sendError(responseWriter, review, fmt.Errorf("can not handle '%s' for '%s': %w", review.Request.Operation, SubPath(handler.GVR()), ErrUnsupportedOperation))
|
||||
return
|
||||
}
|
||||
|
||||
review.Response, err = handler.Admit(webReq)
|
||||
if review.Response == nil {
|
||||
review.Response = &admissionv1.AdmissionResponse{}
|
||||
}
|
||||
logrus.Debugf("admit result: %s %s %s user=%s allowed=%v err=%v", webReq.Operation, webReq.Kind.String(), resourceString(webReq.Namespace, webReq.Name), webReq.UserInfo.Username, review.Response.Allowed, err)
|
||||
if err != nil {
|
||||
sendError(responseWriter, review, err)
|
||||
return
|
||||
}
|
||||
|
||||
review.Response.UID = review.Request.UID
|
||||
writeResponse(responseWriter, review)
|
||||
// if we have reached this point, all admits approved
|
||||
sendResponse(responseWriter, review, response)
|
||||
}
|
||||
}
|
||||
|
||||
// NewMutatingHandlerFunc returns a new HandlerFunc that will call the function returned by the MutatingAdmissionHandler's AdmitFunc() call.
|
||||
func NewMutatingHandlerFunc(handler MutatingAdmissionHandler) http.HandlerFunc {
|
||||
return func(responseWriter http.ResponseWriter, req *http.Request) {
|
||||
review, webReq, err := getReviewAndRequestForHandler(req, handler)
|
||||
if err != nil {
|
||||
// review could not be valid, so initialize some safe defaults
|
||||
sendError(responseWriter, review, err)
|
||||
return
|
||||
}
|
||||
response, err := handler.Admit(webReq)
|
||||
if response == nil {
|
||||
response = &admissionv1.AdmissionResponse{}
|
||||
}
|
||||
logrus.Debugf("admit result: %s %s %s user=%s allowed=%v err=%v", webReq.Operation, webReq.Kind.String(), resourceString(webReq.Namespace, webReq.Name), webReq.UserInfo.Username, response.Allowed, err)
|
||||
if err != nil {
|
||||
review.Response = response
|
||||
sendError(responseWriter, review, err)
|
||||
return
|
||||
}
|
||||
sendResponse(responseWriter, review, response)
|
||||
}
|
||||
}
|
||||
|
||||
// getReviewAndRequestForHandler produces a admission.AdmissionReview and a Request for a given http request and handler.
|
||||
// Returns an error if this handler can't handle this request or if the http.Request couldn't be decoded into an admissionReview.
|
||||
func getReviewAndRequestForHandler(req *http.Request, handler WebhookHandler) (*admissionv1.AdmissionReview, *Request, error) {
|
||||
review := admissionv1.AdmissionReview{}
|
||||
err := json.NewDecoder(req.Body).Decode(&review)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if review.Request == nil {
|
||||
return &review, nil, fmt.Errorf("request is not set: %w", ErrInvalidRequest)
|
||||
}
|
||||
webReq := &Request{
|
||||
AdmissionRequest: *review.Request,
|
||||
Context: req.Context(),
|
||||
}
|
||||
|
||||
// validate that this handler can handle the provided operation
|
||||
if !canHandleOperation(handler, review.Request.Operation) {
|
||||
return &review, nil, fmt.Errorf("can not handle '%s' for '%s': %w", review.Request.Operation, SubPath(handler.GVR()), ErrUnsupportedOperation)
|
||||
}
|
||||
return &review, webReq, nil
|
||||
}
|
||||
|
||||
// Ptr is a generic function that returns the pointer of a string.
|
||||
func Ptr[T ~string](str T) *T {
|
||||
newStr := str
|
||||
return &newStr
|
||||
}
|
||||
|
||||
func sendResponse(responseWriter http.ResponseWriter, review *admissionv1.AdmissionReview, response *admissionv1.AdmissionResponse) {
|
||||
review.Response = response
|
||||
review.Response.UID = review.Request.UID
|
||||
writeResponse(responseWriter, review)
|
||||
}
|
||||
|
||||
func sendError(responseWriter http.ResponseWriter, review *admissionv1.AdmissionReview, err error) {
|
||||
logrus.Error(err)
|
||||
if review == nil || review.Request == nil {
|
||||
http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if review.Response == nil {
|
||||
review.Response = &admissionv1.AdmissionResponse{}
|
||||
}
|
||||
// set the response to 500 so that k8s knows that the request got an error. If we just set the Result status the
|
||||
// failure policy won't apply
|
||||
responseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
review.Response.UID = review.Request.UID
|
||||
|
||||
review.Response.Result = &errors.NewInternalError(err).ErrStatus
|
||||
review.Response.Result.Code = http.StatusInternalServerError
|
||||
writeResponse(responseWriter, review)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,415 @@
|
|||
package admission_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rancher/webhook/pkg/admission"
|
||||
"github.com/stretchr/testify/assert"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type handlerResponse struct {
|
||||
hasAllow bool
|
||||
hasError bool
|
||||
}
|
||||
|
||||
type reviewResponse struct {
|
||||
wantReviewAllow bool
|
||||
wantReviewError bool
|
||||
}
|
||||
|
||||
func TestNewValidatingHandlerFunc(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operationMatchesHandler bool
|
||||
firstHandlerResponse *handlerResponse
|
||||
secondHandlerResponse *handlerResponse
|
||||
|
||||
hasDecodeError bool
|
||||
hasMissingRequest bool
|
||||
|
||||
wantHTTPError bool
|
||||
wantResponse *reviewResponse
|
||||
}{
|
||||
{
|
||||
name: "handler matches, both allow",
|
||||
operationMatchesHandler: true,
|
||||
firstHandlerResponse: &handlerResponse{
|
||||
hasAllow: true,
|
||||
},
|
||||
secondHandlerResponse: &handlerResponse{
|
||||
hasAllow: true,
|
||||
},
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches, first denies, second allows",
|
||||
operationMatchesHandler: true,
|
||||
firstHandlerResponse: &handlerResponse{
|
||||
hasAllow: false,
|
||||
},
|
||||
secondHandlerResponse: &handlerResponse{
|
||||
hasAllow: true,
|
||||
},
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches, first allows, second denies",
|
||||
operationMatchesHandler: true,
|
||||
firstHandlerResponse: &handlerResponse{
|
||||
hasAllow: true,
|
||||
},
|
||||
secondHandlerResponse: &handlerResponse{
|
||||
hasAllow: false,
|
||||
},
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches, both deny",
|
||||
operationMatchesHandler: true,
|
||||
firstHandlerResponse: &handlerResponse{
|
||||
hasAllow: false,
|
||||
},
|
||||
secondHandlerResponse: &handlerResponse{
|
||||
hasAllow: false,
|
||||
},
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches, first error",
|
||||
operationMatchesHandler: true,
|
||||
firstHandlerResponse: &handlerResponse{
|
||||
hasError: true,
|
||||
},
|
||||
wantHTTPError: true,
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
wantReviewError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches, first allow, second error",
|
||||
operationMatchesHandler: true,
|
||||
firstHandlerResponse: &handlerResponse{
|
||||
hasAllow: true,
|
||||
},
|
||||
secondHandlerResponse: &handlerResponse{
|
||||
hasError: true,
|
||||
},
|
||||
wantHTTPError: true,
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
wantReviewError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler doesn't match",
|
||||
operationMatchesHandler: false,
|
||||
wantHTTPError: true,
|
||||
},
|
||||
{
|
||||
name: "decode error",
|
||||
hasDecodeError: true,
|
||||
wantHTTPError: true,
|
||||
},
|
||||
{
|
||||
name: "missing request",
|
||||
hasDecodeError: true,
|
||||
wantHTTPError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
firstAdmitter := setupAdmitter(test.firstHandlerResponse)
|
||||
secondAdmitter := setupAdmitter(test.secondHandlerResponse)
|
||||
handler := fakeValidatingAdmissionHandler{
|
||||
gvr: schema.GroupVersionResource{
|
||||
Group: "test.cattle.io",
|
||||
Version: "v1alpha1",
|
||||
Resource: "resources",
|
||||
},
|
||||
operations: []v1.OperationType{
|
||||
v1.Create,
|
||||
},
|
||||
admitters: []fakeAdmitter{firstAdmitter, secondAdmitter},
|
||||
}
|
||||
var bodyBytes []byte
|
||||
var err error
|
||||
if test.hasMissingRequest {
|
||||
review := admissionv1.AdmissionReview{}
|
||||
bodyBytes, err = json.Marshal(review)
|
||||
assert.NoError(t, err)
|
||||
} else if test.hasDecodeError {
|
||||
data := map[string]any{
|
||||
"request": "value",
|
||||
}
|
||||
bodyBytes, err = json.Marshal(data)
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
review := admissionv1.AdmissionReview{
|
||||
Request: &admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Delete,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: "test.cattle.io",
|
||||
Version: "v1alpha1",
|
||||
Kind: "Resource",
|
||||
},
|
||||
Namespace: "test-ns",
|
||||
Name: "test",
|
||||
UserInfo: authenticationv1.UserInfo{
|
||||
Username: "test-user",
|
||||
},
|
||||
UID: "1",
|
||||
},
|
||||
}
|
||||
if test.operationMatchesHandler {
|
||||
review.Request.Operation = admissionv1.Create
|
||||
}
|
||||
bodyBytes, err = json.Marshal(review)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
body := strings.NewReader(string(bodyBytes))
|
||||
request := httptest.NewRequest("get", "/testEndpoint", body)
|
||||
response := httptest.NewRecorder()
|
||||
handlerFunc := admission.NewValidatingHandlerFunc(&handler)
|
||||
handlerFunc(response, request)
|
||||
if test.wantHTTPError {
|
||||
assert.Greater(t, response.Code, 399, "expected an error code of 400 or higher")
|
||||
}
|
||||
if test.wantResponse != nil {
|
||||
review := admissionv1.AdmissionReview{}
|
||||
err := json.NewDecoder(response.Result().Body).Decode(&review)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.UID("1"), review.Response.UID)
|
||||
assert.Equal(t, test.wantResponse.wantReviewAllow, review.Response.Allowed)
|
||||
if test.wantResponse.wantReviewError {
|
||||
assert.Greater(t, int(review.Response.Result.Code), 399, "expected an error code of 400 or higher")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewMutatingHandlerFunc(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operationMatchesHandler bool
|
||||
handlerResponse *handlerResponse
|
||||
|
||||
hasDecodeError bool
|
||||
hasMissingRequest bool
|
||||
|
||||
wantHTTPError bool
|
||||
wantReviewAllow bool
|
||||
wantResponse *reviewResponse
|
||||
}{
|
||||
{
|
||||
name: "handler matches and allows",
|
||||
operationMatchesHandler: true,
|
||||
handlerResponse: &handlerResponse{
|
||||
hasAllow: true,
|
||||
},
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches and denies",
|
||||
operationMatchesHandler: true,
|
||||
handlerResponse: &handlerResponse{
|
||||
hasAllow: false,
|
||||
},
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler does not match",
|
||||
operationMatchesHandler: false,
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handler matches but gets an error",
|
||||
operationMatchesHandler: true,
|
||||
handlerResponse: &handlerResponse{
|
||||
hasError: true,
|
||||
},
|
||||
wantHTTPError: true,
|
||||
wantResponse: &reviewResponse{
|
||||
wantReviewAllow: false,
|
||||
wantReviewError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "decode error",
|
||||
hasDecodeError: true,
|
||||
wantHTTPError: true,
|
||||
},
|
||||
{
|
||||
name: "missing request",
|
||||
hasMissingRequest: true,
|
||||
wantHTTPError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
admitter := setupAdmitter(test.handlerResponse)
|
||||
handler := fakeMutatingAdmissionHandler{
|
||||
gvr: schema.GroupVersionResource{
|
||||
Group: "test.cattle.io",
|
||||
Version: "v1alpha1",
|
||||
Resource: "resources",
|
||||
},
|
||||
operations: []v1.OperationType{
|
||||
v1.Create,
|
||||
},
|
||||
admitter: admitter,
|
||||
}
|
||||
var bodyBytes []byte
|
||||
var err error
|
||||
if test.hasMissingRequest {
|
||||
review := admissionv1.AdmissionReview{}
|
||||
bodyBytes, err = json.Marshal(review)
|
||||
assert.NoError(t, err)
|
||||
} else if test.hasDecodeError {
|
||||
data := map[string]any{
|
||||
"request": "value",
|
||||
}
|
||||
bodyBytes, err = json.Marshal(data)
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
review := admissionv1.AdmissionReview{
|
||||
Request: &admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Delete,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: "test.cattle.io",
|
||||
Version: "v1alpha1",
|
||||
Kind: "Resource",
|
||||
},
|
||||
Namespace: "test-ns",
|
||||
Name: "test",
|
||||
UserInfo: authenticationv1.UserInfo{
|
||||
Username: "test-user",
|
||||
},
|
||||
UID: "1",
|
||||
},
|
||||
}
|
||||
if test.operationMatchesHandler {
|
||||
review.Request.Operation = admissionv1.Create
|
||||
}
|
||||
bodyBytes, err = json.Marshal(review)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
body := strings.NewReader(string(bodyBytes))
|
||||
request := httptest.NewRequest("get", "/testEndpoint", body)
|
||||
response := httptest.NewRecorder()
|
||||
handlerFunc := admission.NewMutatingHandlerFunc(&handler)
|
||||
handlerFunc(response, request)
|
||||
if test.wantHTTPError {
|
||||
assert.Greater(t, response.Code, 399, "expected an error code of 400 or higher")
|
||||
}
|
||||
if test.wantResponse != nil {
|
||||
review := admissionv1.AdmissionReview{}
|
||||
err := json.NewDecoder(response.Result().Body).Decode(&review)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.UID("1"), review.Response.UID)
|
||||
assert.Equal(t, test.wantResponse.wantReviewAllow, review.Response.Allowed)
|
||||
if test.wantResponse.wantReviewError {
|
||||
assert.Greater(t, int(review.Response.Result.Code), 399, "expected an error code of 400 or higher")
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupAdmitter(response *handlerResponse) fakeAdmitter {
|
||||
admitter := fakeAdmitter{}
|
||||
if response == nil {
|
||||
return admitter
|
||||
}
|
||||
if response.hasError {
|
||||
admitter.err = fmt.Errorf("handler/admitter error")
|
||||
}
|
||||
admitter.response = admissionv1.AdmissionResponse{
|
||||
Allowed: response.hasAllow,
|
||||
}
|
||||
return admitter
|
||||
}
|
||||
|
||||
type fakeValidatingAdmissionHandler struct {
|
||||
gvr schema.GroupVersionResource
|
||||
operations []v1.OperationType
|
||||
admitters []fakeAdmitter
|
||||
}
|
||||
|
||||
func (f *fakeValidatingAdmissionHandler) GVR() schema.GroupVersionResource {
|
||||
return f.gvr
|
||||
}
|
||||
func (f *fakeValidatingAdmissionHandler) Operations() []v1.OperationType {
|
||||
return f.operations
|
||||
}
|
||||
|
||||
func (f *fakeValidatingAdmissionHandler) ValidatingWebhook(clientConfig v1.WebhookClientConfig) []v1.ValidatingWebhook {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeValidatingAdmissionHandler) Admitters() []admission.Admitter {
|
||||
var admitters []admission.Admitter
|
||||
for _, admitter := range f.admitters {
|
||||
admitter := admitter
|
||||
admitters = append(admitters, &admitter)
|
||||
}
|
||||
return admitters
|
||||
}
|
||||
|
||||
type fakeMutatingAdmissionHandler struct {
|
||||
gvr schema.GroupVersionResource
|
||||
operations []v1.OperationType
|
||||
admitter fakeAdmitter
|
||||
}
|
||||
|
||||
func (f *fakeMutatingAdmissionHandler) GVR() schema.GroupVersionResource {
|
||||
return f.gvr
|
||||
}
|
||||
func (f *fakeMutatingAdmissionHandler) Operations() []v1.OperationType {
|
||||
return f.operations
|
||||
}
|
||||
|
||||
func (f *fakeMutatingAdmissionHandler) Admit(req *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
return f.admitter.Admit(req)
|
||||
}
|
||||
|
||||
func (f *fakeMutatingAdmissionHandler) MutatingWebhook(clientConfig v1.WebhookClientConfig) []v1.MutatingWebhook {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeAdmitter struct {
|
||||
response admissionv1.AdmissionResponse
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeAdmitter) Admit(req *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
return &f.response, f.err
|
||||
}
|
||||
|
|
@ -20,13 +20,15 @@ import (
|
|||
|
||||
// Validator validates the namespace admission request.
|
||||
type Validator struct {
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// NewValidator returns a new validator used for validation of namespace requests.
|
||||
func NewValidator(sar authorizationv1.SubjectAccessReviewInterface) *Validator {
|
||||
return &Validator{
|
||||
sar: sar,
|
||||
admitter: admitter{
|
||||
sar: sar,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,9 +86,17 @@ func (v *Validator) ValidatingWebhook(clientConfig admv1.WebhookClientConfig) []
|
|||
return []admv1.ValidatingWebhook{*standardWebhook, *createWebhook, *kubeSystemCreateWebhook}
|
||||
}
|
||||
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
}
|
||||
|
||||
// Admit is the entrypoint for the validator.
|
||||
// Admit will return an error if it is unable to process the request.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("Namespace Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
@ -121,7 +131,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
extras[k] = v1.ExtraValue(v)
|
||||
}
|
||||
|
||||
resp, err := v.sar.Create(request.Context, &v1.SubjectAccessReview{
|
||||
resp, err := a.sar.Create(request.Context, &v1.SubjectAccessReview{
|
||||
Spec: v1.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &v1.ResourceAttributes{
|
||||
Verb: "updatepsa",
|
||||
|
|
|
|||
|
|
@ -342,7 +342,9 @@ func TestValidator_Admit(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
request := createNamespaceRequest(t, &tt)
|
||||
resp, err := validator.Admit(request)
|
||||
admitters := validator.Admitters()
|
||||
assert.Len(t, admitters, 1)
|
||||
resp, err := admitters[0].Admit(request)
|
||||
assert.Equal(t, tt.wantErr, err != nil)
|
||||
if !tt.wantErr {
|
||||
assert.Equal(t, tt.allowed, resp.Allowed)
|
||||
|
|
|
|||
|
|
@ -10,15 +10,12 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
var gvr = schema.GroupVersionResource{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Resource: "secrets",
|
||||
}
|
||||
var gvr = corev1.SchemeGroupVersion.WithResource("secrets")
|
||||
|
||||
// Mutator implements admission.MutatingAdmissionWebhook.
|
||||
type Mutator struct{}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,11 @@ const (
|
|||
|
||||
// Validator implements admission.ValidatingAdmissionWebhook.
|
||||
type Validator struct {
|
||||
roleCache v1.RoleCache
|
||||
roleBindingCache v1.RoleBindingCache
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// NewValidator creates a new secret validator which ensures secrets which own rbac objects aren't deleted with options
|
||||
// to oprhan those RBAC resources
|
||||
// to orphan those RBAC resources.
|
||||
func NewValidator(roleCache v1.RoleCache, roleBindingCache v1.RoleBindingCache) *Validator {
|
||||
roleCache.AddIndexer(roleOwnerIndex, func(obj *rbacv1.Role) ([]string, error) {
|
||||
return secretOwnerIndexer(obj.ObjectMeta), nil
|
||||
|
|
@ -42,8 +41,10 @@ func NewValidator(roleCache v1.RoleCache, roleBindingCache v1.RoleBindingCache)
|
|||
return secretOwnerIndexer(obj.ObjectMeta), nil
|
||||
})
|
||||
return &Validator{
|
||||
roleCache: roleCache,
|
||||
roleBindingCache: roleBindingCache,
|
||||
admitter: admitter{
|
||||
roleCache: roleCache,
|
||||
roleBindingCache: roleBindingCache,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,8 +76,18 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*validatingWebhook}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate secrets.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
roleCache v1.RoleCache
|
||||
roleBindingCache v1.RoleBindingCache
|
||||
}
|
||||
|
||||
// Admit is the entrypoint for the validator. Admit will return an error if it is unable to process the request.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
if request.DryRun != nil && *request.DryRun {
|
||||
return admission.ResponseAllowed(), nil
|
||||
}
|
||||
|
|
@ -99,7 +110,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read secret from request: %w", err)
|
||||
}
|
||||
roles, roleBindings, err := v.getRbacRefs(secret)
|
||||
roles, roleBindings, err := a.getRbacRefs(secret)
|
||||
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||
roleNames := make([]string, len(roles))
|
||||
roleBindingNames := make([]string, len(roleBindings))
|
||||
|
|
@ -131,12 +142,12 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
|
||||
// getRbacRefs checks to see if there are any existing rbac resources which could be orphaned by this delete call
|
||||
func (v *Validator) getRbacRefs(secret *corev1.Secret) ([]*rbacv1.Role, []*rbacv1.RoleBinding, error) {
|
||||
roles, err := v.roleCache.GetByIndex(roleOwnerIndex, fmt.Sprintf(ownerFormat, secret.Namespace, secret.Name))
|
||||
func (a *admitter) getRbacRefs(secret *corev1.Secret) ([]*rbacv1.Role, []*rbacv1.RoleBinding, error) {
|
||||
roles, err := a.roleCache.GetByIndex(roleOwnerIndex, fmt.Sprintf(ownerFormat, secret.Namespace, secret.Name))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
roleBindings, err := v.roleBindingCache.GetByIndex(roleBindingOwnerIndex, fmt.Sprintf(ownerFormat, secret.Namespace, secret.Name))
|
||||
roleBindings, err := a.roleBindingCache.GetByIndex(roleBindingOwnerIndex, fmt.Sprintf(ownerFormat, secret.Namespace, secret.Name))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@ func TestAdmit(t *testing.T) {
|
|||
roleBindingCache.EXPECT().AddIndexer(roleBindingOwnerIndex, gomock.Any())
|
||||
validator := NewValidator(roleCache, roleBindingCache)
|
||||
|
||||
response, err := validator.Admit(&req)
|
||||
admitters := validator.Admitters()
|
||||
assert.Len(t, admitters, 1)
|
||||
response, err := admitters[0].Admit(&req)
|
||||
if test.wantError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -29,15 +29,16 @@ var parsedRangeLessThan123 = semver.MustParseRange("< 1.23.0-rancher0")
|
|||
// NewValidator returns a new validator for management clusters.
|
||||
func NewValidator(sar authorizationv1.SubjectAccessReviewInterface, cache v3.PodSecurityAdmissionConfigurationTemplateCache) *Validator {
|
||||
return &Validator{
|
||||
sar: sar,
|
||||
psact: cache,
|
||||
admitter: admitter{
|
||||
sar: sar,
|
||||
psact: cache,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validator ValidatingWebhook for management clusters.
|
||||
type Validator struct {
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
psact v3.PodSecurityAdmissionConfigurationTemplateCache
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -57,9 +58,19 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*valWebhook}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate clusters.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
psact v3.PodSecurityAdmissionConfigurationTemplateCache
|
||||
}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
response, err := v.validateFleetPermissions(request)
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
response, err := a.validateFleetPermissions(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate fleet permissions: %w", err)
|
||||
}
|
||||
|
|
@ -76,14 +87,14 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
if cluster.Name == "local" || cluster.Spec.RancherKubernetesEngineConfig == nil {
|
||||
return admission.ResponseAllowed(), nil
|
||||
}
|
||||
response, err = v.validatePSACT(request)
|
||||
response, err = a.validatePSACT(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate PodSecurityAdmissionConfigurationTemplate(PSACT): %w", err)
|
||||
}
|
||||
if !response.Allowed {
|
||||
return response, nil
|
||||
}
|
||||
response, err = v.validatePSP(request)
|
||||
response, err = a.validatePSP(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate PSP: %w", err)
|
||||
}
|
||||
|
|
@ -103,7 +114,7 @@ func toExtra(extra map[string]authenticationv1.ExtraValue) map[string]v1.ExtraVa
|
|||
}
|
||||
|
||||
// validateFleetPermissions validates whether the request maker has required permissions around FleetWorkspace.
|
||||
func (v *Validator) validateFleetPermissions(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) validateFleetPermissions(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
oldCluster, newCluster, err := objectsv3.ClusterOldAndNewFromRequest(&request.AdmissionRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get old and new clusters from request: %w", err)
|
||||
|
|
@ -132,7 +143,7 @@ func (v *Validator) validateFleetPermissions(request *admission.Request) (*admis
|
|||
}, nil
|
||||
}
|
||||
|
||||
resp, err := v.sar.Create(request.Context, &v1.SubjectAccessReview{
|
||||
resp, err := a.sar.Create(request.Context, &v1.SubjectAccessReview{
|
||||
Spec: v1.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &v1.ResourceAttributes{
|
||||
Verb: "fleetaddcluster",
|
||||
|
|
@ -166,7 +177,7 @@ func (v *Validator) validateFleetPermissions(request *admission.Request) (*admis
|
|||
}
|
||||
|
||||
// validatePSACT validates the cluster spec when PodSecurityAdmissionConfigurationTemplate is used.
|
||||
func (v *Validator) validatePSACT(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) validatePSACT(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
oldCluster, newCluster, err := objectsv3.ClusterOldAndNewFromRequest(&request.AdmissionRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get old and new clusters from request: %w", err)
|
||||
|
|
@ -181,7 +192,7 @@ func (v *Validator) validatePSACT(request *admission.Request) (*admissionv1.Admi
|
|||
return admission.ResponseBadRequest("PodSecurityAdmissionConfigurationTemplate(PSACT) is only supported in k8s version 1.23 and above"), nil
|
||||
}
|
||||
if newTemplateName != "" {
|
||||
response, err := v.checkPSAConfigOnCluster(newCluster)
|
||||
response, err := a.checkPSAConfigOnCluster(newCluster)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check the PodSecurity Config in the cluster %s: %w", newCluster.Name, err)
|
||||
}
|
||||
|
|
@ -216,7 +227,7 @@ func (v *Validator) validatePSACT(request *admission.Request) (*admissionv1.Admi
|
|||
}
|
||||
|
||||
// checkPSAConfigOnCluster validates the cluster spec when DefaultPodSecurityAdmissionConfigurationTemplateName is set.
|
||||
func (v *Validator) checkPSAConfigOnCluster(cluster *apisv3.Cluster) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) checkPSAConfigOnCluster(cluster *apisv3.Cluster) (*admissionv1.AdmissionResponse, error) {
|
||||
// validate that extra_args.admission-control-config-file is not set at the same time
|
||||
_, found := cluster.Spec.RancherKubernetesEngineConfig.Services.KubeAPI.ExtraArgs["admission-control-config-file"]
|
||||
if found {
|
||||
|
|
@ -225,7 +236,7 @@ func (v *Validator) checkPSAConfigOnCluster(cluster *apisv3.Cluster) (*admission
|
|||
// validate that the configuration for PodSecurityAdmission under the kube-api.admission_configuration section
|
||||
// matches the content of the PodSecurityAdmissionConfigurationTemplate specified in the cluster
|
||||
name := cluster.Spec.DefaultPodSecurityAdmissionConfigurationTemplateName
|
||||
template, err := v.psact.Get(name)
|
||||
template, err := a.psact.Get(name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return admission.ResponseBadRequest(err.Error()), nil
|
||||
|
|
@ -257,7 +268,7 @@ func (v *Validator) checkPSAConfigOnCluster(cluster *apisv3.Cluster) (*admission
|
|||
}
|
||||
|
||||
// validatePSP validates if the PSP feature is enabled in a cluster which version is 1.25 or above.
|
||||
func (v *Validator) validatePSP(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) validatePSP(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
cluster, err := objectsv3.ClusterFromRequest(&request.AdmissionRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cluster from request: %w", err)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@ func TestAdmit(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &Validator{
|
||||
sar: &mockReviewer{},
|
||||
admitter: admitter{
|
||||
sar: &mockReviewer{},
|
||||
},
|
||||
}
|
||||
|
||||
oldClusterBytes, err := json.Marshal(tt.oldCluster)
|
||||
|
|
@ -85,7 +87,10 @@ func TestAdmit(t *testing.T) {
|
|||
newClusterBytes, err := json.Marshal(tt.newCluster)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res, err := v.Admit(&admission.Request{
|
||||
admitters := v.Admitters()
|
||||
assert.Len(t, admitters, 1)
|
||||
|
||||
res, err := admitters[0].Admit(&admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: newClusterBytes,
|
||||
|
|
@ -30,15 +30,16 @@ func NewValidator(crtb *resolvers.CRTBRuleResolver, defaultResolver k8validation
|
|||
roleTemplateResolver *auth.RoleTemplateResolver) *Validator {
|
||||
resolver := resolvers.NewAggregateRuleResolver(defaultResolver, crtb)
|
||||
return &Validator{
|
||||
resolver: resolver,
|
||||
roleTemplateResolver: roleTemplateResolver,
|
||||
admitter: admitter{
|
||||
resolver: resolver,
|
||||
roleTemplateResolver: roleTemplateResolver,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validator conforms to the webhook.Handler interface and is used for validating request for clusteroletemplatebindings.
|
||||
type Validator struct {
|
||||
resolver k8validation.AuthorizationRuleResolver
|
||||
roleTemplateResolver *auth.RoleTemplateResolver
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -56,9 +57,19 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.NamespacedScope, v.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate clusterRoleTemplateBindings.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
resolver k8validation.AuthorizationRuleResolver
|
||||
roleTemplateResolver *auth.RoleTemplateResolver
|
||||
}
|
||||
|
||||
// Admit is the entrypoint for the validator. Admit will return an error if it unable to process the request.
|
||||
// If this function is called without NewValidator(..) calls will panic.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("clusterRoleTemplateBindingValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
@ -87,7 +98,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
|
||||
if request.Operation == admissionv1.Create {
|
||||
if err = v.validateCreateFields(crtb); err != nil {
|
||||
if err = a.validateCreateFields(crtb); err != nil {
|
||||
return &admissionv1.AdmissionResponse{
|
||||
Result: &metav1.Status{
|
||||
Status: "Failure",
|
||||
|
|
@ -100,7 +111,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
}
|
||||
|
||||
roleTemplate, err := v.roleTemplateResolver.RoleTemplateCache().Get(crtb.RoleTemplateName)
|
||||
roleTemplate, err := a.roleTemplateResolver.RoleTemplateCache().Get(crtb.RoleTemplateName)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return &admissionv1.AdmissionResponse{Allowed: true}, nil
|
||||
|
|
@ -108,12 +119,12 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
return nil, fmt.Errorf("failed to get roletemplate '%s': %w", crtb.RoleTemplateName, err)
|
||||
}
|
||||
|
||||
rules, err := v.roleTemplateResolver.RulesFromTemplate(roleTemplate)
|
||||
rules, err := a.roleTemplateResolver.RulesFromTemplate(roleTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve rules from roletemplate '%s': %w", crtb.RoleTemplateName, err)
|
||||
}
|
||||
response := &admissionv1.AdmissionResponse{}
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, crtb.ClusterName, v.resolver))
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, crtb.ClusterName, a.resolver))
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
@ -144,7 +155,7 @@ func validateUpdateFields(oldCRTB, newCRTB *apisv3.ClusterRoleTemplateBinding) e
|
|||
}
|
||||
|
||||
// validateCreateFields checks if all required fields are present and valid.
|
||||
func (v *Validator) validateCreateFields(newCRTB *apisv3.ClusterRoleTemplateBinding) error {
|
||||
func (a *admitter) validateCreateFields(newCRTB *apisv3.ClusterRoleTemplateBinding) error {
|
||||
hasUserTarget := newCRTB.UserName != "" || newCRTB.UserPrincipalName != ""
|
||||
hasGroupTarget := newCRTB.GroupName != "" || newCRTB.GroupPrincipalName != ""
|
||||
|
||||
|
|
@ -156,7 +167,7 @@ func (v *Validator) validateCreateFields(newCRTB *apisv3.ClusterRoleTemplateBind
|
|||
return fmt.Errorf("missing required field 'clusterName': %w", admission.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
roleTemplate, err := v.roleTemplateResolver.RoleTemplateCache().Get(newCRTB.RoleTemplateName)
|
||||
roleTemplate, err := a.roleTemplateResolver.RoleTemplateCache().Get(newCRTB.RoleTemplateName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown reference roleTemplate '%s': %w", newCRTB.RoleTemplateName, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,7 +244,9 @@ func (c *ClusterRoleTemplateBindingSuite) Test_PrivilegeEscalation() {
|
|||
test := tests[i]
|
||||
c.Run(test.name, func() {
|
||||
req := createCRTBRequest(c.T(), test.args.oldCRTB(), test.args.newCRTB(), test.args.username)
|
||||
resp, err := validator.Admit(req)
|
||||
admitters := validator.Admitters()
|
||||
assert.Len(c.T(), admitters, 1)
|
||||
resp, err := admitters[0].Admit(req)
|
||||
c.NoError(err, "Admit failed")
|
||||
if resp.Allowed != test.allowed {
|
||||
c.Failf("Response was incorrectly validated", "Wanted response.Allowed = '%v' got %v: result=%+v", test.allowed, resp.Allowed, resp.Result)
|
||||
|
|
@ -544,7 +546,9 @@ func (c *ClusterRoleTemplateBindingSuite) Test_UpdateValidation() {
|
|||
c.Run(test.name, func() {
|
||||
c.T().Parallel()
|
||||
req := createCRTBRequest(c.T(), test.args.oldCRTB(), test.args.newCRTB(), test.args.username)
|
||||
resp, err := validator.Admit(req)
|
||||
admitters := validator.Admitters()
|
||||
assert.Len(c.T(), admitters, 1)
|
||||
resp, err := admitters[0].Admit(req)
|
||||
c.NoError(err, "Admit failed")
|
||||
if resp.Allowed != test.allowed {
|
||||
c.Failf("Response was incorrectly validated", "Wanted response.Allowed = '%v' got %v: result=%+v", test.allowed, resp.Allowed, resp.Result)
|
||||
|
|
@ -704,7 +708,9 @@ func (c *ClusterRoleTemplateBindingSuite) Test_Create() {
|
|||
c.Run(test.name, func() {
|
||||
c.T().Parallel()
|
||||
req := createCRTBRequest(c.T(), test.args.oldCRTB(), test.args.newCRTB(), test.args.username)
|
||||
resp, err := validator.Admit(req)
|
||||
admitters := validator.Admitters()
|
||||
assert.Len(c.T(), admitters, 1)
|
||||
resp, err := admitters[0].Admit(req)
|
||||
c.NoError(err, "Admit failed")
|
||||
if resp.Allowed != test.allowed {
|
||||
c.Failf("Response was incorrectly validated", "Wanted response.Allowed = '%v' got %v: result=%+v", test.allowed, resp.Allowed, resp.Result)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,16 @@ var gvr = schema.GroupVersionResource{
|
|||
}
|
||||
|
||||
// Validator for validating features.
|
||||
type Validator struct{}
|
||||
type Validator struct {
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// NewValidator returns a new validator for features.
|
||||
func NewValidator() *Validator {
|
||||
return &Validator{
|
||||
admitter: admitter{},
|
||||
}
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
func (v *Validator) GVR() schema.GroupVersionResource {
|
||||
|
|
@ -39,8 +48,15 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*valWebhook}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate features.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct{}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("featureValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,15 @@ var gvr = schema.GroupVersionResource{
|
|||
// NewValidator returns a new validator used for validation globalRoles.
|
||||
func NewValidator(resolver validation.AuthorizationRuleResolver) *Validator {
|
||||
return &Validator{
|
||||
resolver: resolver,
|
||||
admitter: admitter{
|
||||
resolver: resolver,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validator implements admission.ValidatingAdmissionHandler
|
||||
// Validator implements admission.ValidatingAdmissionHandler.
|
||||
type Validator struct {
|
||||
resolver validation.AuthorizationRuleResolver
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -47,9 +49,18 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate globalRoles.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
resolver validation.AuthorizationRuleResolver
|
||||
}
|
||||
|
||||
// Admit is the entrypoint for the validator. Admit will return an error if it unable to process the request.
|
||||
// If this function is called without NewValidator(..) calls will panic.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("globalRoleValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
@ -82,7 +93,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
|
||||
response := &admissionv1.AdmissionResponse{}
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, newGR.Rules, "", v.resolver))
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, newGR.Rules, "", a.resolver))
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,15 +26,16 @@ var gvr = schema.GroupVersionResource{
|
|||
// NewValidator returns a new validator for GlobalRoleBindings.
|
||||
func NewValidator(grCache v3.GlobalRoleCache, resolver rbacvalidation.AuthorizationRuleResolver) *Validator {
|
||||
return &Validator{
|
||||
resolver: resolver,
|
||||
globalRoles: grCache,
|
||||
admitter: admitter{
|
||||
resolver: resolver,
|
||||
globalRoles: grCache,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validator is used to validate operations to GlobalRoleBindings.
|
||||
type Validator struct {
|
||||
resolver rbacvalidation.AuthorizationRuleResolver
|
||||
globalRoles v3.GlobalRoleCache
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -52,8 +53,18 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate globalRoleBindings.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
resolver rbacvalidation.AuthorizationRuleResolver
|
||||
globalRoles v3.GlobalRoleCache
|
||||
}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("globalRoleBindingValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
@ -63,7 +74,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
|
||||
// Pull the global role to get the rules
|
||||
globalRole, err := v.globalRoles.Get(newGRB.GlobalRoleName)
|
||||
globalRole, err := a.globalRoles.Get(newGRB.GlobalRoleName)
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
|
|
@ -93,7 +104,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
|
||||
response := &admissionv1.AdmissionResponse{}
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, globalRole.Rules, "", v.resolver))
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, globalRole.Rules, "", a.resolver))
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ var gvr = schema.GroupVersionResource{
|
|||
|
||||
// Validator validates the PodSecurityAdmissionConfigurationTemplate admission request.
|
||||
type Validator struct {
|
||||
ManagementClusterCache v3.ClusterCache
|
||||
provisioningClusterCache v1.ClusterCache
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -42,15 +41,18 @@ const (
|
|||
rancherRestrictedPSACTName = "rancher-restricted"
|
||||
)
|
||||
|
||||
// NewValidator returns a validator for PodSecurityAdmissionConfigurationTemplates
|
||||
// NewValidator returns a validator for PodSecurityAdmissionConfigurationTemplates.
|
||||
func NewValidator(managementCache v3.ClusterCache, provisioningCache v1.ClusterCache) *Validator {
|
||||
val := &Validator{
|
||||
adm := admitter{
|
||||
ManagementClusterCache: managementCache,
|
||||
provisioningClusterCache: provisioningCache,
|
||||
}
|
||||
val.ManagementClusterCache.AddIndexer(byPodSecurityAdmissionConfigurationName, byPodSecurityAdmissionConfigurationTemplateV3)
|
||||
val.provisioningClusterCache.AddIndexer(byPodSecurityAdmissionConfigurationName, byPodSecurityAdmissionConfigurationTemplateV1)
|
||||
return val
|
||||
adm.ManagementClusterCache.AddIndexer(byPodSecurityAdmissionConfigurationName, byPodSecurityAdmissionConfigurationTemplateV3)
|
||||
adm.provisioningClusterCache.AddIndexer(byPodSecurityAdmissionConfigurationName, byPodSecurityAdmissionConfigurationTemplateV1)
|
||||
|
||||
return &Validator{
|
||||
admitter: adm,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatingWebhook returns the ValidatingWebhook used for this CRD.
|
||||
|
|
@ -84,8 +86,18 @@ func (v *Validator) Operations() []admissionregistrationv1.OperationType {
|
|||
return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create, admissionregistrationv1.Delete}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate podsecurityadmissionconfigurationtemplate.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
ManagementClusterCache v3.ClusterCache
|
||||
provisioningClusterCache v1.ClusterCache
|
||||
}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (v *Validator) Admit(req *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(req *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("PodSecurityAdmissionConfigurationTemplate Admit", trace.Field{Key: "user", Value: req.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(2 * time.Second)
|
||||
|
||||
|
|
@ -97,7 +109,7 @@ func (v *Validator) Admit(req *admission.Request) (*admissionv1.AdmissionRespons
|
|||
|
||||
switch req.Operation {
|
||||
case admissionv1.Create, admissionv1.Update:
|
||||
err = v.validateConfiguration(newTemplate)
|
||||
err = a.validateConfiguration(newTemplate)
|
||||
if err != nil {
|
||||
resp.Result = &metav1.Status{
|
||||
Status: "Failure",
|
||||
|
|
@ -122,7 +134,7 @@ func (v *Validator) Admit(req *admission.Request) (*admissionv1.AdmissionRespons
|
|||
break
|
||||
}
|
||||
|
||||
clustersUsingTemplate, clusterType, err := v.handleDeletion(oldTemplate)
|
||||
clustersUsingTemplate, clusterType, err := a.handleDeletion(oldTemplate)
|
||||
if err != nil {
|
||||
// error encountered with indexer
|
||||
resp.Result = &metav1.Status{
|
||||
|
|
@ -159,18 +171,18 @@ func (v *Validator) Admit(req *admission.Request) (*admissionv1.AdmissionRespons
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (v *Validator) handleDeletion(oldTemplate *mgmtv3.PodSecurityAdmissionConfigurationTemplate) (clustersUsingTemplate int, clusterType string, err error) {
|
||||
func (a *admitter) handleDeletion(oldTemplate *mgmtv3.PodSecurityAdmissionConfigurationTemplate) (clustersUsingTemplate int, clusterType string, err error) {
|
||||
|
||||
// we can't allow templates to be deleted if they are being used by active clusters. Depending on the distro,
|
||||
// the template reference could be stored on the v1.Cluster or v3.Cluster.
|
||||
mgmtClusters, err := v.ManagementClusterCache.GetByIndex(byPodSecurityAdmissionConfigurationName, oldTemplate.Name)
|
||||
mgmtClusters, err := a.ManagementClusterCache.GetByIndex(byPodSecurityAdmissionConfigurationName, oldTemplate.Name)
|
||||
if err != nil {
|
||||
return 0, "management", fmt.Errorf("error encountered within management cluster indexer: %w", err)
|
||||
} else if len(mgmtClusters) > 0 {
|
||||
return len(mgmtClusters), "management", nil
|
||||
}
|
||||
|
||||
provClusters, err := v.provisioningClusterCache.GetByIndex(byPodSecurityAdmissionConfigurationName, oldTemplate.Name)
|
||||
provClusters, err := a.provisioningClusterCache.GetByIndex(byPodSecurityAdmissionConfigurationName, oldTemplate.Name)
|
||||
if err != nil {
|
||||
return 0, "provisioning", fmt.Errorf("error encountered within provisioning cluster indexer: %w", err)
|
||||
} else if len(provClusters) > 0 {
|
||||
|
|
@ -180,7 +192,7 @@ func (v *Validator) handleDeletion(oldTemplate *mgmtv3.PodSecurityAdmissionConfi
|
|||
return 0, "", nil
|
||||
}
|
||||
|
||||
func (v *Validator) validateConfiguration(configurationTemplate *mgmtv3.PodSecurityAdmissionConfigurationTemplate) error {
|
||||
func (a *admitter) validateConfiguration(configurationTemplate *mgmtv3.PodSecurityAdmissionConfigurationTemplate) error {
|
||||
defaults := configurationTemplate.Configuration.Defaults
|
||||
|
||||
// validate any provided defaults
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ var (
|
|||
|
||||
func TestAdmit(t *testing.T) {
|
||||
validator = Validator{
|
||||
ManagementClusterCache: mockMgmtCache{},
|
||||
provisioningClusterCache: mockProvisioningCache{},
|
||||
admitter: admitter{
|
||||
ManagementClusterCache: mockMgmtCache{},
|
||||
provisioningClusterCache: mockProvisioningCache{},
|
||||
},
|
||||
}
|
||||
validConfiguration := v3.PodSecurityAdmissionConfigurationTemplateSpec{
|
||||
Defaults: v3.PodSecurityAdmissionConfigurationTemplateDefaults{
|
||||
|
|
@ -117,7 +119,6 @@ func TestAdmit(t *testing.T) {
|
|||
wantAllowed: true,
|
||||
},
|
||||
}
|
||||
|
||||
validationTests = []validationTest{
|
||||
{
|
||||
testName: "Completely Valid Template Test",
|
||||
|
|
@ -549,7 +550,11 @@ func TestAdmit(t *testing.T) {
|
|||
t.Log(fmt.Errorf("failed to create DELETE request for PodSecurityAdmissionConfigurationTemplate object: %w", err))
|
||||
t.Fail()
|
||||
}
|
||||
resp, _ := validator.Admit(&req)
|
||||
admitters := validator.Admitters()
|
||||
if len(admitters) != 1 {
|
||||
t.Logf("wanted only one admitter but got = %d", len(admitters))
|
||||
}
|
||||
resp, _ := admitters[0].Admit(&req)
|
||||
if resp.Allowed != testcase.wantAllowed {
|
||||
t.Logf("wanted allowed = %t, got allowed = %t", testcase.wantAllowed, resp.Allowed)
|
||||
t.Fail()
|
||||
|
|
@ -564,7 +569,11 @@ func TestAdmit(t *testing.T) {
|
|||
t.Log(fmt.Errorf("failed to create CREATE request for PodSecurityAdmissionConfigurationTemplate object: %w", err))
|
||||
t.Fail()
|
||||
}
|
||||
resp, _ := validator.Admit(&req)
|
||||
admitters := validator.Admitters()
|
||||
if len(admitters) != 1 {
|
||||
t.Logf("wanted only one admitter but got = %d", len(admitters))
|
||||
}
|
||||
resp, _ := admitters[0].Admit(&req)
|
||||
if resp.Allowed != testcase.wantAllowed {
|
||||
t.Logf("wanted allowed = %t, got allowed = %t", testcase.wantAllowed, resp.Allowed)
|
||||
t.Fail()
|
||||
|
|
|
|||
|
|
@ -32,17 +32,17 @@ func NewValidator(prtb *resolvers.PRTBRuleResolver, crtb *resolvers.CRTBRuleReso
|
|||
clusterResolver := resolvers.NewAggregateRuleResolver(defaultResolver, crtb)
|
||||
projectResolver := resolvers.NewAggregateRuleResolver(defaultResolver, prtb)
|
||||
return &Validator{
|
||||
clusterResolver: clusterResolver,
|
||||
projectResolver: projectResolver,
|
||||
roleTemplateResolver: roleTemplateResolver,
|
||||
admitter: admitter{
|
||||
clusterResolver: clusterResolver,
|
||||
projectResolver: projectResolver,
|
||||
roleTemplateResolver: roleTemplateResolver,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validator validates PRTB admission request.
|
||||
type Validator struct {
|
||||
clusterResolver k8validation.AuthorizationRuleResolver
|
||||
projectResolver k8validation.AuthorizationRuleResolver
|
||||
roleTemplateResolver *auth.RoleTemplateResolver
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -60,9 +60,20 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.NamespacedScope, v.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate ProjectRoleTemplateBindings.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
clusterResolver k8validation.AuthorizationRuleResolver
|
||||
projectResolver k8validation.AuthorizationRuleResolver
|
||||
roleTemplateResolver *auth.RoleTemplateResolver
|
||||
}
|
||||
|
||||
// Admit is the entrypoint for the validator. Admit will return an error if it unable to process the request.
|
||||
// If this function is called without NewValidator(..) calls will panic.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("projectRoleTemplateBindingValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
@ -91,7 +102,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
|
||||
if request.Operation == admissionv1.Create {
|
||||
if err = v.validateCreateFields(prtb); err != nil {
|
||||
if err = a.validateCreateFields(prtb); err != nil {
|
||||
return &admissionv1.AdmissionResponse{
|
||||
Result: &metav1.Status{
|
||||
Status: "Failure",
|
||||
|
|
@ -106,7 +117,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
|
||||
clusterNS, projectNS := clusterFromProject(prtb.ProjectName)
|
||||
|
||||
roleTemplate, err := v.roleTemplateResolver.RoleTemplateCache().Get(prtb.RoleTemplateName)
|
||||
roleTemplate, err := a.roleTemplateResolver.RoleTemplateCache().Get(prtb.RoleTemplateName)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return &admissionv1.AdmissionResponse{
|
||||
|
|
@ -116,18 +127,18 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
return nil, fmt.Errorf("failed to get referenced roleTemplate '%s' for PRTB: %w", roleTemplate.Name, err)
|
||||
}
|
||||
|
||||
rules, err := v.roleTemplateResolver.RulesFromTemplate(roleTemplate)
|
||||
rules, err := a.roleTemplateResolver.RulesFromTemplate(roleTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rules from referenced roleTemplate '%s': %w", roleTemplate.Name, err)
|
||||
}
|
||||
|
||||
err = auth.ConfirmNoEscalation(request, rules, clusterNS, v.clusterResolver)
|
||||
err = auth.ConfirmNoEscalation(request, rules, clusterNS, a.clusterResolver)
|
||||
if err == nil {
|
||||
return &admissionv1.AdmissionResponse{Allowed: true}, nil
|
||||
}
|
||||
|
||||
response := &admissionv1.AdmissionResponse{}
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, projectNS, v.projectResolver))
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, projectNS, a.projectResolver))
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
@ -166,7 +177,7 @@ func validateUpdateFields(oldPRTB, newPRTB *apisv3.ProjectRoleTemplateBinding) e
|
|||
}
|
||||
|
||||
// validateCreateFields checks if all required fields are present and valid.
|
||||
func (v *Validator) validateCreateFields(newPRTB *apisv3.ProjectRoleTemplateBinding) error {
|
||||
func (a *admitter) validateCreateFields(newPRTB *apisv3.ProjectRoleTemplateBinding) error {
|
||||
hasUserTarget := newPRTB.UserName != "" || newPRTB.UserPrincipalName != ""
|
||||
hasGroupTarget := newPRTB.GroupName != "" || newPRTB.GroupPrincipalName != ""
|
||||
|
||||
|
|
@ -178,7 +189,7 @@ func (v *Validator) validateCreateFields(newPRTB *apisv3.ProjectRoleTemplateBind
|
|||
return fmt.Errorf("binding must have field projectName set: %w", admission.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
roleTemplate, err := v.roleTemplateResolver.RoleTemplateCache().Get(newPRTB.RoleTemplateName)
|
||||
roleTemplate, err := a.roleTemplateResolver.RoleTemplateCache().Get(newPRTB.RoleTemplateName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown reference roleTemplate '%s': %w", newPRTB.RoleTemplateName, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,9 @@ func (p *ProjectRoleTemplateBindingSuite) Test_PrivilegeEscalation() {
|
|||
p.Run(test.name, func() {
|
||||
p.T().Parallel()
|
||||
req := createPRTBRequest(p.T(), test.args.oldPRTB(), test.args.newPRTB(), test.args.username)
|
||||
resp, err := validator.Admit(req)
|
||||
admitters := validator.Admitters()
|
||||
p.Len(admitters, 1)
|
||||
resp, err := admitters[0].Admit(req)
|
||||
p.NoError(err, "Admit failed")
|
||||
if resp.Allowed != test.allowed {
|
||||
p.Failf("Response was incorrectly validated", "Wanted response.Allowed = '%v' got %v: result=%+v", test.allowed, resp.Allowed, resp.Result)
|
||||
|
|
@ -578,7 +580,9 @@ func (p *ProjectRoleTemplateBindingSuite) Test_UpdateValidation() {
|
|||
p.Run(test.name, func() {
|
||||
p.T().Parallel()
|
||||
req := createPRTBRequest(p.T(), test.args.oldPRTB(), test.args.newPRTB(), test.args.username)
|
||||
resp, err := validator.Admit(req)
|
||||
admitters := validator.Admitters()
|
||||
p.Len(admitters, 1)
|
||||
resp, err := admitters[0].Admit(req)
|
||||
p.NoError(err, "Admit failed")
|
||||
if resp.Allowed != test.allowed {
|
||||
p.Failf("Response was incorrectly validated", "Wanted response.Allowed = '%v' got %v: result=%+v", test.allowed, resp.Allowed, resp.Result)
|
||||
|
|
@ -741,7 +745,9 @@ func (p *ProjectRoleTemplateBindingSuite) Test_Create() {
|
|||
p.Run(test.name, func() {
|
||||
p.T().Parallel()
|
||||
req := createPRTBRequest(p.T(), test.args.oldPRTB(), test.args.newPRTB(), test.args.username)
|
||||
resp, err := validator.Admit(req)
|
||||
admitters := validator.Admitters()
|
||||
p.Len(admitters, 1)
|
||||
resp, err := admitters[0].Admit(req)
|
||||
p.NoError(err, "Admit failed")
|
||||
if resp.Allowed != test.allowed {
|
||||
p.Failf("Response was incorrectly validated", "Wanted response.Allowed = '%v' got %v: result=%+v", test.allowed, resp.Allowed, resp.Result)
|
||||
|
|
|
|||
|
|
@ -25,20 +25,21 @@ var roleTemplateGVR = schema.GroupVersionResource{
|
|||
Resource: "roletemplates",
|
||||
}
|
||||
|
||||
// NewValidator returns a new validator used for validating roleTemplates.
|
||||
func NewValidator(resolver validation.AuthorizationRuleResolver, roleTemplateResolver *auth.RoleTemplateResolver,
|
||||
sar authorizationv1.SubjectAccessReviewInterface) *Validator {
|
||||
return &Validator{
|
||||
resolver: resolver,
|
||||
roleTemplateResolver: roleTemplateResolver,
|
||||
sar: sar,
|
||||
admitter: admitter{
|
||||
resolver: resolver,
|
||||
roleTemplateResolver: roleTemplateResolver,
|
||||
sar: sar,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validator for validating roleTemplates.
|
||||
type Validator struct {
|
||||
resolver validation.AuthorizationRuleResolver
|
||||
roleTemplateResolver *auth.RoleTemplateResolver
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -56,8 +57,19 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate RoleTemplates.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct {
|
||||
resolver validation.AuthorizationRuleResolver
|
||||
roleTemplateResolver *auth.RoleTemplateResolver
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("Validator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
@ -72,7 +84,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
return &admissionv1.AdmissionResponse{Allowed: true}, nil
|
||||
}
|
||||
//check for circular references produced by this role
|
||||
circularTemplate, err := v.checkCircularRef(roleTemplate)
|
||||
circularTemplate, err := a.checkCircularRef(roleTemplate)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error when trying to check for a circular ref: %s", err)
|
||||
return nil, err
|
||||
|
|
@ -89,7 +101,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}, nil
|
||||
}
|
||||
|
||||
rules, err := v.roleTemplateResolver.RulesFromTemplate(roleTemplate)
|
||||
rules, err := a.roleTemplateResolver.RulesFromTemplate(roleTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -109,7 +121,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
}
|
||||
}
|
||||
|
||||
allowed, err := auth.EscalationAuthorized(request, roleTemplateGVR, v.sar, "")
|
||||
allowed, err := auth.EscalationAuthorized(request, roleTemplateGVR, a.sar, "")
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to check for the 'escalate' verb on RoleTemplates: %v", err)
|
||||
}
|
||||
|
|
@ -118,7 +130,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
return &admissionv1.AdmissionResponse{Allowed: true}, nil
|
||||
}
|
||||
response := &admissionv1.AdmissionResponse{}
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, "", v.resolver))
|
||||
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, "", a.resolver))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +138,7 @@ func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionRes
|
|||
// for example - template 1 inherits template 2 which inherits template 1. These setups can cause high cpu usage/crashes
|
||||
// If a circular ref was found, returns the first template which inherits this role template. Returns nil otherwise.
|
||||
// Can return an error if any role template was not found.
|
||||
func (v *Validator) checkCircularRef(template *v3.RoleTemplate) (*v3.RoleTemplate, error) {
|
||||
func (a *admitter) checkCircularRef(template *v3.RoleTemplate) (*v3.RoleTemplate, error) {
|
||||
seen := make(map[string]struct{})
|
||||
queue := []*v3.RoleTemplate{template}
|
||||
for len(queue) > 0 {
|
||||
|
|
@ -141,7 +153,7 @@ func (v *Validator) checkCircularRef(template *v3.RoleTemplate) (*v3.RoleTemplat
|
|||
}
|
||||
// if we haven't seen this yet, we add to the queue to process
|
||||
if _, ok := seen[inherited]; !ok {
|
||||
newTemplate, err := v.roleTemplateResolver.RoleTemplateCache().Get(inherited)
|
||||
newTemplate, err := a.roleTemplateResolver.RoleTemplateCache().Get(inherited)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get roletemplate %s with error %w", inherited, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func TestCheckCircularRef(t *testing.T) {
|
|||
}
|
||||
inputRole := createNestedRoleTemplate(rtName, mockCache, testCase.depth, testCase.circleDepth, testCase.errorDepth)
|
||||
validator := createValidator(mockCache)
|
||||
result, err := validator.checkCircularRef(inputRole)
|
||||
result, err := validator.admitter.checkCircularRef(inputRole)
|
||||
if testCase.errDesired {
|
||||
assert.NotNil(t, err, "checkCircularRef(), expected err but did not get an error")
|
||||
} else {
|
||||
|
|
@ -139,6 +139,8 @@ func createRoleTemplate(name string, rules []rbacv1.PolicyRule) *v3.RoleTemplate
|
|||
|
||||
func createValidator(cache controllerv3.RoleTemplateCache) *Validator {
|
||||
return &Validator{
|
||||
roleTemplateResolver: auth.NewRoleTemplateResolver(cache, nil),
|
||||
admitter: admitter{
|
||||
roleTemplateResolver: auth.NewRoleTemplateResolver(cache, nil),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,18 +34,17 @@ var fleetNameRegex = regexp.MustCompile("^[^-][-a-z0-9]+$")
|
|||
// NewProvisioningClusterValidator returns a new validator for provisioning clusters
|
||||
func NewProvisioningClusterValidator(client *clients.Clients) *ProvisioningClusterValidator {
|
||||
return &ProvisioningClusterValidator{
|
||||
sar: client.K8s.AuthorizationV1().SubjectAccessReviews(),
|
||||
mgmtClusterClient: client.Management.Cluster(),
|
||||
secretCache: client.Core.Secret().Cache(),
|
||||
psactCache: client.Management.PodSecurityAdmissionConfigurationTemplate().Cache(),
|
||||
admitter: provisioningAdmitter{
|
||||
sar: client.K8s.AuthorizationV1().SubjectAccessReviews(),
|
||||
mgmtClusterClient: client.Management.Cluster(),
|
||||
secretCache: client.Core.Secret().Cache(),
|
||||
psactCache: client.Management.PodSecurityAdmissionConfigurationTemplate().Cache(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ProvisioningClusterValidator struct {
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
mgmtClusterClient v3.ClusterClient
|
||||
secretCache corev1controller.SecretCache
|
||||
psactCache v3.PodSecurityAdmissionConfigurationTemplateCache
|
||||
admitter provisioningAdmitter
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
|
|
@ -63,8 +62,20 @@ func (p *ProvisioningClusterValidator) ValidatingWebhook(clientConfig admissionr
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(p, clientConfig, admissionregistrationv1.NamespacedScope, p.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate provisioning clusters.
|
||||
func (p *ProvisioningClusterValidator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&p.admitter}
|
||||
}
|
||||
|
||||
type provisioningAdmitter struct {
|
||||
sar authorizationv1.SubjectAccessReviewInterface
|
||||
mgmtClusterClient v3.ClusterClient
|
||||
secretCache corev1controller.SecretCache
|
||||
psactCache v3.PodSecurityAdmissionConfigurationTemplateCache
|
||||
}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (p *ProvisioningClusterValidator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (p *provisioningAdmitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("provisioningClusterValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
oldCluster, cluster, err := objectsv1.ClusterOldAndNewFromRequest(&request.AdmissionRequest)
|
||||
|
|
@ -98,7 +109,7 @@ func (p *ProvisioningClusterValidator) Admit(request *admission.Request) (*admis
|
|||
return response, nil
|
||||
}
|
||||
|
||||
func (p *ProvisioningClusterValidator) validateCloudCredentialAccess(request *admission.Request, response *admissionv1.AdmissionResponse, oldCluster, newCluster *v1.Cluster) error {
|
||||
func (p *provisioningAdmitter) validateCloudCredentialAccess(request *admission.Request, response *admissionv1.AdmissionResponse, oldCluster, newCluster *v1.Cluster) error {
|
||||
if newCluster.Spec.CloudCredentialSecretName == "" ||
|
||||
oldCluster.Spec.CloudCredentialSecretName == newCluster.Spec.CloudCredentialSecretName {
|
||||
return nil
|
||||
|
|
@ -149,7 +160,7 @@ func getCloudCredentialSecretInfo(namespace, name string) (string, string) {
|
|||
return namespace, name
|
||||
}
|
||||
|
||||
func (p *ProvisioningClusterValidator) validateClusterName(request *admission.Request, response *admissionv1.AdmissionResponse, cluster *v1.Cluster) error {
|
||||
func (p *provisioningAdmitter) validateClusterName(request *admission.Request, response *admissionv1.AdmissionResponse, cluster *v1.Cluster) error {
|
||||
if request.Operation != admissionv1.Create {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -174,7 +185,7 @@ func (p *ProvisioningClusterValidator) validateClusterName(request *admission.Re
|
|||
}
|
||||
|
||||
// validatePSACT validate if the cluster and underlying secret are configured properly when PSACT is enabled or disabled
|
||||
func (p *ProvisioningClusterValidator) validatePSACT(request *admission.Request, response *admissionv1.AdmissionResponse, cluster *v1.Cluster) error {
|
||||
func (p *provisioningAdmitter) validatePSACT(request *admission.Request, response *admissionv1.AdmissionResponse, cluster *v1.Cluster) error {
|
||||
if cluster.Name == "local" || cluster.Spec.RKEConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,16 @@ var gvr = schema.GroupVersionResource{
|
|||
}
|
||||
|
||||
// Validator for validating machineconfigs.
|
||||
type Validator struct{}
|
||||
type Validator struct {
|
||||
admitter admitter
|
||||
}
|
||||
|
||||
// NewValidator returns a new machineconfig validator.
|
||||
func NewValidator() *Validator {
|
||||
return &Validator{
|
||||
admitter: admitter{},
|
||||
}
|
||||
}
|
||||
|
||||
// GVR returns the GroupVersionKind for this CRD.
|
||||
func (v *Validator) GVR() schema.GroupVersionResource {
|
||||
|
|
@ -34,8 +43,15 @@ func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.Webho
|
|||
return []admissionregistrationv1.ValidatingWebhook{*admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.NamespacedScope, v.Operations())}
|
||||
}
|
||||
|
||||
// Admitters returns the admitter objects used to validate machineconfigs.
|
||||
func (v *Validator) Admitters() []admission.Admitter {
|
||||
return []admission.Admitter{&v.admitter}
|
||||
}
|
||||
|
||||
type admitter struct{}
|
||||
|
||||
// Admit handles the webhook admission request sent to this webhook.
|
||||
func (v *Validator) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
|
||||
listTrace := trace.New("machineConfigValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
|
||||
defer listTrace.LogIfLong(admission.SlowTraceDuration)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import (
|
|||
// Validation returns a list of all ValidatingAdmissionHandlers used by the webhook.
|
||||
func Validation(clients *clients.Clients) ([]admission.ValidatingAdmissionHandler, error) {
|
||||
handlers := []admission.ValidatingAdmissionHandler{
|
||||
&feature.Validator{},
|
||||
feature.NewValidator(),
|
||||
managementCluster.NewValidator(clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.Management.PodSecurityAdmissionConfigurationTemplate().Cache()),
|
||||
provisioningCluster.NewProvisioningClusterValidator(clients),
|
||||
&machineconfig.Validator{},
|
||||
machineconfig.NewValidator(),
|
||||
nshandler.NewValidator(clients.K8s.AuthorizationV1().SubjectAccessReviews()),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ func listenAndServe(ctx context.Context, clients *clients.Clients, validators []
|
|||
|
||||
logrus.Debug("Creating Webhook routes")
|
||||
for _, webhook := range validators {
|
||||
route := router.HandleFunc(admission.Path(validationPath, webhook), admission.NewHandlerFunc(webhook))
|
||||
route := router.HandleFunc(admission.Path(validationPath, webhook), admission.NewValidatingHandlerFunc(webhook))
|
||||
path, _ := route.GetPathTemplate()
|
||||
logrus.Debugf("creating route: %s", path)
|
||||
}
|
||||
for _, webhook := range mutators {
|
||||
route := router.HandleFunc(admission.Path(mutationPath, webhook), admission.NewHandlerFunc(webhook))
|
||||
route := router.HandleFunc(admission.Path(mutationPath, webhook), admission.NewMutatingHandlerFunc(webhook))
|
||||
path, _ := route.GetPathTemplate()
|
||||
logrus.Debugf("creating route: %s", path)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue