Skip to content

Commit

Permalink
add auth enabler
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirill-Garbar committed Jun 6, 2024
1 parent 3d90173 commit 4ae2408
Show file tree
Hide file tree
Showing 21 changed files with 925 additions and 157 deletions.
19 changes: 2 additions & 17 deletions .github/workflows/make-test-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,9 @@ on:

jobs:
test-e2e:
name: test-e2e on k8s ${{ matrix.k8s.attribute }} version
name: test-e2e on k8s 1.30.0 version
# Pull request has label 'ok-to-test' or the author is a member of the organization
if: contains(github.event.pull_request.labels.*.name, 'ok-to-test') || contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.pull_request.author_association)
strategy:
matrix:
k8s:
- version: 1.28.3
attribute: penultimate
- version: 1.29.3
attribute: previous
- version: default
attribute: latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4.1.6
Expand All @@ -33,10 +24,4 @@ jobs:
kubectl-version: v1.30.0
# Empty kubeconfig file
base64-kube-config: "YXBpVmVyc2lvbjogdjEKa2luZDogQ29uZmlnCnByZWZlcmVuY2VzOiB7fQo="
- run: |
if [ "${{ matrix.k8s.version }}" = "default" ]; then
# For latest version use default from Makefile
make test-e2e
else
ENVTEST_K8S_VERSION=${{ matrix.k8s.version }} make test-e2e
fi
- run: make test-e2e
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ vendor

# editor and IDE paraphernalia
.idea
.vscode
*.swp
*.swo
*~
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ mod-tidy: ## Run go mod tidy against code.

.PHONY: test
test: manifests generate fmt vet envtest ## Run tests.
echo "Check for kubernetes version $(ENVTEST_K8S_VERSION_TRIMMED_V) in $(ENVTEST)"
@$(ENVTEST) list | grep -q $(ENVTEST_K8S_VERSION_TRIMMED_V)
@echo "Check for kubernetes version $(ENVTEST_K8S_VERSION_TRIMMED_V) in $(ENVTEST)"
@$(ENVTEST) list | grep $(ENVTEST_K8S_VERSION_TRIMMED_V) > /dev/null
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION_TRIMMED_V) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out

# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
Expand Down
25 changes: 25 additions & 0 deletions api/v1alpha1/aux_functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package v1alpha1

func IsClientSecurityEnabled(c *EtcdCluster) bool {
clientSecurityEnabled := false
if c.Spec.Security != nil && c.Spec.Security.TLS.ClientSecret != "" {
clientSecurityEnabled = true
}
return clientSecurityEnabled
}

func IsServerSecurityEnabled(c *EtcdCluster) bool {
serverSecurityEnabled := false
if c.Spec.Security != nil && c.Spec.Security.TLS.ServerSecret != "" {
serverSecurityEnabled = true
}
return serverSecurityEnabled
}

func IsServerCADefined(c *EtcdCluster) bool {
serverCADefined := false
if c.Spec.Security != nil && c.Spec.Security.TLS.ServerTrustedCASecret != "" {
serverCADefined = true
}
return serverCADefined
}
109 changes: 109 additions & 0 deletions api/v1alpha1/aux_functions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package v1alpha1_test

import (
"github.com/aenix-io/etcd-operator/api/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Aux Functions", func() {

Context("When running IsClientSecurityEnabled function", func() {
It("should return true if ClientSecret is set", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{
Security: &v1alpha1.SecuritySpec{
TLS: v1alpha1.TLSSpec{
ClientSecret: "some-client-secret",
},
},
},
}
Expect(v1alpha1.IsClientSecurityEnabled(cluster)).To(BeTrue())
})

It("should return false if ClientSecret is not set", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{
Security: &v1alpha1.SecuritySpec{
TLS: v1alpha1.TLSSpec{},
},
},
}
Expect(v1alpha1.IsClientSecurityEnabled(cluster)).To(BeFalse())
})

It("should return false if Security is nil", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{},
}
Expect(v1alpha1.IsClientSecurityEnabled(cluster)).To(BeFalse())
})
})

Context("When running IsServerSecurityEnabled function", func() {
It("should return true if ServerSecret is set", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{
Security: &v1alpha1.SecuritySpec{
TLS: v1alpha1.TLSSpec{
ServerSecret: "some-server-secret",
},
},
},
}
Expect(v1alpha1.IsServerSecurityEnabled(cluster)).To(BeTrue())
})

