Compare commits

..

5 Commits

Author SHA1 Message Date
Maksim Fedotov
afae361627 fix(helm): jobs in capsule helm chart should use the same tolerations as deployment 2022-04-07 08:16:03 +00:00
Dario Tranchitella
535ef7412c chore(ci): force use of go 1.16 2022-04-06 15:52:22 +00:00
Davide Imola
f373debf54 fix: fixing the helm chart 2022-03-31 13:02:25 +00:00
Davide Imola
569d803e95 fix: using configuration for mutating and validating webhooks 2022-03-31 13:02:25 +00:00
Davide Imola
7b3b0d6504 fix: using configuration for tls and ca secret names 2022-03-31 13:02:25 +00:00
15 changed files with 162 additions and 69 deletions

View File

@@ -36,7 +36,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
go-version: '1.16'
- run: make installer
- name: Checking if YAML installer file is not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi

View File

@@ -37,7 +37,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
go-version: '1.16'
- run: make manifests
- name: Checking if manifests are disaligned
run: test -z "$(git diff 2> /dev/null)"
@@ -47,7 +47,7 @@ jobs:
run: go get github.com/onsi/ginkgo/ginkgo
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
go-version: '1.16'
- uses: engineerd/setup-kind@v0.5.0
with:
skipClusterCreation: true

View File

@@ -5,4 +5,8 @@ const (
ForbiddenNodeLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-node-labels-regexp"
ForbiddenNodeAnnotationsAnnotation = "capsule.clastix.io/forbidden-node-annotations"
ForbiddenNodeAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-node-annotations-regexp"
CASecretNameAnnotation = "capsule.clastix.io/ca-secret-name"
TLSSecretNameAnnotation = "capsule.clastix.io/tls-secret-name"
MutatingWebhookConfigurationName = "capsule.clastix.io/mutating-webhook-configuration-name"
ValidatingWebhookConfigurationName = "capsule.clastix.io/validating-webhook-configuration-name"
)

View File

@@ -21,7 +21,7 @@ sources:
# 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.
version: 0.1.7
version: 0.1.8
# This is the version number of the application being deployed.
# This version number should be incremented each time you make changes to the application.

View File

