mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-05-06 01:16:44 +00:00
Enforcing container registry via list or regex (#142)
Adding also NamespaceSelector to specific webhooks in order to decrease the chance ov breaking other critical Namespaces in case of Capsule failures.
This commit is contained in:
committed by
GitHub
parent
8442eef72b
commit
5aed7a01d5
83
api/v1alpha1/domain/registry.go
Normal file
83
api/v1alpha1/domain/registry.go
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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 domain
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type registry map[string]string
|
||||
|
||||
func (r registry) Registry() string {
|
||||
res, ok := r["registry"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return "docker.io"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r registry) Repository() string {
|
||||
res, ok := r["repository"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if res == "docker.io" {
|
||||
return ""
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r registry) Image() string {
|
||||
res, ok := r["image"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r registry) Tag() string {
|
||||
res, ok := r["tag"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if len(res) == 0 {
|
||||
res = "latest"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func NewRegistry(value string) Registry {
|
||||
registry := make(registry)
|
||||
r := regexp.MustCompile(`(((?P<registry>[a-zA-Z0-9-.]+)\/)?((?P<repository>[a-zA-Z0-9-.]+)\/))?(?P<image>[a-zA-Z0-9-.]+)(:(?P<tag>[a-zA-Z0-9-.]+))?`)
|
||||
match := r.FindStringSubmatch(value)
|
||||
for i, name := range r.SubexpNames() {
|
||||
if i > 0 && i <= len(match) {
|
||||
registry[name] = match[i]
|
||||
}
|
||||
}
|
||||
return registry
|
||||
}
|
||||
|
||||
type Registry interface {
|
||||
Registry() string
|
||||
Repository() string
|
||||
Image() string
|
||||
Tag() string
|
||||
}
|
||||
78
api/v1alpha1/domain/registry_test.go
Normal file
78
api/v1alpha1/domain/registry_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewRegistry(t *testing.T) {
|
||||
type tc struct {
|
||||
registry string
|
||||
repo string
|
||||
image string
|
||||
tag string
|
||||
}
|
||||
for name, tc := range map[string]tc{
|
||||
"docker.io/my-org/my-repo:v0.0.1": {
|
||||
registry: "docker.io",
|
||||
repo: "my-org",
|
||||
image: "my-repo",
|
||||
tag: "v0.0.1",
|
||||
},
|
||||
"unnamed/repository:1.2.3": {
|
||||
registry: "docker.io",
|
||||
repo: "unnamed",
|
||||
image: "repository",
|
||||
tag: "1.2.3",
|
||||
},
|
||||
"quay.io/clastix/capsule:v1.0.0": {
|
||||
registry: "quay.io",
|
||||
repo: "clastix",
|
||||
image: "capsule",
|
||||
tag: "v1.0.0",
|
||||
},
|
||||
"docker.io/redis:alpine": {
|
||||
registry: "docker.io",
|
||||
repo: "",
|
||||
image: "redis",
|
||||
tag: "alpine",
|
||||
},
|
||||
"nginx:alpine": {
|
||||
registry: "docker.io",
|
||||
repo: "",
|
||||
image: "nginx",
|
||||
tag: "alpine",
|
||||
},
|
||||
"nginx": {
|
||||
registry: "docker.io",
|
||||
repo: "",
|
||||
image: "nginx",
|
||||
tag: "latest",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewRegistry(name)
|
||||
assert.Equal(t, tc.registry, r.Registry())
|
||||
assert.Equal(t, tc.repo, r.Repository())
|
||||
assert.Equal(t, tc.image, r.Image())
|
||||
assert.Equal(t, tc.tag, r.Tag())
|
||||
})
|
||||
}
|
||||
}
|
||||
43
api/v1alpha1/registry_class_list.go
Normal file
43
api/v1alpha1/registry_class_list.go
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RegistryList []string
|
||||
|
||||
func (in RegistryList) Len() int {
|
||||
return len(in)
|
||||
}
|
||||
|
||||
func (in RegistryList) Swap(i, j int) {
|
||||
in[i], in[j] = in[j], in[i]
|
||||
}
|
||||
|
||||
func (in RegistryList) Less(i, j int) bool {
|
||||
return strings.ToLower(in[i]) < strings.ToLower(in[j])
|
||||
}
|
||||
|
||||
func (in RegistryList) IsStringInList(value string) (ok bool) {
|
||||
sort.Sort(in)
|
||||
i := sort.SearchStrings(in, value)
|
||||
ok = i < in.Len() && in[i] == value
|
||||
return
|
||||
}
|
||||
@@ -47,15 +47,23 @@ type IngressClassesSpec struct {
|
||||
AllowedRegex string `json:"allowedRegex"`
|
||||
}
|
||||
|
||||
type ContainerRegistriesSpec struct {
|
||||
// +nullable
|
||||
Allowed RegistryList `json:"allowed"`
|
||||
// +nullable
|
||||
AllowedRegex string `json:"allowedRegex"`
|
||||
}
|
||||
|
||||
// TenantSpec defines the desired state of Tenant
|
||||
type TenantSpec struct {
|
||||
Owner OwnerSpec `json:"owner"`
|
||||
// +kubebuilder:validation:Optional
|
||||
NamespacesMetadata AdditionalMetadata `json:"namespacesMetadata"`
|
||||
// +kubebuilder:validation:Optional
|
||||
ServicesMetadata AdditionalMetadata `json:"servicesMetadata"`
|
||||
StorageClasses StorageClassesSpec `json:"storageClasses"`
|
||||
IngressClasses IngressClassesSpec `json:"ingressClasses"`
|
||||
ServicesMetadata AdditionalMetadata `json:"servicesMetadata"`
|
||||
StorageClasses StorageClassesSpec `json:"storageClasses"`
|
||||
IngressClasses IngressClassesSpec `json:"ingressClasses"`
|
||||
ContainerRegistries *ContainerRegistriesSpec `json:"containerRegistries,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
NodeSelector map[string]string `json:"nodeSelector"`
|
||||
NamespaceQuota NamespaceQuota `json:"namespaceQuota"`
|
||||
|
||||
@@ -76,6 +76,26 @@ func (in *AdditionalRoleBindings) DeepCopy() *AdditionalRoleBindings {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ContainerRegistriesSpec) DeepCopyInto(out *ContainerRegistriesSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
*out = make(RegistryList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerRegistriesSpec.
|
||||
func (in *ContainerRegistriesSpec) DeepCopy() *ContainerRegistriesSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ContainerRegistriesSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in IngressClassList) DeepCopyInto(out *IngressClassList) {
|
||||
{
|
||||
@@ -149,6 +169,25 @@ func (in *OwnerSpec) DeepCopy() *OwnerSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in RegistryList) DeepCopyInto(out *RegistryList) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(RegistryList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryList.
|
||||
func (in RegistryList) DeepCopy() RegistryList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RegistryList)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in StorageClassList) DeepCopyInto(out *StorageClassList) {
|
||||
{
|
||||
@@ -255,6 +294,11 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
in.ServicesMetadata.DeepCopyInto(&out.ServicesMetadata)
|
||||
in.StorageClasses.DeepCopyInto(&out.StorageClasses)
|
||||
in.IngressClasses.DeepCopyInto(&out.IngressClasses)
|
||||
if in.ContainerRegistries != nil {
|
||||
in, out := &in.ContainerRegistries, &out.ContainerRegistries
|
||||
*out = new(ContainerRegistriesSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
|
||||
@@ -106,6 +106,20 @@ spec:
|
||||
- subjects
|
||||
type: object
|
||||
type: array
|
||||
containerRegistries:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
allowedRegex:
|
||||
nullable: true
|
||||
type: string
|
||||
required:
|
||||
- allowed
|
||||
- allowedRegex
|
||||
type: object
|
||||
ingressClasses:
|
||||
properties:
|
||||
allowed:
|
||||
|
||||
@@ -90,3 +90,7 @@ spec:
|
||||
allowed:
|
||||
- default
|
||||
allowedRegex: ""
|
||||
containerRegistries:
|
||||
allowed:
|
||||
- docker.io
|
||||
allowedRegex: ""
|
||||
|
||||
@@ -2,5 +2,13 @@ resources:
|
||||
- manifests.yaml
|
||||
- service.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: admissionregistration.k8s.io
|
||||
kind: ValidatingWebhookConfiguration
|
||||
name: validating-webhook-configuration
|
||||
version: v1beta1
|
||||
path: patch_ns_selector.yaml
|
||||
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
|
||||
@@ -121,6 +121,23 @@ webhooks:
|
||||
- CREATE
|
||||
resources:
|
||||
- persistentvolumeclaims
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-registry
|
||||
failurePolicy: Ignore
|
||||
name: pod.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- pods
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
|
||||
30
config/webhook/patch_ns_selector.yaml
Normal file
30
config/webhook/patch_ns_selector.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
- op: add
|
||||
path: /webhooks/0/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/1/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/3/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/4/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/5/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
4
main.go
4
main.go
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/clastix/capsule/pkg/webhook/network_policies"
|
||||
"github.com/clastix/capsule/pkg/webhook/owner_reference"
|
||||
"github.com/clastix/capsule/pkg/webhook/pvc"
|
||||
"github.com/clastix/capsule/pkg/webhook/registry"
|
||||
"github.com/clastix/capsule/pkg/webhook/tenant"
|
||||
"github.com/clastix/capsule/pkg/webhook/tenant_prefix"
|
||||
"github.com/clastix/capsule/pkg/webhook/utils"
|
||||
@@ -152,11 +153,12 @@ func main() {
|
||||
}
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
// webhooks
|
||||
// webhooks: the order matters, don't change it and just append
|
||||
wl := append(
|
||||
make([]webhook.Webhook, 0),
|
||||
ingress.Webhook(ingress.Handler()),
|
||||
pvc.Webhook(pvc.Handler()),
|
||||
registry.Webhook(registry.Handler()),
|
||||
owner_reference.Webhook(utils.InCapsuleGroup(capsuleGroup, owner_reference.Handler(forceTenantPrefix))),
|
||||
namespace_quota.Webhook(utils.InCapsuleGroup(capsuleGroup, namespace_quota.Handler())),
|
||||
network_policies.Webhook(utils.InCapsuleGroup(capsuleGroup, network_policies.Handler())),
|
||||
|
||||
49
pkg/webhook/registry/errors.go
Normal file
49
pkg/webhook/registry/errors.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
type registryClassForbidden struct {
|
||||
fqdi string
|
||||
spec v1alpha1.ContainerRegistriesSpec
|
||||
}
|
||||
|
||||
func NewContainerRegistryForbidden(image string, spec v1alpha1.ContainerRegistriesSpec) error {
|
||||
return ®istryClassForbidden{
|
||||
fqdi: image,
|
||||
spec: spec,
|
||||
}
|
||||
}
|
||||
|
||||
func (f registryClassForbidden) Error() (err string) {
|
||||
err = fmt.Sprintf("Container image %s registry is forbidden for the current Tenant: ", f.fqdi)
|
||||
var extra []string
|
||||
if len(f.spec.Allowed) > 0 {
|
||||
extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(f.spec.Allowed, ", ")))
|
||||
}
|
||||
if len(f.spec.AllowedRegex) > 0 {
|
||||
extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", f.spec.AllowedRegex))
|
||||
}
|
||||
err += strings.Join(extra, " or ")
|
||||
return
|
||||
}
|
||||
111
pkg/webhook/registry/validating.go
Normal file
111
pkg/webhook/registry/validating.go
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
"github.com/clastix/capsule/api/v1alpha1/domain"
|
||||
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
|
||||
)
|
||||
|
||||
// +kubebuilder:webhook:path=/validating-v1-registry,mutating=false,failurePolicy=ignore,groups="",resources=pods,verbs=create,versions=v1,name=pod.capsule.clastix.io
|
||||
|
||||
type webhook struct {
|
||||
handler capsulewebhook.Handler
|
||||
}
|
||||
|
||||
func Webhook(handler capsulewebhook.Handler) capsulewebhook.Webhook {
|
||||
return &webhook{handler: handler}
|
||||
}
|
||||
|
||||
func (w *webhook) GetName() string {
|
||||
return "registry"
|
||||
}
|
||||
|
||||
func (w *webhook) GetPath() string {
|
||||
return "/validating-v1-registry"
|
||||
}
|
||||
|
||||
func (w *webhook) GetHandler() capsulewebhook.Handler {
|
||||
return w.handler
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
}
|
||||
|
||||
func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
pod := &v1.Pod{}
|
||||
if err := decoder.Decode(req, pod); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
tl := &capsulev1alpha1.TenantList{}
|
||||
if err := c.List(ctx, tl, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace),
|
||||
}); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
if len(tl.Items) == 0 {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
tnt := tl.Items[0]
|
||||
|
||||
if tnt.Spec.ContainerRegistries != nil {
|
||||
var valid, matched bool
|
||||
regex := regexp.MustCompile(tnt.Spec.ContainerRegistries.AllowedRegex)
|
||||
for _, container := range pod.Spec.Containers {
|
||||
r := domain.NewRegistry(container.Image)
|
||||
valid = tnt.Spec.ContainerRegistries.Allowed.IsStringInList(r.Registry())
|
||||
if len(tnt.Spec.ContainerRegistries.AllowedRegex) > 0 {
|
||||
matched = regex.MatchString(r.Registry())
|
||||
}
|
||||
if !valid && !matched {
|
||||
return admission.Errored(http.StatusBadRequest, NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
52
use_cases.md
52
use_cases.md
@@ -33,7 +33,7 @@ Acme Corp. can use Capsule to address the following scenarios:
|
||||
* [Control the Ingress selector in the tenant](#control-the-ingress-selector-in-the-tenant)
|
||||
* [Assign Storage classes in the tenant](#assign-storage-classes-in-the-tenant)
|
||||
* [Set network policies in the tenant](#set-network-policies-in-the-tenant)
|
||||
|
||||
* [Enforce Pod running images provided by a set of trusted registries](#enforce-pod-running-images-provided-by-a-set-of-trusted-registries)
|
||||
|
||||
### Onboarding of a new customer
|
||||
Bill receives a new request from the CaaS onboarding system that a new
|
||||
@@ -963,3 +963,53 @@ the given subjects.
|
||||
> With the following example, Capsule is forbidding to any authenticated user
|
||||
> to run privileged pods and let them to performs privilege escalation as
|
||||
> declared by the Cluster Role `psp:privileged`.
|
||||
|
||||
# Enforce Pod running images provided by a set of trusted registries
|
||||
|
||||
Let's say you have a strict policy on the ownership running in a certain
|
||||
Tenant: you'd like to allow running just images hosted on a list of specific
|
||||
container registries.
|
||||
|
||||
The spec `containerRegistries` addresses this task and can provide combination
|
||||
with hard enforcement using a list of allowed values as a valid regular
|
||||
expression for a maximum flexibility.
|
||||
|
||||
## Allowing using a regex
|
||||
|
||||
This can be useful if you want to allow run images from any registry in your
|
||||
organization or for any other particular use case.
|
||||
|
||||
```yaml
|
||||
apiVersion: capsule.clastix.io/v1alpha1
|
||||
kind: Tenant
|
||||
spec:
|
||||
containerRegistries:
|
||||
allowed: []
|
||||
regex: "internal.registry.\\w.tld"
|
||||
```
|
||||
|
||||
A Pod running `internal.registry.foo.tld` as registry will be allowed, as well
|
||||
`internal.registry.bar.tld` since these are matching the regular expression.
|
||||
|
||||
> You can also set a catch-all as .* to allow every kind of registry,
|
||||
> that would be the same result of unsetting `containerRegistries` at all
|
||||
|
||||
## Allowing from a list of registries
|
||||
|
||||
For more strict requirements, you can specify an array of string.
|
||||
|
||||
```yaml
|
||||
apiVersion: capsule.clastix.io/v1alpha1
|
||||
kind: Tenant
|
||||
spec:
|
||||
containerRegistries:
|
||||
allowed:
|
||||
- docker.io
|
||||
- quay.io
|
||||
regex: ""
|
||||
```
|
||||
|
||||
> In case of naked and official images hosted on Docker Hub, Capsule is going
|
||||
> to retrieve the registry even if it's not explicit: a `busybox:latest` Pod
|
||||
> running on a Tenant allowing `docker.io` will not blocked, even if the image
|
||||
> field is not explicit as `docker.io/busybox:latest`.
|
||||
|
||||
Reference in New Issue
Block a user