mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 18:09:58 +00:00
Add protected-namespace-regex (#73)
This commit is contained in:
@@ -63,6 +63,8 @@ Log verbosity of the Capsule controller can be increased by passing the `--zap-l
|
||||
|
||||
During startup Capsule controller will create additional ClusterRoles `capsule-namespace:deleter`, `capsule-namespace:provisioner` and ClusterRoleBinding `capsule-namespace:provisioner`. These resources are used in order to allow Capsule users to manage their namespaces in tenants.
|
||||
|
||||
You can disallow users to create namespaces matching a particular regexp by passing `--protected-namespace-regex` option with a value of regular expression.
|
||||
|
||||
## Admission Controllers
|
||||
Capsule implements Kubernetes multi-tenancy capabilities using a minimum set of standard [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) enabled on the Kubernetes APIs server: `--enable-admission-plugins=PodNodeSelector,LimitRanger,ResourceQuota,MutatingAdmissionWebhook,ValidatingAdmissionWebhook`. In addition to these default controllers, Capsule implements its own set of Admission Controllers through the [Dynamic Admission Controller](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/), providing callbacks to add further validation or resource patching.
|
||||
|
||||
|
||||
@@ -54,16 +54,16 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
It("should fail", func() {
|
||||
args := append(defaulManagerPodArgs, []string{"--capsule-user-group=test"}...)
|
||||
ModifyCapsuleManagerPodArgs(args)
|
||||
CapsuleClusterGroupParamShouldBeUpdated("test")
|
||||
CapsuleClusterGroupParamShouldBeUpdated("test", podRecreationTimeoutInterval)
|
||||
ns := NewNamespace("cg-namespace-fail")
|
||||
NamespaceCreationShouldNotSucceed(ns, tnt)
|
||||
NamespaceCreationShouldNotSucceed(ns, tnt, podRecreationTimeoutInterval)
|
||||
})
|
||||
It("should succeed and be available in Tenant namespaces list", func() {
|
||||
ModifyCapsuleManagerPodArgs(defaulManagerPodArgs)
|
||||
CapsuleClusterGroupParamShouldBeUpdated("capsule.clastix.io")
|
||||
CapsuleClusterGroupParamShouldBeUpdated("capsule.clastix.io", podRecreationTimeoutInterval)
|
||||
ns := NewNamespace("cg-namespace")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, podRecreationTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, podRecreationTimeoutInterval)
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,8 +65,8 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
ns := NewNamespace("ingress-class-disallowed")
|
||||
cs := ownerClient(tnt)
|
||||
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
By("non-specifying the class", func() {
|
||||
Eventually(func() (err error) {
|
||||
@@ -128,8 +128,8 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
ns := NewNamespace("ingress-class-allowed-annotation")
|
||||
cs := ownerClient(tnt)
|
||||
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
for _, c := range tnt.Spec.IngressClasses {
|
||||
Eventually(func() (err error) {
|
||||
@@ -168,8 +168,8 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
Skip("Running test ont Kubernetes " + v.String() + ", doesn't provide .spec.ingressClassName")
|
||||
}
|
||||
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
for _, c := range tnt.Spec.IngressClasses {
|
||||
Eventually(func() (err error) {
|
||||
|
||||
@@ -52,7 +52,7 @@ var _ = Describe("creating a Namespace as Tenant owner", func() {
|
||||
})
|
||||
It("should be available in Tenant namespaces list", func() {
|
||||
ns := NewNamespace("new-namespace")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,8 +54,8 @@ var _ = Describe("creating a Namespace over-quota", func() {
|
||||
By("creating three Namespaces", func() {
|
||||
for _, name := range []string{"bob-dev", "bob-staging", "bob-production"} {
|
||||
ns := NewNamespace(name)
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
It("should disallow deletions", func() {
|
||||
By("blocking Capsule Limit ranges", func() {
|
||||
ns := NewNamespace("limit-range-disallow")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
lr := &corev1.LimitRange{}
|
||||
Eventually(func() error {
|
||||
@@ -117,8 +117,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
})
|
||||
By("blocking Capsule Network Policy", func() {
|
||||
ns := NewNamespace("network-policy-disallow")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
np := &networkingv1.NetworkPolicy{}
|
||||
Eventually(func() error {
|
||||
@@ -131,8 +131,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
})
|
||||
By("blocking blocking Capsule Resource Quota", func() {
|
||||
ns := NewNamespace("resource-quota-disallow")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
rq := &corev1.ResourceQuota{}
|
||||
Eventually(func() error {
|
||||
@@ -147,8 +147,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
It("should allow listing", func() {
|
||||
By("Limit Range resources", func() {
|
||||
ns := NewNamespace("limit-range-list")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
Eventually(func() (err error) {
|
||||
cs := ownerClient(tnt)
|
||||
@@ -158,8 +158,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
})
|
||||
By("Network Policy resources", func() {
|
||||
ns := NewNamespace("network-policy-list")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
Eventually(func() (err error) {
|
||||
cs := ownerClient(tnt)
|
||||
@@ -169,8 +169,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
})
|
||||
By("Resource Quota resources", func() {
|
||||
ns := NewNamespace("resource-quota-list")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
Eventually(func() (err error) {
|
||||
cs := ownerClient(tnt)
|
||||
@@ -181,8 +181,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
})
|
||||
It("should allow all actions to Tenant owner Network Policy resources", func() {
|
||||
ns := NewNamespace("network-policy-allow")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
cs := ownerClient(tnt)
|
||||
np := &networkingv1.NetworkPolicy{
|
||||
|
||||
63
e2e/protected_namespace_regex_test.go
Normal file
63
e2e/protected_namespace_regex_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a Namespace with --protected-namespace-regex enabled", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tenantprotectednamespace",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "alice",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
tnt.ResourceVersion = ""
|
||||
Expect(k8sClient.Create(context.TODO(), tnt)).Should(Succeed())
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
|
||||
})
|
||||
It("should succeed and be available in Tenant namespaces list", func() {
|
||||
args := append(defaulManagerPodArgs, []string{"--protected-namespace-regex=^.*[-.]system$"}...)
|
||||
ModifyCapsuleManagerPodArgs(args)
|
||||
ns := NewNamespace("test-ok")
|
||||
NamespaceCreationShouldSucceed(ns, tnt, podRecreationTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, podRecreationTimeoutInterval)
|
||||
})
|
||||
It("should fail", func() {
|
||||
ModifyCapsuleManagerPodArgs(defaulManagerPodArgs)
|
||||
ns := NewNamespace("test-system")
|
||||
NamespaceCreationShouldNotSucceed(ns, tnt, podRecreationTimeoutInterval)
|
||||
})
|
||||
})
|
||||
@@ -91,8 +91,8 @@ var _ = Describe("exceeding Tenant resource quota", func() {
|
||||
},
|
||||
},
|
||||
NetworkPolicies: []networkingv1.NetworkPolicySpec{},
|
||||
NamespaceQuota: 2,
|
||||
NodeSelector: map[string]string{},
|
||||
NamespaceQuota: 2,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{
|
||||
{
|
||||
Hard: map[corev1.ResourceName]resource.Quantity{
|
||||
@@ -125,8 +125,8 @@ var _ = Describe("exceeding Tenant resource quota", func() {
|
||||
By("creating the Namespaces", func() {
|
||||
for _, i := range nsl {
|
||||
ns := NewNamespace(i)
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -157,7 +157,7 @@ var _ = Describe("exceeding Tenant resource quota", func() {
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "my-pause",
|
||||
Name: "my-pause",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
},
|
||||
},
|
||||
@@ -204,7 +204,7 @@ var _ = Describe("exceeding Tenant resource quota", func() {
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "my-exceeded",
|
||||
Name: "my-exceeded",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@ var _ = Describe("creating a Namespace with Tenant selector", func() {
|
||||
l: t2.Name,
|
||||
}
|
||||
})
|
||||
NamespaceCreationShouldSucceed(ns, t2)
|
||||
NamespaceShouldBeManagedByTenant(ns, t2)
|
||||
NamespaceCreationShouldSucceed(ns, t2, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, t2, defaultTimeoutInterval)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -60,8 +60,8 @@ var _ = Describe("when Tenant handles Storage classes", func() {
|
||||
})
|
||||
It("should block non allowed Storage Class", func() {
|
||||
ns := NewNamespace("storage-class-disallowed")
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
By("non-specifying the class", func() {
|
||||
Eventually(func() (err error) {
|
||||
@@ -108,8 +108,8 @@ var _ = Describe("when Tenant handles Storage classes", func() {
|
||||
ns := NewNamespace("storage-class-allowed")
|
||||
cs := ownerClient(tnt)
|
||||
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
for _, c := range tnt.Spec.StorageClasses {
|
||||
Eventually(func() (err error) {
|
||||
|
||||
@@ -168,8 +168,8 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() {
|
||||
By("creating the Namespaces", func() {
|
||||
for _, i := range nsl {
|
||||
ns := NewNamespace(i)
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -170,8 +170,8 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() {
|
||||
By("creating the Namespaces", func() {
|
||||
for _, i := range nsl {
|
||||
ns := NewNamespace(i)
|
||||
NamespaceCreationShouldSucceed(ns, tnt)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt)
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,8 +34,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeoutInterval = 25 * time.Second
|
||||
defaultPollInterval = time.Second
|
||||
defaultTimeoutInterval = 15 * time.Second
|
||||
podRecreationTimeoutInterval = 90 * time.Second
|
||||
defaultPollInterval = time.Second
|
||||
)
|
||||
|
||||
func NewNamespace(name string) *corev1.Namespace {
|
||||
@@ -46,36 +47,36 @@ func NewNamespace(name string) *corev1.Namespace {
|
||||
}
|
||||
}
|
||||
|
||||
func NamespaceCreationShouldSucceed(ns *corev1.Namespace, t *v1alpha1.Tenant) {
|
||||
func NamespaceCreationShouldSucceed(ns *corev1.Namespace, t *v1alpha1.Tenant, timeout time.Duration) {
|
||||
cs := ownerClient(t)
|
||||
Eventually(func() (err error) {
|
||||
_, err = cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
}, timeout, defaultPollInterval).Should(Succeed())
|
||||
}
|
||||
|
||||
func NamespaceCreationShouldNotSucceed(ns *corev1.Namespace, t *v1alpha1.Tenant) {
|
||||
func NamespaceCreationShouldNotSucceed(ns *corev1.Namespace, t *v1alpha1.Tenant, timeout time.Duration) {
|
||||
cs := ownerClient(t)
|
||||
Eventually(func() (err error) {
|
||||
_, err = cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
|
||||
}, timeout, defaultPollInterval).ShouldNot(Succeed())
|
||||
}
|
||||
|
||||
func NamespaceShouldBeManagedByTenant(ns *corev1.Namespace, t *v1alpha1.Tenant) {
|
||||
func NamespaceShouldBeManagedByTenant(ns *corev1.Namespace, t *v1alpha1.Tenant, timeout time.Duration) {
|
||||
Eventually(func() v1alpha1.NamespaceList {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: t.GetName()}, t)).Should(Succeed())
|
||||
return t.Status.Namespaces
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(ContainElement(ns.GetName()))
|
||||
}, timeout, defaultPollInterval).Should(ContainElement(ns.GetName()))
|
||||
}
|
||||
|
||||
func CapsuleClusterGroupParamShouldBeUpdated(capsuleClusterGroup string) {
|
||||
func CapsuleClusterGroupParamShouldBeUpdated(capsuleClusterGroup string, timeout time.Duration) {
|
||||
capsuleCRB := &rbacv1.ClusterRoleBinding{}
|
||||
|
||||
Eventually(func() string {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "capsule-namespace:provisioner"}, capsuleCRB)).Should(Succeed())
|
||||
return capsuleCRB.Subjects[0].Name
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeIdenticalTo(capsuleClusterGroup))
|
||||
}, timeout, defaultPollInterval).Should(BeIdenticalTo(capsuleClusterGroup))
|
||||
|
||||
}
|
||||
|
||||
@@ -100,15 +101,17 @@ func ModifyCapsuleManagerPodArgs(args []string) {
|
||||
}
|
||||
}
|
||||
return containerArgs
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(len(args)))
|
||||
}, podRecreationTimeoutInterval, defaultPollInterval).Should(HaveLen(len(args)))
|
||||
|
||||
pl := &corev1.PodList{}
|
||||
Eventually(func() []corev1.Pod {
|
||||
Expect(k8sClient.List(context.TODO(), pl, client.MatchingLabels{"control-plane": "controller-manager"})).Should(Succeed())
|
||||
return pl.Items
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(2))
|
||||
}, podRecreationTimeoutInterval, defaultPollInterval).Should(HaveLen(2))
|
||||
Eventually(func() []corev1.Pod {
|
||||
Expect(k8sClient.List(context.TODO(), pl, client.MatchingLabels{"control-plane": "controller-manager"})).Should(Succeed())
|
||||
return pl.Items
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(1))
|
||||
}, podRecreationTimeoutInterval, defaultPollInterval).Should(HaveLen(1))
|
||||
// had to add sleep in order to manager be started
|
||||
time.Sleep(defaultTimeoutInterval)
|
||||
}
|
||||
|
||||
13
main.go
13
main.go
@@ -20,6 +20,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
goRuntime "runtime"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -73,6 +74,8 @@ func main() {
|
||||
var forceTenantPrefix bool
|
||||
var v bool
|
||||
var capsuleGroup string
|
||||
var protectedNamespaceRegexpString string
|
||||
var protectedNamespaceRegexp *regexp.Regexp
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.StringVar(&capsuleGroup, "capsule-user-group", capsulev1alpha1.GroupVersion.Group, "Name of the group for capsule users")
|
||||
@@ -83,6 +86,7 @@ func main() {
|
||||
flag.BoolVar(&forceTenantPrefix, "force-tenant-prefix", false, "Enforces the Tenant owner, "+
|
||||
"during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. "+
|
||||
"This is useful to avoid Namespace name collision in a public CaaS environment.")
|
||||
flag.StringVar(&protectedNamespaceRegexpString, "protected-namespace-regex", "", "Disallow creation of namespaces, whose name matches this regexp")
|
||||
opts := zap.Options{}
|
||||
opts.BindFlags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
@@ -106,6 +110,13 @@ func main() {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(protectedNamespaceRegexpString) > 0 {
|
||||
protectedNamespaceRegexp, err = regexp.Compile(protectedNamespaceRegexpString)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to compile protected-namespace-regex", "protected-namespace-regex", protectedNamespaceRegexp)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
_ = mgr.AddReadyzCheck("ping", healthz.Ping)
|
||||
_ = mgr.AddHealthzCheck("ping", healthz.Ping)
|
||||
@@ -122,7 +133,7 @@ func main() {
|
||||
|
||||
//webhooks
|
||||
wl := make([]webhook.Webhook, 0)
|
||||
wl = append(wl, &ingress.ExtensionIngress{}, &ingress.NetworkIngress{}, pvc.Webhook{}, &owner_reference.Webhook{}, &namespace_quota.Webhook{}, network_policies.Webhook{}, tenant_prefix.Webhook{ForceTenantPrefix: forceTenantPrefix})
|
||||
wl = append(wl, &ingress.ExtensionIngress{}, &ingress.NetworkIngress{}, pvc.Webhook{}, &owner_reference.Webhook{}, &namespace_quota.Webhook{}, network_policies.Webhook{}, tenant_prefix.Webhook{ForceTenantPrefix: forceTenantPrefix, ProtectedNamespacesRegex: protectedNamespaceRegexp})
|
||||
err = webhook.Register(mgr, capsuleGroup, wl...)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to setup webhooks")
|
||||
|
||||
@@ -19,6 +19,7 @@ package tenant_prefix
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -33,11 +34,12 @@ import (
|
||||
// +kubebuilder:webhook:path=/validating-v1-namespace-tenant-prefix,mutating=false,failurePolicy=fail,groups="",resources=namespaces,verbs=create,versions=v1,name=prefix.namespace.capsule.clastix.io
|
||||
|
||||
type Webhook struct {
|
||||
ForceTenantPrefix bool
|
||||
ForceTenantPrefix bool
|
||||
ProtectedNamespacesRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (o Webhook) GetHandler() webhook.Handler {
|
||||
return &handler{forceTenantPrefix: o.ForceTenantPrefix}
|
||||
return &handler{forceTenantPrefix: o.ForceTenantPrefix, protectedNamespacesRegex: o.ProtectedNamespacesRegex}
|
||||
}
|
||||
|
||||
func (o Webhook) GetName() string {
|
||||
@@ -49,18 +51,26 @@ func (o Webhook) GetPath() string {
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
forceTenantPrefix bool
|
||||
forceTenantPrefix bool
|
||||
protectedNamespacesRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(ctx context.Context, req admission.Request, clt client.Client, decoder *admission.Decoder) admission.Response {
|
||||
if !r.forceTenantPrefix {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
ns := &corev1.Namespace{}
|
||||
if err := decoder.Decode(req, ns); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if r.protectedNamespacesRegex != nil {
|
||||
if matched := r.protectedNamespacesRegex.MatchString(ns.GetName()); matched {
|
||||
return admission.Denied("Creating namespaces with name matching " + r.protectedNamespacesRegex.String() + " regexp is not allowed; please, reach out the system administrators")
|
||||
}
|
||||
}
|
||||
|
||||
if !r.forceTenantPrefix {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
t := &v1alpha1.Tenant{}
|
||||
for _, or := range ns.ObjectMeta.OwnerReferences {
|
||||
// retrieving the selected Tenant
|
||||
|
||||
Reference in New Issue
Block a user