Skip to content

Commit

Permalink
Merge commit from fork
Browse files Browse the repository at this point in the history
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
  • Loading branch information
oliverbaehler authored Aug 20, 2024
1 parent 1d9fcc7 commit d620b04
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 5 deletions.
3 changes: 2 additions & 1 deletion charts/capsule/templates/configuration-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ spec:
nodeMetadata:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}

119 changes: 119 additions & 0 deletions e2e/namespace_hijacking_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//go:build e2e

// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0

package e2e

import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"math/rand"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

var _ = Describe("creating several Namespaces for a Tenant", func() {
tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "capsule-ns-attack-1",
},
Spec: capsulev1beta2.TenantSpec{
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "charlie",
Kind: "User",
},
{
Kind: "ServiceAccount",
Name: "system:serviceaccount:attacker-system:attacker",
},
},
},
}

kubeSystem := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
},
}
JustBeforeEach(func() {
EventuallyCreation(func() (err error) {
tnt.ResourceVersion = ""
err = k8sClient.Create(context.TODO(), tnt)

return
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())

})

It("Can't hijack offlimits namespace", func() {
tenant := &capsulev1beta2.Tenant{}
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tenant)).Should(Succeed())

// Get the namespace
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed())

for _, owner := range tnt.Spec.Owners {
cs := ownerClient(owner)

patch := []byte(fmt.Sprintf(`{"metadata":{"ownerReferences":[{"apiVersion":"%s/%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, capsulev1beta2.GroupVersion.Group, capsulev1beta2.GroupVersion.Version, tenant.GetName(), tenant.GetUID()))

_, err := cs.CoreV1().Namespaces().Patch(context.TODO(), kubeSystem.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{})
Expect(err).To(HaveOccurred())

}
})

It("Owners can create and attempt to patch new namespaces but patches should not be applied", func() {
for _, owner := range tnt.Spec.Owners {
cs := ownerClient(owner)

// Each owner creates a new namespace
ns := NewNamespace("")
NamespaceCreation(ns, owner, defaultTimeoutInterval).Should(Succeed())

// Attempt to patch the owner references of the new namespace
tenant := &capsulev1beta2.Tenant{}
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tenant)).Should(Succeed())

randomUID := types.UID(fmt.Sprintf("%d", rand.Int()))
randomName := fmt.Sprintf("random-tenant-%d", rand.Int())
patch := []byte(fmt.Sprintf(`{"metadata":{"ownerReferences":[{"apiVersion":"%s/%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, capsulev1beta2.GroupVersion.Group, capsulev1beta2.GroupVersion.Version, randomName, randomUID))

_, err := cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{})
Expect(err).ToNot(HaveOccurred())

retrievedNs := &corev1.Namespace{}
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.Name}, retrievedNs)).Should(Succeed())

// Check if the namespace has an owner reference with the specific UID and name
hasSpecificOwnerRef := false
for _, ownerRef := range retrievedNs.OwnerReferences {
if ownerRef.UID == randomUID && ownerRef.Name == randomName {
hasSpecificOwnerRef = true
break
}
}
Expect(hasSpecificOwnerRef).To(BeFalse(), "Namespace should not have owner reference with UID %s and name %s", randomUID, randomName)

hasOriginReference := false
for _, ownerRef := range retrievedNs.OwnerReferences {
if ownerRef.UID == tenant.GetUID() && ownerRef.Name == tenant.GetName() {
hasOriginReference = true
break
}
}
Expect(hasOriginReference).To(BeTrue(), "Namespace should have origin reference", tenant.GetUID(), tenant.GetName())
}
})

})
35 changes: 31 additions & 4 deletions pkg/webhook/ownerreference/patching.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/fields"
"net/http"
"sort"
"strings"
Expand Down Expand Up @@ -49,15 +50,26 @@ func (h *handler) OnDelete(client.Client, admission.Decoder, record.EventRecorde
}
}

func (h *handler) OnUpdate(_ client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
return func(_ context.Context, req admission.Request) *admission.Response {
func (h *handler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
oldNs := &corev1.Namespace{}
if err := decoder.DecodeRaw(req.OldObject, oldNs); err != nil {
return utils.ErroredResponse(err)
}

if len(oldNs.OwnerReferences) == 0 {
return nil
tntList := &capsulev1beta2.TenantList{}
if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", oldNs.Name),
}); err != nil {
return utils.ErroredResponse(err)
}

if !h.namespaceIsOwned(oldNs, tntList, req) {
recorder.Eventf(oldNs, corev1.EventTypeWarning, "OfflimitNamespace", "Namespace %s can not be patched", oldNs.GetName())

response := admission.Denied("Denied patch request for this namespace")

return &response
}

newNs := &corev1.Namespace{}
Expand Down Expand Up @@ -101,6 +113,21 @@ func (h *handler) OnUpdate(_ client.Client, decoder admission.Decoder, _ record.
}
}

func (h *handler) namespaceIsOwned(ns *corev1.Namespace, tenantList *capsulev1beta2.TenantList, req admission.Request) bool {
for _, tenant := range tenantList.Items {
for _, ownerRef := range ns.OwnerReferences {
if !capsuleutils.IsTenantOwnerReference(ownerRef) {
continue
}
if ownerRef.UID == tenant.UID && utils.IsTenantOwner(tenant.Spec.Owners, req.UserInfo) {

Check failure on line 122 in pkg/webhook/ownerreference/patching.go

View workflow job for this annotation

GitHub Actions / lint

if statements should only be cuddled with assignments (wsl)
return true
}
}
}

return false
}

func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client client.Client, decoder admission.Decoder, recorder record.EventRecorder) *admission.Response {
ns := &corev1.Namespace{}
if err := decoder.Decode(req, ns); err != nil {
Expand Down

0 comments on commit d620b04

Please sign in to comment.