feat(manager): add forbidden annotations, forbidden labels to service options

Signed-off-by: Siarhei Rasiukevich <s.rasiukevich@gmail.com>
This commit is contained in:
Siarhei Rasiukevich
2023-11-24 12:30:59 +01:00
committed by Dario Tranchitella
parent 8695dfb7a2
commit b27780d74c
12 changed files with 658 additions and 104 deletions
+22
View File
@@ -3133,6 +3133,28 @@ spec:
type: string
type: object
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:
@@ -1873,6 +1873,28 @@ spec:
required:
- allowed
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the
@@ -3125,6 +3147,28 @@ spec:
required:
- allowed
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the
+160
View File
@@ -4822,6 +4822,20 @@ Specifies options for the Service, such as additional metadata or block of certa
Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecserviceoptionsforbiddenannotations-1">forbiddenAnnotations</a></b></td>
<td>object</td>
<td>
Define the annotations that a Tenant Owner cannot set for their Service resources.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecserviceoptionsforbiddenlabels-1">forbiddenLabels</a></b></td>
<td>object</td>
<td>
Define the labels that a Tenant Owner cannot set for their Service resources.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
@@ -4931,6 +4945,72 @@ Specifies the external IPs that can be used in Services with type ClusterIP. An
</table>
### Tenant.spec.serviceOptions.forbiddenAnnotations
Define the annotations that a Tenant Owner cannot set for their Service resources.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>denied</b></td>
<td>[]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>deniedRegex</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.serviceOptions.forbiddenLabels
Define the labels that a Tenant Owner cannot set for their Service resources.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>denied</b></td>
<td>[]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>deniedRegex</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.storageClasses
@@ -6681,6 +6761,20 @@ Specifies options for the Service, such as additional metadata or block of certa
Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecserviceoptionsforbiddenannotations">forbiddenAnnotations</a></b></td>
<td>object</td>
<td>
Define the annotations that a Tenant Owner cannot set for their Service resources.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecserviceoptionsforbiddenlabels">forbiddenLabels</a></b></td>
<td>object</td>
<td>
Define the labels that a Tenant Owner cannot set for their Service resources.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
@@ -6790,6 +6884,72 @@ Specifies the external IPs that can be used in Services with type ClusterIP. An
</table>
### Tenant.spec.serviceOptions.forbiddenAnnotations
Define the annotations that a Tenant Owner cannot set for their Service resources.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>denied</b></td>
<td>[]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>deniedRegex</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.serviceOptions.forbiddenLabels
Define the labels that a Tenant Owner cannot set for their Service resources.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>denied</b></td>
<td>[]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>deniedRegex</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.storageClasses
+229
View File
@@ -0,0 +1,229 @@
//go:build e2e
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api"
)
var _ = Describe("creating a Service with user-specified labels and annotations", func() {
tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-user-metadata-forbidden",
},
Spec: capsulev1beta2.TenantSpec{
ServiceOptions: &api.ServiceOptions{
ForbiddenLabels: api.ForbiddenListSpec{
Exact: []string{"foo", "bar"},
Regex: "^gatsby-.*$",
},
ForbiddenAnnotations: api.ForbiddenListSpec{
Exact: []string{"foo", "bar"},
Regex: "^gatsby-.*$",
},
},
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should allow", func() {
By("specifying non-forbidden labels", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "non-forbidden-labels",
})
svc.SetLabels(map[string]string{"bim": "baz"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
})
By("specifying non-forbidden annotations", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "non-forbidden-annotations",
})
svc.SetAnnotations(map[string]string{"bim": "baz"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
})
})
It("should fail when creating a Service", func() {
By("specifying forbidden labels using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-exact",
})
svc.SetLabels(map[string]string{"foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden labels using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-regex",
})
svc.SetLabels(map[string]string{"gatsby-foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden annotations using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-exact",
})
svc.SetAnnotations(map[string]string{"foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden annotations using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-regex",
})
svc.SetAnnotations(map[string]string{"gatsby-foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
})
It("should fail when updating a Service", func() {
cs := ownerClient(tnt.Spec.Owners[0])
By("specifying forbidden labels using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-exact-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetLabels(map[string]string{"foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 10*time.Second, time.Second).ShouldNot(Succeed())
})
By("specifying forbidden labels using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-regex-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetLabels(map[string]string{"gatsby-foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 3*time.Second, time.Second).ShouldNot(Succeed())
})
By("specifying forbidden annotations using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-exact-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetAnnotations(map[string]string{"foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 10*time.Second, time.Second).ShouldNot(Succeed())
})
By("specifying forbidden annotations using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-regex-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetAnnotations(map[string]string{"gatsby-foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 10*time.Second, time.Second).ShouldNot(Succeed())
})
})
})
+22
View File
@@ -31,6 +31,28 @@ const (
defaultPollInterval = time.Second
)
func NewService(svc types.NamespacedName) *corev1.Service {
return &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: svc.Name,
Namespace: svc.Namespace,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: int32(80)},
},
},
}
}
func ServiceCreation(svc *corev1.Service, owner capsulev1beta2.OwnerSpec, timeout time.Duration) AsyncAssertion {
cs := ownerClient(owner)
return Eventually(func() (err error) {
_, err = cs.CoreV1().Services(svc.Namespace).Create(context.TODO(), svc, metav1.CreateOptions{})
return
}, timeout, defaultPollInterval)
}
func NewNamespace(name string) *corev1.Namespace {
if len(name) == 0 {
name = rand.String(10)
+63 -1
View File
@@ -4,13 +4,21 @@
package api
import (
"fmt"
"reflect"
"regexp"
"sort"
"strings"
)
// +kubebuilder:object:generate=true
const (
// ForbiddenLabelReason used as reason string to deny forbidden labels.
ForbiddenLabelReason = "ForbiddenLabel"
// ForbiddenAnnotationReason used as reason string to deny forbidden annotations.
ForbiddenAnnotationReason = "ForbiddenAnnotation"
)
// +kubebuilder:object:generate=true
type ForbiddenListSpec struct {
Exact []string `json:"denied,omitempty"`
Regex string `json:"deniedRegex,omitempty"`
@@ -37,3 +45,57 @@ func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) {
return
}
type ForbiddenError struct {
key string
spec ForbiddenListSpec
}
func NewForbiddenError(key string, forbiddenSpec ForbiddenListSpec) error {
return &ForbiddenError{
key: key,
spec: forbiddenSpec,
}
}
//nolint:predeclared
func (f *ForbiddenError) appendForbiddenError() (append string) {
append += "Forbidden are "
if len(f.spec.Exact) > 0 {
append += fmt.Sprintf("one of the following (%s)", strings.Join(f.spec.Exact, ", "))
if len(f.spec.Regex) > 0 {
append += " or "
}
}
if len(f.spec.Regex) > 0 {
append += fmt.Sprintf("matching the regex %s", f.spec.Regex)
}
return
}
func (f ForbiddenError) Error() string {
return fmt.Sprintf("%s is forbidden for the current Tenant. %s", f.key, f.appendForbiddenError())
}
func ValidateForbidden(metadata map[string]string, forbiddenList ForbiddenListSpec) error {
if reflect.DeepEqual(ForbiddenListSpec{}, forbiddenList) {
return nil
}
for key := range metadata {
var forbidden, matched bool
forbidden = forbiddenList.ExactMatch(key)
matched = forbiddenList.RegexMatch(key)
if forbidden || matched {
return NewForbiddenError(
key,
forbiddenList,
)
}
}
return nil
}
+47
View File
@@ -72,3 +72,50 @@ func TestForbiddenListSpec_RegexMatch(t *testing.T) {
}
}
}
func TestValidateForbidden(t *testing.T) {
type tc struct {
Keys map[string]string
ForbiddenSpec ForbiddenListSpec
HasError bool
}
for _, tc := range []tc{
{
Keys: map[string]string{"foobar": "", "thesecondkey": "", "anotherkey": ""},
ForbiddenSpec: ForbiddenListSpec{
Exact: []string{"foobar", "somelabelkey1"},
},
HasError: true,
},
{
Keys: map[string]string{"foobar": ""},
ForbiddenSpec: ForbiddenListSpec{
Exact: []string{"foobar.io", "somelabelkey1", "test-exact"},
},
HasError: false,
},
{
Keys: map[string]string{"foobar": "", "barbaz": ""},
ForbiddenSpec: ForbiddenListSpec{
Regex: "foo.*",
},
HasError: true,
},
{
Keys: map[string]string{"foobar": "", "another-annotation-key": ""},
ForbiddenSpec: ForbiddenListSpec{
Regex: "foo1111",
},
HasError: false,
},
} {
if tc.HasError {
assert.Error(t, ValidateForbidden(tc.Keys, tc.ForbiddenSpec))
}
if !tc.HasError {
assert.NoError(t, ValidateForbidden(tc.Keys, tc.ForbiddenSpec))
}
}
}
+4
View File
@@ -12,4 +12,8 @@ type ServiceOptions struct {
AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
// Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"`
// Define the labels that a Tenant Owner cannot set for their Service resources.
ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels,omitempty"`
// Define the annotations that a Tenant Owner cannot set for their Service resources.
ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"`
}
+2
View File
@@ -290,6 +290,8 @@ func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) {
*out = new(ExternalServiceIPsSpec)
(*in).DeepCopyInto(*out)
}
in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels)
in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions.
-56
View File
@@ -3,30 +3,6 @@
package namespace
import (
"fmt"
"strings"
capsuleapi "github.com/projectcapsule/capsule/pkg/api"
)
//nolint:predeclared
func appendForbiddenError(spec *capsuleapi.ForbiddenListSpec) (append string) {
append += "Forbidden are "
if len(spec.Exact) > 0 {
append += fmt.Sprintf("one of the following (%s)", strings.Join(spec.Exact, ", "))
if len(spec.Regex) > 0 {
append += " or "
}
}
if len(spec.Regex) > 0 {
append += fmt.Sprintf("matching the regex %s", spec.Regex)
}
return
}
type namespaceQuotaExceededError struct{}
func NewNamespaceQuotaExceededError() error {
@@ -36,35 +12,3 @@ func NewNamespaceQuotaExceededError() error {
func (namespaceQuotaExceededError) Error() string {
return "Cannot exceed Namespace quota: please, reach out to the system administrators"
}
type namespaceLabelForbiddenError struct {
label string
spec *capsuleapi.ForbiddenListSpec
}
func NewNamespaceLabelForbiddenError(label string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error {
return &namespaceLabelForbiddenError{
label: label,
spec: forbiddenSpec,
}
}
func (f namespaceLabelForbiddenError) Error() string {
return fmt.Sprintf("Label %s is forbidden for namespaces in the current Tenant. %s", f.label, appendForbiddenError(f.spec))
}
type namespaceAnnotationForbiddenError struct {
annotation string
spec *capsuleapi.ForbiddenListSpec
}
func NewNamespaceAnnotationForbiddenError(annotation string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error {
return &namespaceAnnotationForbiddenError{
annotation: annotation,
spec: forbiddenSpec,
}
}
func (f namespaceAnnotationForbiddenError) Error() string {
return fmt.Sprintf("Annotation %s is forbidden for namespaces in the current Tenant. %s", f.annotation, appendForbiddenError(f.spec))
}
+43 -47
View File
@@ -5,8 +5,8 @@ package namespace
import (
"context"
"fmt"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
@@ -14,6 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api"
capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook"
"github.com/projectcapsule/capsule/pkg/webhook/utils"
)
@@ -24,48 +25,6 @@ func UserMetadataHandler() capsulewebhook.Handler {
return &userMetadataHandler{}
}
func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta2.Tenant, recorder record.EventRecorder, labels map[string]string, annotations map[string]string) *admission.Response {
if tnt.Spec.NamespaceOptions != nil {
forbiddenLabels := tnt.Spec.NamespaceOptions.ForbiddenLabels
for label := range labels {
var forbidden, matched bool
forbidden = forbiddenLabels.ExactMatch(label)
matched = forbiddenLabels.RegexMatch(label)
if forbidden || matched {
recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceLabel", fmt.Sprintf("Label %s is forbidden for a namespaces of the current Tenant ", label))
response := admission.Denied(NewNamespaceLabelForbiddenError(label, &forbiddenLabels).Error())
return &response
}
}
}
if tnt.Spec.NamespaceOptions == nil {
return nil
}
forbiddenAnnotations := tnt.Spec.NamespaceOptions.ForbiddenLabels
for annotation := range annotations {
var forbidden, matched bool
forbidden = forbiddenAnnotations.ExactMatch(annotation)
matched = forbiddenAnnotations.RegexMatch(annotation)
if forbidden || matched {
recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceAnnotation", fmt.Sprintf("Annotation %s is forbidden for a namespaces of the current Tenant ", annotation))
response := admission.Denied(NewNamespaceAnnotationForbiddenError(annotation, &forbiddenAnnotations).Error())
return &response
}
}
return nil
}
func (r *userMetadataHandler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
ns := &corev1.Namespace{}
@@ -81,10 +40,27 @@ func (r *userMetadataHandler) OnCreate(client client.Client, decoder *admission.
}
}
labels := ns.GetLabels()
annotations := ns.GetAnnotations()
if tnt.Spec.NamespaceOptions != nil {
err := api.ValidateForbidden(ns.ObjectMeta.Annotations, tnt.Spec.NamespaceOptions.ForbiddenAnnotations)
if err != nil {
err = errors.Wrap(err, "namespace annotations validation failed")
recorder.Eventf(tnt, corev1.EventTypeWarning, api.ForbiddenAnnotationReason, err.Error())
response := admission.Denied(err.Error())
return r.validateUserMetadata(tnt, recorder, labels, annotations)
return &response
}
err = api.ValidateForbidden(ns.ObjectMeta.Labels, tnt.Spec.NamespaceOptions.ForbiddenLabels)
if err != nil {
err = errors.Wrap(err, "namespace labels validation failed")
recorder.Eventf(tnt, corev1.EventTypeWarning, api.ForbiddenLabelReason, err.Error())
response := admission.Denied(err.Error())
return &response
}
}
return nil
}
}
@@ -173,6 +149,26 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission.
delete(annotations, key)
}
return r.validateUserMetadata(tnt, recorder, labels, annotations)
if tnt.Spec.NamespaceOptions != nil {
err := api.ValidateForbidden(annotations, tnt.Spec.NamespaceOptions.ForbiddenAnnotations)
if err != nil {
err = errors.Wrap(err, "namespace annotations validation failed")
recorder.Eventf(tnt, corev1.EventTypeWarning, api.ForbiddenAnnotationReason, err.Error())
response := admission.Denied(err.Error())
return &response
}
err = api.ValidateForbidden(labels, tnt.Spec.NamespaceOptions.ForbiddenLabels)
if err != nil {
err = errors.Wrap(err, "namespace labels validation failed")
recorder.Eventf(tnt, corev1.EventTypeWarning, api.ForbiddenLabelReason, err.Error())
response := admission.Denied(err.Error())
return &response
}
}
return nil
}
}
+22
View File
@@ -8,6 +8,7 @@ import (
"net"
"strings"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/tools/record"
@@ -15,6 +16,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api"
capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook"
"github.com/projectcapsule/capsule/pkg/webhook/utils"
)
@@ -68,6 +70,26 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
return &response
}
if tnt.Spec.ServiceOptions != nil {
err := api.ValidateForbidden(svc.Annotations, tnt.Spec.ServiceOptions.ForbiddenAnnotations)
if err != nil {
err = errors.Wrap(err, "service annotations validation failed")
recorder.Eventf(&tnt, corev1.EventTypeWarning, api.ForbiddenAnnotationReason, err.Error())
response := admission.Denied(err.Error())
return &response
}
err = api.ValidateForbidden(svc.Labels, tnt.Spec.ServiceOptions.ForbiddenLabels)
if err != nil {
err = errors.Wrap(err, "service labels validation failed")
recorder.Eventf(&tnt, corev1.EventTypeWarning, api.ForbiddenLabelReason, err.Error())
response := admission.Denied(err.Error())
return &response
}
}
if svc.Spec.ExternalIPs == nil || (tnt.Spec.ServiceOptions == nil || tnt.Spec.ServiceOptions.ExternalServiceIPs == nil) {
return nil
}