It("should return false if ServerSecret is not set", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{
Security: &v1alpha1.SecuritySpec{
TLS: v1alpha1.TLSSpec{},
},
},
}
Expect(v1alpha1.IsServerSecurityEnabled(cluster)).To(BeFalse())
})

It("should return false if Security is nil", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{},
}
Expect(v1alpha1.IsServerSecurityEnabled(cluster)).To(BeFalse())
})
})

Context("When running IsServerCADefined function", func() {
It("should return true if ServerTrustedCASecret is set", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{
Security: &v1alpha1.SecuritySpec{
TLS: v1alpha1.TLSSpec{
ServerTrustedCASecret: "some-ca-secret",
},
},
},
}
Expect(v1alpha1.IsServerCADefined(cluster)).To(BeTrue())
})

It("should return false if ServerTrustedCASecret is not set", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{
Security: &v1alpha1.SecuritySpec{
TLS: v1alpha1.TLSSpec{},
},
},
}
Expect(v1alpha1.IsServerCADefined(cluster)).To(BeFalse())
})

It("should return false if Security is nil", func() {
cluster := &v1alpha1.EtcdCluster{
Spec: v1alpha1.EtcdClusterSpec{},
}
Expect(v1alpha1.IsServerCADefined(cluster)).To(BeFalse())
})
})
})
16 changes: 14 additions & 2 deletions api/v1alpha1/etcdcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,24 +174,36 @@ type SecuritySpec struct {
// Section for user-managed tls certificates
// +optional
TLS TLSSpec `json:"tls,omitempty"`
// Section to enable etcd auth
EnableAuth bool `json:"enableAuth,omitempty"`
}

// TLSSpec defines user-managed certificates names.
type TLSSpec struct {
// Trusted CA certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have tls.crt field in the secret.
// Trusted CA certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have ca.crt field in the secret.
// This secret must be created in the namespace with etcdCluster CR.
// +optional
PeerTrustedCASecret string `json:"peerTrustedCASecret,omitempty"`
// Certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have tls.crt and tls.key fields in the secret.
// This secret must be created in the namespace with etcdCluster CR.
// +optional
PeerSecret string `json:"peerSecret,omitempty"`
// Trusted CA for etcd server certificates for client-server communication. Is necessary to set trust between operator and etcd.
// It is expected to have ca.crt field in the secret. If it is not specified, then insecure communication will be used.
// This secret must be created in the namespace with etcdCluster CR.
// +optional
ServerTrustedCASecret string `json:"serverTrustedCASecret,omitempty"`
// Server certificate secret to secure client-server communication. Is provided to the client who connects to etcd by client port (2379 by default).
// It is expected to have tls.crt and tls.key fields in the secret.
// This secret must be created in the namespace with etcdCluster CR.
// +optional
ServerSecret string `json:"serverSecret,omitempty"`
// Trusted CA for client certificates that are provided by client to etcd. It is expected to have tls.crt field in the secret.
// Trusted CA for client certificates that are provided by client to etcd. It is expected to have ca.crt field in the secret.
// This secret must be created in the namespace with etcdCluster CR.
// +optional
ClientTrustedCASecret string `json:"clientTrustedCASecret,omitempty"`
// Client certificate for etcd-operator to do maintenance. It is expected to have tls.crt and tls.key fields in the secret.
// This secret must be created in the namespace with etcdCluster CR.
// +optional
ClientSecret string `json:"clientSecret,omitempty"`
}
Expand Down
9 changes: 9 additions & 0 deletions api/v1alpha1/etcdcluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ func (r *EtcdCluster) validateSecurity() field.ErrorList {
)
}

if security.EnableAuth && (security.TLS.ClientSecret == "" || security.TLS.ServerSecret == "") {

allErrors = append(allErrors, field.Invalid(
field.NewPath("spec", "security"),
security.TLS,
"if auth is enabled, client secret and server secret must be provided"),
)
}

if len(allErrors) > 0 {
return allErrors
}
Expand Down
60 changes: 60 additions & 0 deletions api/v1alpha1/etcdcluster_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,66 @@ var _ = Describe("EtcdCluster Webhook", func() {
}
}
})

