Skip to content

Commit

Permalink
RemoteUserBinding permissions on RemoteUser webhook check
Browse files Browse the repository at this point in the history
Signed-off-by: Damien Dassieu <[email protected]>
  • Loading branch information
damsien committed Jan 2, 2025
1 parent ad7d0fe commit 5274d72
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 52 deletions.
21 changes: 21 additions & 0 deletions charts/0.3.1/templates/webhook/webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ webhooks:
resources:
- remotesyncers
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-crd-service
namespace: {{ .Release.Namespace }}
path: /syngit-v1beta2-remoteuserbinding-permissions
failurePolicy: Fail
name: vremoteuserbindings-permissions.v1beta2.syngit.io
rules:
- apiGroups:
- syngit.io
apiVersions:
- v1beta2
operations:
- CREATE
- UPDATE
- DELETE
resources:
- remoteuserbindings
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down
4 changes: 4 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ func main() {
Client: mgr.GetClient(),
Decoder: admission.NewDecoder(mgr.GetScheme()),
}})
mgr.GetWebhookServer().Register("/syngit-v1beta2-remoteuserbinding-permissions", &webhook.Admission{Handler: &webhooksyngitv1beta2.RemoteUserBindingPermissionsWebhookHandler{
Client: mgr.GetClient(),
Decoder: admission.NewDecoder(mgr.GetScheme()),
}})
mgr.GetWebhookServer().Register("/syngit-v1beta2-remotesyncer-rules-permissions", &webhook.Admission{Handler: &webhooksyngitv1beta2.RemoteSyncerWebhookHandler{
Client: mgr.GetClient(),
Decoder: admission.NewDecoder(mgr.GetScheme()),
Expand Down
21 changes: 21 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,27 @@ webhooks:
resources:
- remoteusers
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /syngit-v1beta2-remoteuserbinding-permissions
failurePolicy: Fail
name: vremoteuserbindings-permissions.v1beta2.syngit.io
rules:
- apiGroups:
- syngit.io
apiVersions:
- v1beta2
operations:
- CREATE
- UPDATE
- DELETE
resources:
- remoteuserbindings
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down
12 changes: 2 additions & 10 deletions internal/webhook/v1beta2/remotesyncer_rules_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,8 @@ func (rswh *RemoteSyncerWebhookHandler) Handle(ctx context.Context, req admissio

rs := &syngit.RemoteSyncer{}

if string(req.Operation) != "DELETE" { //nolint:goconst
err := rswh.Decoder.Decode(req, rs)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
} else {
err := rswh.Decoder.DecodeRaw(req.OldObject, rs)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if err := utils.GetObjectFromWebhookRequest(rswh.Decoder, rs, req); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

if authorized, forbiddenResources, err := rswh.hasRightResourcesPermissions(*rs, user); err != nil {
Expand Down
13 changes: 3 additions & 10 deletions internal/webhook/v1beta2/remoteuser_secrets_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

syngit "github.com/syngit-org/syngit/pkg/api/v1beta2"
utils "github.com/syngit-org/syngit/pkg/utils"
authv1 "k8s.io/api/authorization/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
Expand All @@ -28,16 +29,8 @@ func (ruwh *RemoteUserPermissionsWebhookHandler) Handle(ctx context.Context, req

ru := &syngit.RemoteUser{}

if string(req.Operation) != "DELETE" { //nolint:goconst
err := ruwh.Decoder.Decode(req, ru)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
} else {
err := ruwh.Decoder.DecodeRaw(req.OldObject, ru)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if err := utils.GetObjectFromWebhookRequest(ruwh.Decoder, ru, req); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

namespace := ru.GetNamespace()
Expand Down
68 changes: 68 additions & 0 deletions internal/webhook/v1beta2/remoteuserbinding_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package v1beta2

import (
"context"
"fmt"
"net/http"

syngit "github.com/syngit-org/syngit/pkg/api/v1beta2"
utils "github.com/syngit-org/syngit/pkg/utils"
authv1 "k8s.io/api/authorization/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

/*
Handle webhook and get kubernetes user id
*/

type RemoteUserBindingPermissionsWebhookHandler struct {
Client client.Client
Decoder *admission.Decoder
}

// +kubebuilder:webhook:path=/syngit-v1beta2-remoteuserbinding-permissions,mutating=false,failurePolicy=fail,sideEffects=None,groups=syngit.io,resources=remoteuserbindings,verbs=create;update;delete,versions=v1beta2,admissionReviewVersions=v1,name=vremoteuserbindings-permissions.v1beta2.syngit.io

func (rubwh *RemoteUserBindingPermissionsWebhookHandler) Handle(ctx context.Context, req admission.Request) admission.Response {

user := req.DeepCopy().UserInfo

rub := &syngit.RemoteUserBinding{}

if err := utils.GetObjectFromWebhookRequest(rubwh.Decoder, rub, req); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

namespace := rub.GetNamespace()
for _, ru := range rub.Spec.RemoteRefs {
if ru.Namespace != "" {
namespace = ru.Namespace
}
sar := &authv1.SubjectAccessReview{
Spec: authv1.SubjectAccessReviewSpec{
User: user.Username,
Groups: user.Groups,
ResourceAttributes: &authv1.ResourceAttributes{
Namespace: namespace,
Verb: "get",
Group: "syngit.io",
Version: "v1beta2",
Resource: "remoteusers",
Name: ru.Name,
},
},
}

err := rubwh.Client.Create(context.Background(), sar)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

if !sar.Status.Allowed {
return admission.Denied(fmt.Sprintf("The user %s is not allowed to get the referenced remoteuser: %s", user, ru.Name))
}

}

return admission.Allowed(fmt.Sprintf("The user %s is allowed to get all the referenced remoteusers.", user))
}
21 changes: 0 additions & 21 deletions pkg/utils/parser.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package utils

import (
"fmt"

admissionv1 "k8s.io/api/admissionregistration/v1"
)

// Remove the specified path from the json object
// Path examples :

Expand Down Expand Up @@ -86,18 +80,3 @@ func ExcludedFieldsFromJson(data map[string]interface{}, path string) {
data = next
}
}

func OperationToVerb(operation admissionv1.OperationType) ([]string, error) {
switch operation {
case admissionv1.Create:
return []string{"create"}, nil
case admissionv1.Delete:
return []string{"delete"}, nil
case admissionv1.Update:
return []string{"update", "patch"}, nil
case admissionv1.Connect:
return []string{"connect"}, nil
default:
return nil, fmt.Errorf("unsupported operation: %v", operation)
}
}
40 changes: 40 additions & 0 deletions pkg/utils/webhooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package utils

import (
"fmt"

admissionv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

func OperationToVerb(operation admissionv1.OperationType) ([]string, error) {
switch operation {
case admissionv1.Create:
return []string{"create"}, nil
case admissionv1.Delete:
return []string{"delete"}, nil
case admissionv1.Update:
return []string{"update", "patch"}, nil
case admissionv1.Connect:
return []string{"connect"}, nil
default:
return nil, fmt.Errorf("unsupported operation: %v", operation)
}
}

func GetObjectFromWebhookRequest(decoder *admission.Decoder, obj runtime.Object, req admission.Request) error {

if string(req.Operation) != "DELETE" {
err := decoder.Decode(req, obj)
if err != nil {
return err
}
} else {
err := decoder.DecodeRaw(req.OldObject, obj)
if err != nil {
return err
}
}
return nil
}
138 changes: 138 additions & 0 deletions test/e2e/syngit/11_remoteuserbinding_permissions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e_syngit

import (
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
syngit "github.com/syngit-org/syngit/pkg/api/v1beta2"
. "github.com/syngit-org/syngit/test/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
)

var _ = Describe("11 RemoteUserBinding permissions checker", func() {

const (
remoteUserBrookName = "remoteuser-brook"
remoteUserBindingBrookName = "remoteuserbinding-brook"
)

It("should deny the remoteuserbinding creation", func() {

By("adding syngit to scheme")
err := syngit.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

Wait5()
By("creating the RemoteUser")
brookSecretName := string(Brook) + "-creds"
remoteUserBrook := &syngit.RemoteUser{
ObjectMeta: metav1.ObjectMeta{
Name: remoteUserBrookName,
Namespace: namespace,
},
Spec: syngit.RemoteUserSpec{
Email: "[email protected]",
GitBaseDomainFQDN: gitP1Fqdn,
SecretRef: corev1.SecretReference{
Name: brookSecretName,
},
},
}
Eventually(func() bool {
err := sClient.As(Brook).CreateOrUpdate(remoteUserBrook)
return err == nil
}, timeout, interval).Should(BeTrue())

Wait5()
By("creating the RemoteUserBinding using a not allowed remoteuser name")
remoteUserBindingBrook := &syngit.RemoteUserBinding{
ObjectMeta: metav1.ObjectMeta{
Name: remoteUserBindingBrookName,
Namespace: namespace,
},
Spec: syngit.RemoteUserBindingSpec{
RemoteRefs: []corev1.ObjectReference{
{
Name: "not-allowed-remoteuser-name",
Namespace: namespace,
},
},
},
}
Eventually(func() bool {
err := sClient.As(Brook).CreateOrUpdate(remoteUserBindingBrook)
return err != nil && strings.Contains(err.Error(), rubPermissionsDeniedMessage)
}, timeout, interval).Should(BeTrue())

})

It("should create the remoteuserbinding", func() {

By("adding syngit to scheme")
err := syngit.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

Wait5()
By("creating the RemoteUser using an allowed secret name")
brookSecretName := string(Brook) + "-creds"
remoteUserBrook := &syngit.RemoteUser{
ObjectMeta: metav1.ObjectMeta{
Name: remoteUserBrookName,
Namespace: namespace,
},
Spec: syngit.RemoteUserSpec{
Email: "[email protected]",
GitBaseDomainFQDN: gitP1Fqdn,
SecretRef: corev1.SecretReference{
Name: brookSecretName,
},
},
}
Eventually(func() bool {
err := sClient.As(Brook).CreateOrUpdate(remoteUserBrook)
return err == nil
}, timeout, interval).Should(BeTrue())

Wait5()
By("creating the RemoteUserBinding")
remoteUserBindingBrook := &syngit.RemoteUserBinding{
ObjectMeta: metav1.ObjectMeta{
Name: remoteUserBindingBrookName,
Namespace: namespace,
},
Spec: syngit.RemoteUserBindingSpec{
RemoteRefs: []corev1.ObjectReference{
{
Name: remoteUserBrookName,
Namespace: namespace,
},
},
},
}
Eventually(func() bool {
err := sClient.As(Brook).CreateOrUpdate(remoteUserBindingBrook)
return err == nil
}, timeout, interval).Should(BeTrue())

})

})
Loading

0 comments on commit 5274d72

Please sign in to comment.