Skip to content

Commit

Permalink
Support Naming Policy (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
bells17 committed Nov 11, 2021
1 parent 80b5de6 commit 606318c
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 41 deletions.
2 changes: 1 addition & 1 deletion charts/accurate/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.0
version: 0.2.1

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
Expand Down
19 changes: 19 additions & 0 deletions charts/accurate/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ controller:
- version: v1
kind: ResourceQuota

# controller.config.namingPolicies -- List of nameing policy for SubNamespaces.
# root and match are both regular expressions.
# When a SubNamespace is created in a tree starting from a root namespace and the root namespace's name matches the "root" regular expression, the SubNamespace name is validated with the "match" regular expression.
#
# "match" namingPolicies can use variables of regexp capture group naming of "root" namingPolicies.
# example:
# root: ^app-(?P<team>.*)
# match: ^app-${team}-.*
# root namespace: app-team1
# compiled match naming policy: ^app-team1-.*
# This feature is provided using https://pkg.go.dev/regexp#Regexp.Expand
# namingPolicies:
# - root: foo
# match: foo_.*
# - root: bar
# match: bar_.*
# - root: ^app-(?P<team>.*)
# match: ^app-${team}-.*

additionalRBAC:
# controller.additionalRBAC.rules -- Specify the RBAC rules to be added to the controller.
# ClusterRole and ClusterRoleBinding are created with the names `{{ release name }}-additional-resources`.
Expand Down
4 changes: 3 additions & 1 deletion cmd/accurate-controller/sub/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ func subMain(ns, addr string, port int) error {
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to create SubNamespace controller: %w", err)
}
hooks.SetupSubNamespaceWebhook(mgr, dec)
if err = hooks.SetupSubNamespaceWebhook(mgr, dec, cfg.NamingPolicyRegexps); err != nil {
return fmt.Errorf("unable to create SubNamespace webhook: %w", err)
}

// Resource propagation controller
for _, res := range watched {
Expand Down
27 changes: 0 additions & 27 deletions config.yaml

This file was deleted.

60 changes: 59 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,46 @@ Read [Helm Chart](helm.md) for details.
The repository includes an example as follows:

```yaml
{{#include ../config.yaml}}
# Labels to be propagated to sub-namespaces.
# It is also possible to specify a glob pattern that can be interpreted by Go's "path.Match" func.
# https://pkg.go.dev/path#Match
labelKeys:
- team

# Annotations to be propagated to sub-namespaces.
# It is also possible to specify a glob pattern that can be interpreted by Go's "path.Match" func.
# https://pkg.go.dev/path#Match
annotationKeys:
# An example to propagate an annotation for MetalLB
# https://metallb.universe.tf/usage/#requesting-specific-ips
- metallb.universe.tf/address-pool

# List of GVK for namespace-scoped resources that can be propagated.
# Any namespace-scoped resource is allowed.
watches:
- group: rbac.authorization.k8s.io
version: v1
kind: Role
- group: rbac.authorization.k8s.io
version: v1
kind: RoleBinding
- version: v1
kind: Secret
- version: v1
kind: ResourceQuota

# List of nameing policy for SubNamespaces.
# root and match are both regular expressions.
# When a SubNamespace is created in a tree starting from a root namespace and the root namespace's name matches the "root" regular expression, the SubNamespace name is validated with the "match" regular expression.
#
# "match" namingPolicies can use variables of regexp capture group naming of "root" namingPolicies.
# example:
# root: ^app-(?P<team>.*)
# match: ^app-${team}-.*
# root namespace: app-team1
# compiled match naming policy: ^app-team1-.*
# This feature is provided using https://pkg.go.dev/regexp#Regexp.Expand
namingPolicies: []
```

Only labels and annotations specified in the configuration file will be inherited.
Expand Down Expand Up @@ -49,6 +88,25 @@ controller:
kind: RoleBinding
- version: v1
kind: Secret

# controller.config.namingPolicies -- List of nameing policy for SubNamespaces.
# root and match are both regular expressions.
# When a SubNamespace is created in a tree starting from a root namespace and the root namespace's name matches the "root" regular expression, the SubNamespace name is validated with the "match" regular expression.
#
# "match" namingPolicies can use variables of regexp capture group naming of "root" namingPolicies.
# example:
# root: ^app-(?P<team>.*)
# match: ^app-${team}-.*
# root namespace: app-team1
# compiled match naming policy: ^app-team1-.*
# This feature is provided using https://pkg.go.dev/regexp#Regexp.Expand
namingPolicies:
- root: foo
match: foo_.*
- root: bar
match: bar_.*
- root: ^app-(?P<team>.*)
match: ^app-${team}-.*
<snip>
```

Expand Down
67 changes: 60 additions & 7 deletions hooks/subnamespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"encoding/json"
"fmt"
"net/http"
"regexp"

accuratev1 "github.com/cybozu-go/accurate/api/v1"
"github.com/cybozu-go/accurate/pkg/config"
"github.com/cybozu-go/accurate/pkg/constants"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand Down Expand Up @@ -47,7 +50,8 @@ func (m *subNamespaceMutator) Handle(ctx context.Context, req admission.Request)

type subNamespaceValidator struct {
client.Client
dec *admission.Decoder
dec *admission.Decoder
namingPolicies []config.NamingPolicyRegexp
}

var _ admission.Handler = &subNamespaceValidator{}
Expand All @@ -67,15 +71,62 @@ func (v *subNamespaceValidator) Handle(ctx context.Context, req admission.Reques
return admission.Errored(http.StatusInternalServerError, err)
}

if ns.Labels[constants.LabelType] == constants.NSTypeRoot || ns.Labels[constants.LabelParent] != "" {
return admission.Allowed("")
if ns.Labels[constants.LabelType] != constants.NSTypeRoot && ns.Labels[constants.LabelParent] == "" {
return admission.Denied(fmt.Sprintf("namespace %s is neither a root nor a sub namespace", ns.Name))
}

root, err := v.getRootNamespace(ctx, ns)
if err != nil {
return admission.Denied(err.Error())
}
ok, msg, err := v.notMatchingNamingPolicy(ctx, sn.Name, root.Name)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
if !ok {
return admission.Denied(fmt.Sprintf("namespace %s is not match naming policies: %s", ns.Name, msg))
}
return admission.Allowed("")
}

func (v *subNamespaceValidator) getRootNamespace(ctx context.Context, ns *corev1.Namespace) (*corev1.Namespace, error) {
if ns.Labels[constants.LabelType] == constants.NSTypeRoot {
return ns, nil
}

return admission.Denied(fmt.Sprintf("namespace %s is neither a root nor a sub namespace", ns.Name))
parent := &corev1.Namespace{}
if err := v.Get(ctx, client.ObjectKey{Name: ns.Labels[constants.LabelParent]}, parent); err != nil {
if !apierrors.IsNotFound(err) {
return nil, fmt.Errorf("failed to get namespace %s: %w", ns.Labels[constants.LabelParent], err)
}
return nil, fmt.Errorf("namespace %s is not found", ns.Labels[constants.LabelParent])
}
return v.getRootNamespace(ctx, parent)
}

func (v *subNamespaceValidator) notMatchingNamingPolicy(ctx context.Context, ns, root string) (bool, string, error) {
for _, policy := range v.namingPolicies {
matches := policy.Root.FindAllStringSubmatchIndex(root, -1)
if len(matches) > 0 {
m := []byte{}
for _, match := range matches {
m = policy.Root.ExpandString(m, policy.Match, root, match)
}
r, err := regexp.Compile(string(m))
if err != nil {
return false, "", fmt.Errorf("invalid naming policy: %w", err)
}

if !r.MatchString(ns) {
return false, fmt.Sprintf("namespace - target=%s root=%s denied policy - root=%s match=%s", ns, root, policy.Root, policy.Match), nil
}
}
}
return true, "", nil
}

// SetupSubNamespaceWebhook registers the webhooks for SubNamespace
func SetupSubNamespaceWebhook(mgr manager.Manager, dec *admission.Decoder) {
func SetupSubNamespaceWebhook(mgr manager.Manager, dec *admission.Decoder, namingPolicyRegexps []config.NamingPolicyRegexp) error {
serv := mgr.GetWebhookServer()

m := &subNamespaceMutator{
Expand All @@ -84,8 +135,10 @@ func SetupSubNamespaceWebhook(mgr manager.Manager, dec *admission.Decoder) {
serv.Register("/mutate-accurate-cybozu-com-v1-subnamespace", &webhook.Admission{Handler: m})

v := &subNamespaceValidator{
Client: mgr.GetClient(),
dec: dec,
Client: mgr.GetClient(),
dec: dec,
namingPolicies: namingPolicyRegexps,
}
serv.Register("/validate-accurate-cybozu-com-v1-subnamespace", &webhook.Admission{Handler: v})
return nil
}
Loading

0 comments on commit 606318c

Please sign in to comment.