It("Shouldn't reject if auth is enabled and security client certs are defined", func() {
localCluster := etcdCluster.DeepCopy()
localCluster.Spec.Security = &SecuritySpec{
EnableAuth: true,
TLS: TLSSpec{
ClientTrustedCASecret: "test-client-trusted-ca-cert",
ClientSecret: "test-client-cert",
ServerSecret: "test-server-cert",
},
}
err := localCluster.validateSecurity()
Expect(err).To(BeNil())
})

It("Should reject if auth is enabled and one of client and server certs is defined", func() {
localCluster := etcdCluster.DeepCopy()
localCluster.Spec.Security = &SecuritySpec{
EnableAuth: true,
TLS: TLSSpec{
ClientSecret: "test-client-cert",
ClientTrustedCASecret: "test-client-trusted-ca-cert",
},
}
err := localCluster.validateSecurity()
if Expect(err).NotTo(BeNil()) {
expectedFieldErr := field.Invalid(
field.NewPath("spec", "security"),
localCluster.Spec.Security.TLS,
"if auth is enabled, client secret and server secret must be provided",
)
if Expect(err).To(HaveLen(1)) {
Expect(*(err[0])).To(Equal(*expectedFieldErr))
}
}

})

It("Should reject if auth is enabled and one of client and server certs is defined", func() {
localCluster := etcdCluster.DeepCopy()
localCluster.Spec.Security = &SecuritySpec{
EnableAuth: true,
TLS: TLSSpec{
ServerSecret: "test-server-cert",
},
}
err := localCluster.validateSecurity()
if Expect(err).NotTo(BeNil()) {
expectedFieldErr := field.Invalid(
field.NewPath("spec", "security"),
localCluster.Spec.Security.TLS,
"if auth is enabled, client secret and server secret must be provided",
)
if Expect(err).To(HaveLen(1)) {
Expect(*(err[0])).To(Equal(*expectedFieldErr))
}
}

})

})

Context("Validate PDB", func() {
Expand Down
26 changes: 22 additions & 4 deletions charts/etcd-operator/crds/etcd-cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -202,25 +202,43 @@ spec:
security:
description: Security describes security settings of etcd (authentication, certificates, rbac)
properties:
enableAuth:
description: Section to enable etcd auth
type: boolean
tls:
description: Section for user-managed tls certificates
properties:
clientSecret:
description: Client certificate for etcd-operator to do maintenance. It is expected to have tls.crt and tls.key fields in the secret.
description: |-
Client certificate for etcd-operator to do maintenance. It is expected to have tls.crt and tls.key fields in the secret.
This secret must be created in the namespace with etcdCluster CR.
type: string
clientTrustedCASecret:
description: Trusted CA for client certificates that are provided by client to etcd. It is expected to have tls.crt field in the secret.
description: |-
Trusted CA for client certificates that are provided by client to etcd. It is expected to have ca.crt field in the secret.
This secret must be created in the namespace with etcdCluster CR.
type: string
peerSecret:
description: Certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have tls.crt and tls.key fields in the secret.
description: |-
Certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have tls.crt and tls.key fields in the secret.
This secret must be created in the namespace with etcdCluster CR.
type: string
peerTrustedCASecret:
description: Trusted CA certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have tls.crt field in the secret.
description: |-
Trusted CA certificate secret to secure peer-to-peer communication between etcd nodes. It is expected to have ca.crt field in the secret.
This secret must be created in the namespace with etcdCluster CR.
type: string
serverSecret:
description: |-
Server certificate secret to secure client-server communication. Is provided to the client who connects to etcd by client port (2379 by default).
It is expected to have tls.crt and tls.key fields in the secret.
This secret must be created in the namespace with etcdCluster CR.
type: string
serverTrustedCASecret:
description: |-
Trusted CA for etcd server certificates for client-server communication. Is necessary to set trust between operator and etcd.
It is expected to have ca.crt field in the secret. If it is not specified, then insecure communication will be used.
This secret must be created in the namespace with etcdCluster CR.
type: string
type: object
type: object
Expand Down
6 changes: 6 additions & 0 deletions charts/etcd-operator/templates/workload/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ spec:
- configMapRef:
name: {{ include "etcd-operator.fullname" . }}-env
{{- end }}
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
Expand Down
Loading

0 comments on commit 4ae2408

Please sign in to comment.