@@ -4,8 +4,12 @@ metadata:
name: default
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
capsule.clastix.io/ca-secret-name: {{ include "capsule.secretCaName" . }}
capsule.clastix.io/mutating-webhook-configuration-name: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
capsule.clastix.io/tls-secret-name: {{ include "capsule.secretTlsName" . }}
capsule.clastix.io/validating-webhook-configuration-name: {{ include "capsule.fullname" . }}-validating-webhook-configuration
{{- with .Values.customAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:

View File

@@ -1,4 +1,4 @@
{{- $cmd := "while [ -z $$(kubectl -n $NAMESPACE get secret capsule-tls -o jsonpath='{.data.tls\\\\.crt}') ];" -}}
{{- $cmd := printf "while [ -z $$(kubectl -n $NAMESPACE get secret %s -o jsonpath='{.data.tls\\\\.crt}') ];" (include "capsule.secretCaName" .) -}}
{{- $cmd = printf "%s do echo 'waiting Capsule to be up and running...' && sleep 5;" $cmd -}}
{{- $cmd = printf "%s done" $cmd -}}
apiVersion: batch/v1
@@ -29,6 +29,10 @@ spec:
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
restartPolicy: Never
containers:
- name: post-install-job

View File

@@ -30,6 +30,10 @@ spec:
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
restartPolicy: Never
containers:
- name: pre-delete-job

View File

@@ -24,18 +24,20 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/clastix/capsule/pkg/cert"
"github.com/clastix/capsule/pkg/configuration"
)
type CAReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
Namespace string
Log logr.Logger
Scheme *runtime.Scheme
Namespace string
Configuration configuration.Configuration
}
func (r *CAReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Secret{}, forOptionPerInstanceName(CASecretName)).
For(&corev1.Secret{}).
Complete(r)
}
@@ -78,7 +80,7 @@ func (r *CAReconciler) UpdateCustomResourceDefinition(caBundle []byte) error {
func (r CAReconciler) UpdateValidatingWebhookConfiguration(caBundle []byte) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
vw := &admissionregistrationv1.ValidatingWebhookConfiguration{}
err = r.Get(context.TODO(), types.NamespacedName{Name: "capsule-validating-webhook-configuration"}, vw)
err = r.Get(context.TODO(), types.NamespacedName{Name: r.Configuration.ValidatingWebhookConfigurationName()}, vw)
if err != nil {
r.Log.Error(err, "cannot retrieve ValidatingWebhookConfiguration")
return err
@@ -97,7 +99,7 @@ func (r CAReconciler) UpdateValidatingWebhookConfiguration(caBundle []byte) erro
func (r CAReconciler) UpdateMutatingWebhookConfiguration(caBundle []byte) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
mw := &admissionregistrationv1.MutatingWebhookConfiguration{}
err = r.Get(context.TODO(), types.NamespacedName{Name: "capsule-mutating-webhook-configuration"}, mw)
err = r.Get(context.TODO(), types.NamespacedName{Name: r.Configuration.MutatingWebhookConfigurationName()}, mw)
if err != nil {
r.Log.Error(err, "cannot retrieve MutatingWebhookConfiguration")
return err
@@ -115,6 +117,10 @@ func (r CAReconciler) UpdateMutatingWebhookConfiguration(caBundle []byte) error
func (r CAReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
var err error
if request.Name != r.Configuration.CASecretName() {
return ctrl.Result{}, nil
}
r.Log = r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
r.Log.Info("Reconciling CA Secret")
@@ -128,7 +134,7 @@ func (r CAReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl
var ca cert.CA
var rq time.Duration
ca, err = getCertificateAuthority(r.Client, r.Namespace)
ca, err = getCertificateAuthority(r.Client, r.Namespace, r.Configuration.CASecretName())
if err != nil && errors.Is(err, MissingCaError{}) {
ca, err = cert.GenerateCertificateAuthority()
if err != nil {
@@ -189,7 +195,7 @@ func (r CAReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl
tls := &corev1.Secret{}
err = r.Get(ctx, types.NamespacedName{
Namespace: r.Namespace,
Name: TLSSecretName,
Name: r.Configuration.TLSSecretName(),
}, tls)
if err != nil {
r.Log.Error(err, "Capsule TLS Secret missing")

View File

@@ -6,7 +6,4 @@ package secret
const (
certSecretKey = "tls.crt"
privateKeySecretKey = "tls.key"
CASecretName = "capsule-ca"
TLSSecretName = "capsule-tls"
)

View File

@@ -9,23 +9,20 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/clastix/capsule/pkg/cert"
)
func getCertificateAuthority(client client.Client, namespace string) (ca cert.CA, err error) {
func getCertificateAuthority(client client.Client, namespace, name string) (ca cert.CA, err error) {
instance := &corev1.Secret{}
err = client.Get(context.TODO(), types.NamespacedName{
Namespace: namespace,
Name: CASecretName,
Name: name,
}, instance)
if err != nil {
return nil, fmt.Errorf("missing secret %s, cannot reconcile", CASecretName)
return nil, fmt.Errorf("missing secret %s, cannot reconcile", name)
}
if instance.Data == nil {
@@ -39,24 +36,3 @@ func getCertificateAuthority(client client.Client, namespace string) (ca cert.CA
return
}
func forOptionPerInstanceName(instanceName string) builder.ForOption {
return builder.WithPredicates(predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
return filterByName(event.Object.GetName(), instanceName)
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return filterByName(deleteEvent.Object.GetName(), instanceName)
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
return filterByName(updateEvent.ObjectNew.GetName(), instanceName)
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return filterByName(genericEvent.Object.GetName(), instanceName)
},
})
}
func filterByName(objName, desired string) bool {
return objName == desired
}

View File

@@ -22,24 +22,30 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/clastix/capsule/pkg/cert"
"github.com/clastix/capsule/pkg/configuration"
)
type TLSReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
Namespace string
Log logr.Logger
Scheme *runtime.Scheme
Namespace string
Configuration configuration.Configuration
}
func (r *TLSReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Secret{}, forOptionPerInstanceName(TLSSecretName)).
For(&corev1.Secret{}).
Complete(r)
}
func (r TLSReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
var err error
if request.Name != r.Configuration.TLSSecretName() {
return ctrl.Result{}, nil
}
r.Log = r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
r.Log.Info("Reconciling TLS Secret")
@@ -54,7 +60,7 @@ func (r TLSReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctr
var ca cert.CA
var rq time.Duration
ca, err = getCertificateAuthority(r.Client, r.Namespace)
ca, err = getCertificateAuthority(r.Client, r.Namespace, r.Configuration.CASecretName())
if err != nil {
return reconcile.Result{}, err
}
@@ -112,7 +118,7 @@ func (r TLSReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctr
return reconcile.Result{}, err
}
if instance.Name == TLSSecretName && res == controllerutil.OperationResultUpdated {
if instance.Name == r.Configuration.TLSSecretName() && res == controllerutil.OperationResultUpdated {
r.Log.Info("Capsule TLS certificates has been updated, Controller pods must be restarted to load new certificate")
hostname, _ := os.Hostname()

View File

@@ -151,17 +151,26 @@ apiVersion: capsule.clastix.io/v1alpha1
kind: CapsuleConfiguration
metadata:
name: default
annotations:
capsule.clastix.io/ca-secret-name: "capsule-ca"
capsule.clastix.io/tls-secret-name: "capsule-tls"
capsule.clastix.io/mutating-webhook-configuration-name: "capsule-mutating-webhook-configuration"
capsule.clastix.io/validating-webhook-configuration-name: "capsule-validating-webhook-configuration"
spec:
userGroups: ["capsule.clastix.io"]
forceTenantPrefix: false
protectedNamespaceRegex: ""
```
Option | Description | Default
--- | --- | ---
`.spec.forceTenantPrefix` | Force the tenant name as prefix for namespaces: `<tenant_name>-<namespace>`. | `false`
`.spec.userGroups` | Array of Capsule groups to which all tenant owners must belong. | `[capsule.clastix.io]`
`.spec.protectedNamespaceRegex` | Disallows creation of namespaces matching the passed regexp. | `null`
Option | Description | Default
--- |------------------------------------------------------------------------------| ---
`.spec.forceTenantPrefix` | Force the tenant name as prefix for namespaces: `<tenant_name>-<namespace>`. | `false`
`.spec.userGroups` | Array of Capsule groups to which all tenant owners must belong. | `[capsule.clastix.io]`
`.spec.protectedNamespaceRegex` | Disallows creation of namespaces matching the passed regexp. | `null`
`.metadata.annotations.capsule.clastix.io/ca-secret-name` | Set the Capsule Certificate Authority secret name | `capsule-ca`
`.metadata.annotations.capsule.clastic.io/tls-secret-name` | Set the Capsule TLS secret name | `capsule-tls`
`.metadata.annotations.capsule.clastix.io/mutating-webhook-configuration-name` | Set the MutatingWebhookConfiguration name | `mutating-webhook-configuration-name`
`.metadata.annotations.capsule.clastix.io/validating-webhook-configuration-name` | Set the ValidatingWebhookConfiguration name | `validating-webhook-configuration-name`
Upon installation using Kustomize or Helm, a `capsule-default` resource will be created.
The reference to this configuration is managed by the CLI flag `--configuration-name`.

37
main.go
View File

@@ -20,6 +20,7 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
@@ -127,21 +128,25 @@ func main() {
ctx := ctrl.SetupSignalHandler()
cfg := configuration.NewCapsuleConfiguration(manager.GetClient(), configurationName)
if err = (&secretcontroller.CAReconciler{
Client: manager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("CA"),
Scheme: manager.GetScheme(),
Namespace: namespace,
Client: manager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("CA"),
Scheme: manager.GetScheme(),
Namespace: namespace,
Configuration: cfg,
}).SetupWithManager(manager); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
os.Exit(1)
}
if err = (&secretcontroller.TLSReconciler{
Client: manager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Tls"),
Scheme: manager.GetScheme(),
Namespace: namespace,
Client: manager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Tls"),
Scheme: manager.GetScheme(),
Namespace: namespace,
Configuration: cfg,
}).SetupWithManager(manager); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
os.Exit(1)
@@ -153,13 +158,23 @@ func main() {
os.Exit(1)
}
ca, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretcontroller.CASecretName, metav1.GetOptions{})
directClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{
Scheme: manager.GetScheme(),
Mapper: manager.GetRESTMapper(),
})
if err != nil {
setupLog.Error(err, "unable to create the direct client")
os.Exit(1)
}
directCfg := configuration.NewCapsuleConfiguration(directClient, configurationName)
ca, err := clientset.CoreV1().Secrets(namespace).Get(ctx, directCfg.CASecretName(), metav1.GetOptions{})
if err != nil {
setupLog.Error(err, "unable to get Capsule CA secret")
os.Exit(1)
}
tls, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretcontroller.TLSSecretName, metav1.GetOptions{})
tls, err := clientset.CoreV1().Secrets(namespace).Get(ctx, directCfg.TLSSecretName(), metav1.GetOptions{})
if err != nil {
setupLog.Error(err, "unable to get Capsule TLS secret")
os.Exit(1)
@@ -194,8 +209,6 @@ func main() {
os.Exit(1)
}
cfg := configuration.NewCapsuleConfiguration(manager.GetClient(), configurationName)
// webhooks: the order matters, don't change it and just append
webhooksList := append(
make([]webhook.Webhook, 0),

View File

@@ -13,9 +13,8 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
// capsuleConfiguration is the Capsule Configuration retrieval mode
@@ -62,6 +61,66 @@ func (c capsuleConfiguration) ForceTenantPrefix() bool {
return c.retrievalFn().Spec.ForceTenantPrefix
}
func (c capsuleConfiguration) CASecretName() (name string) {
name = CASecretName
if c.retrievalFn().Annotations == nil {
return
}
v, ok := c.retrievalFn().Annotations[capsulev1alpha1.CASecretNameAnnotation]
if ok {
return v
}
return
}
func (c capsuleConfiguration) TLSSecretName() (name string) {
name = TLSSecretName
if c.retrievalFn().Annotations == nil {
return
}
v, ok := c.retrievalFn().Annotations[capsulev1alpha1.TLSSecretNameAnnotation]
if ok {
return v
}
return
}
func (c capsuleConfiguration) MutatingWebhookConfigurationName() (name string) {
name = MutatingWebhookConfigurationName
if c.retrievalFn().Annotations == nil {
return
}
v, ok := c.retrievalFn().Annotations[capsulev1alpha1.MutatingWebhookConfigurationName]
if ok {
return v
}
return
}
func (c capsuleConfiguration) ValidatingWebhookConfigurationName() (name string) {
name = ValidatingWebhookConfigurationName
if c.retrievalFn().Annotations == nil {
return
}
v, ok := c.retrievalFn().Annotations[capsulev1alpha1.ValidatingWebhookConfigurationName]
if ok {
return v
}
return
}
func (c capsuleConfiguration) UserGroups() []string {
return c.retrievalFn().Spec.UserGroups
}

View File

@@ -9,9 +9,20 @@ import (
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
const (
CASecretName = "capsule-ca"
TLSSecretName = "capsule-tls"
MutatingWebhookConfigurationName = "capsule-mutating-webhook-configuration"
ValidatingWebhookConfigurationName = "capsule-validating-webhook-configuration"
)
type Configuration interface {
ProtectedNamespaceRegexp() (*regexp.Regexp, error)
ForceTenantPrefix() bool
CASecretName() string
TLSSecretName() string
MutatingWebhookConfigurationName() string
ValidatingWebhookConfigurationName() string
UserGroups() []string
ForbiddenUserNodeLabels() *capsulev1beta1.ForbiddenListSpec
ForbiddenUserNodeAnnotations() *capsulev1beta1.ForbiddenListSpec