From bb2f436856d2972ed5c2ef3107c241731340375c Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Thu, 16 Apr 2020 10:20:49 +0200 Subject: [PATCH 1/9] Support password authentication --- operator/params.yaml | 4 + operator/templates/node-scripts.yaml | 6 +- operator/templates/repair-job.yaml | 18 ++- operator/templates/stateful-set.yaml | 17 ++- templates/operator/params.yaml.template | 4 + .../templates/stateful-set.yaml.template | 17 ++- tests/go.sum | 5 + .../authentication/authentication_test.go | 111 ++++++++++++++++++ 8 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 tests/suites/authentication/authentication_test.go diff --git a/operator/params.yaml b/operator/params.yaml index 15f4270d..5812c436 100644 --- a/operator/params.yaml +++ b/operator/params.yaml @@ -273,6 +273,10 @@ parameters: description: "Authentication backend, implementing IAuthenticator; used to identify users." default: "AllowAllAuthenticator" + - name: AUTHENTICATION_CREDENTIALS_SECRET + description: "Secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry." + default: "" + - name: AUTHORIZER description: "Authorization backend, implementing IAuthorizer; used to limit access/provide permissions." default: "AllowAllAuthorizer" diff --git a/operator/templates/node-scripts.yaml b/operator/templates/node-scripts.yaml index 1fb19735..247efe20 100644 --- a/operator/templates/node-scripts.yaml +++ b/operator/templates/node-scripts.yaml @@ -4,10 +4,12 @@ metadata: name: {{ .Name }}-node-scripts namespace: {{ .Namespace }} data: + node-drain.sh: | + nodetool {{ if .Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} drain node-readiness-probe.sh: | - nodetool status -p {{ .Params.JMX_PORT }} | grep -q "UN ${POD_IP}" + nodetool {{ if .Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} status -p {{ .Params.JMX_PORT }} | grep -q "UN ${POD_IP}" node-liveness-probe.sh: | - nodetool info + nodetool {{ if .Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} info generate-rackdc-properties.sh: | # Generate the rackdc-properties RACK=`kubectl get node -L$RACKLABEL | grep ${NODE_NAME} | awk '{print $6}'` diff --git a/operator/templates/repair-job.yaml b/operator/templates/repair-job.yaml index 124aa660..71d3f1d3 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -12,7 +12,21 @@ spec: spec: containers: - name: repair-job - image: bitnami/kubectl:1.18.0 - command: [ "kubectl", "exec", "{{ $.Params.REPAIR_POD }}", "--", "nodetool", "repair" ] + image: bitnami/kubectl:latest + command: ["/bin/bash"] + args: [ "-c", "kubectl exec {{ $.Params.REPAIR_POD }} -- nodetool {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $SECRET_USERNAME -pw $SECRET_PASSWORD{{ end }} repair"] + {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + env: + - name: SECRET_USERNAME + valueFrom: + secretKeyRef: + name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + key: username + - name: SECRET_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + key: password + {{ end }} restartPolicy: Never serviceAccountName: {{ .Name }}-node-repairer diff --git a/operator/templates/stateful-set.yaml b/operator/templates/stateful-set.yaml index 17f97e85..c9e7383a 100644 --- a/operator/templates/stateful-set.yaml +++ b/operator/templates/stateful-set.yaml @@ -121,8 +121,8 @@ spec: preStop: exec: command: - - nodetool - - drain + - /bin/bash + - /etc/cassandra/node-drain.sh readinessProbe: exec: command: @@ -222,6 +222,9 @@ spec: - name: jvm-options mountPath: /etc/cassandra/jvm.options subPath: jvm.options + - name: node-scripts + mountPath: /etc/cassandra/node-drain.sh + subPath: node-drain.sh - name: node-scripts mountPath: /etc/cassandra/node-readiness-probe.sh subPath: node-readiness-probe.sh @@ -239,6 +242,11 @@ spec: - name: generate-tls-artifacts mountPath: /etc/tls/bin {{ end }} + {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + mountPath: /etc/cassandra/authentication + readOnly: true + {{ end }} {{ if eq $.Params.PROMETHEUS_EXPORTER_ENABLED "true" }} - name: prometheus-exporter image: {{ $.Params.PROMETHEUS_EXPORTER_DOCKER_IMAGE }} @@ -356,6 +364,11 @@ spec: name: {{ $.Name }}-generate-tls-artifacts-sh defaultMode: 0755 {{ end }} + {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + secret: + secretName: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + {{ end }} volumeClaimTemplates: - metadata: name: var-lib-cassandra diff --git a/templates/operator/params.yaml.template b/templates/operator/params.yaml.template index 248dc0d6..e857b495 100644 --- a/templates/operator/params.yaml.template +++ b/templates/operator/params.yaml.template @@ -273,6 +273,10 @@ parameters: description: "Authentication backend, implementing IAuthenticator; used to identify users." default: "AllowAllAuthenticator" + - name: AUTHENTICATION_CREDENTIALS_SECRET + description: "Secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry." + default: "" + - name: AUTHORIZER description: "Authorization backend, implementing IAuthorizer; used to limit access/provide permissions." default: "AllowAllAuthorizer" diff --git a/templates/operator/templates/stateful-set.yaml.template b/templates/operator/templates/stateful-set.yaml.template index 17f97e85..c9e7383a 100644 --- a/templates/operator/templates/stateful-set.yaml.template +++ b/templates/operator/templates/stateful-set.yaml.template @@ -121,8 +121,8 @@ spec: preStop: exec: command: - - nodetool - - drain + - /bin/bash + - /etc/cassandra/node-drain.sh readinessProbe: exec: command: @@ -222,6 +222,9 @@ spec: - name: jvm-options mountPath: /etc/cassandra/jvm.options subPath: jvm.options + - name: node-scripts + mountPath: /etc/cassandra/node-drain.sh + subPath: node-drain.sh - name: node-scripts mountPath: /etc/cassandra/node-readiness-probe.sh subPath: node-readiness-probe.sh @@ -239,6 +242,11 @@ spec: - name: generate-tls-artifacts mountPath: /etc/tls/bin {{ end }} + {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + mountPath: /etc/cassandra/authentication + readOnly: true + {{ end }} {{ if eq $.Params.PROMETHEUS_EXPORTER_ENABLED "true" }} - name: prometheus-exporter image: {{ $.Params.PROMETHEUS_EXPORTER_DOCKER_IMAGE }} @@ -356,6 +364,11 @@ spec: name: {{ $.Name }}-generate-tls-artifacts-sh defaultMode: 0755 {{ end }} + {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + secret: + secretName: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + {{ end }} volumeClaimTemplates: - metadata: name: var-lib-cassandra diff --git a/tests/go.sum b/tests/go.sum index 28597bc7..c10a2274 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -16,6 +16,7 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= @@ -39,6 +40,7 @@ github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+Nmgy github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053 h1:H/GMMKYPkEIC3DF/JWQz8Pdd+Feifov2EIgGfNpeogI= github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053/go.mod h1:xW8sBma2LE3QxFSzCnH9qe6gAE2yO9GvQaWwX89HxbE= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -332,6 +334,7 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -561,6 +564,7 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20191106092431-e228e37189d3 h1:ho4SukHOmqjp7XHH7nPNx7GcgDK6ObVflhAQAT7MvpE= gopkg.in/yaml.v3 v3.0.0-20191106092431-e228e37189d3/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= @@ -607,6 +611,7 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= sigs.k8s.io/controller-runtime v0.5.1 h1:TNidCfVoU/cs2i+9xoTcL/l7yhl0bDhYXU0NCG6wmiE= sigs.k8s.io/controller-runtime v0.5.1/go.mod h1:Uojny7gvg55YLQnEGnPzRE3dC4ik2tRlZJgOUCWXAV4= sigs.k8s.io/controller-tools v0.2.6/go.mod h1:9VKHPszmf2DHz/QmHkcfZoewO6BL7pPs9uAiBVsaJSE= +sigs.k8s.io/kind v0.6.1 h1:13zGO85bX34eyJFQWilTWyuqncRAhCaHoffF9vPuYbg= sigs.k8s.io/kind v0.6.1/go.mod h1:dhW5h0O4PRVq8B6eExphIa9mBnrlBzxEz3R/P1YcYj0= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= diff --git a/tests/suites/authentication/authentication_test.go b/tests/suites/authentication/authentication_test.go new file mode 100644 index 00000000..5e8a8933 --- /dev/null +++ b/tests/suites/authentication/authentication_test.go @@ -0,0 +1,111 @@ +package authentication + +import ( + "fmt" + "os" + "testing" + "time" + + testclient "github.com/kudobuilder/test-tools/pkg/client" + "github.com/kudobuilder/test-tools/pkg/kubernetes" + "github.com/kudobuilder/test-tools/pkg/kudo" + . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/reporters" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + + "github.com/mesosphere/kudo-cassandra-operator/tests/cassandra" +) + +var ( + kubeConfigPath = os.Getenv("KUBECONFIG") + operatorName = os.Getenv("OPERATOR_NAME") + operatorDirectory = os.Getenv("OPERATOR_DIRECTORY") + + instanceName = fmt.Sprintf("%s-instance", operatorName) + testNamespace = "authentication" +) + +var _ = Describe("Authentication tests", func() { + var ( + client testclient.Client + credentials kubernetes.Secret + operator kudo.Operator + ) + + AfterEach(func() { + err := operator.Uninstall() + Expect(err).NotTo(HaveOccurred()) + + err = credentials.Delete() + Expect(err).NotTo(HaveOccurred()) + + err = kubernetes.DeleteNamespace(client, testNamespace) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("when using the 'PasswordAuthenticator'", func() { + It("should authenticate 'nodetool' calls", func() { + var err error + + client, err = testclient.NewForConfig(kubeConfigPath) + Expect(err).NotTo(HaveOccurred()) + + By("Setting up namespace") + err = kubernetes.CreateNamespace(client, testNamespace) + if !errors.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + + By("Adding a secret containing the default user credentials") + const secretName = "authn-credentials" ////nolint:gosec + + credentials, err = kubernetes.CreateSecret(secretName). + WithNamespace(testNamespace). + WithStringData(map[string]string{ + "username": "cassandra", + "password": "cassandra", + }). + Do(client) + Expect(err).NotTo(HaveOccurred()) + + By("Installing the operator with 'PasswordAuthenticator'") + parameters := map[string]string{ + "AUTHENTICATOR": "PasswordAuthenticator", + "AUTHENTICATION_CREDENTIALS_SECRET": secretName, + } + + operator, err = kudo.InstallOperator(operatorDirectory). + WithNamespace(testNamespace). + WithInstance(instanceName). + WithParameters(parameters). + Do(client) + Expect(err).NotTo(HaveOccurred()) + + err = operator.Instance.WaitForPlanComplete("deploy", kudo.WaitTimeout(time.Minute*10)) + Expect(err).NotTo(HaveOccurred()) + + By("Triggering a Cassandra node repair which uses 'nodetool'") + podName, err := cassandra.FirstPodName(operator.Instance) + Expect(err).To(BeNil()) + + err = operator.Instance.UpdateParameters(map[string]string{ + "REPAIR_POD": podName, + }) + Expect(err).To(BeNil()) + + err = operator.Instance.WaitForPlanComplete("repair-pod") + Expect(err).To(BeNil()) + + repair, err := cassandra.NodeWasRepaired(client, operator.Instance) + Expect(err).To(BeNil()) + Expect(repair).To(BeTrue()) + }) + }) +}) + +func TestService(t *testing.T) { + RegisterFailHandler(Fail) + junitReporter := reporters.NewJUnitReporter("authentication-test-junit.xml") + RunSpecsWithDefaultAndCustomReporters(t, "Authentication tests", []Reporter{junitReporter}) +} From 45ae0e8617a3bd1754655c4b914326f168e9f6b4 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Mon, 20 Apr 2020 09:30:15 +0200 Subject: [PATCH 2/9] Address review comments --- operator/params.yaml | 8 ++++++-- operator/templates/node-scripts.yaml | 10 +++++++--- operator/templates/repair-job.yaml | 10 +++++----- operator/templates/stateful-set.yaml | 12 ++++++------ templates/operator/params.yaml.template | 8 ++++++-- .../operator/templates/stateful-set.yaml.template | 12 ++++++------ tests/suites/authentication/authentication_test.go | 4 ++-- 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/operator/params.yaml b/operator/params.yaml index 5812c436..59e725b4 100644 --- a/operator/params.yaml +++ b/operator/params.yaml @@ -273,8 +273,8 @@ parameters: description: "Authentication backend, implementing IAuthenticator; used to identify users." default: "AllowAllAuthenticator" - - name: AUTHENTICATION_CREDENTIALS_SECRET - description: "Secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry." + - name: AUTHENTICATION_SECRET_NAME + description: "Name of the secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry." default: "" - name: AUTHORIZER @@ -781,6 +781,10 @@ parameters: description: "Base64-encoded Cassandra properties appended to cassandra.yaml." default: "" + - name: KUBECTL_VERSION + description: "Version of 'bitnami/kubectl' image. This image is used for some functionality of the operator." + default: "1.18.2" + ################################################################################ ################################ JVM Options ################################### ################################################################################ diff --git a/operator/templates/node-scripts.yaml b/operator/templates/node-scripts.yaml index 247efe20..524ab141 100644 --- a/operator/templates/node-scripts.yaml +++ b/operator/templates/node-scripts.yaml @@ -1,3 +1,7 @@ +{{ $auth_params := "" }} +{{ if .Params.AUTHENTICATION_SECRET_NAME }} +{{ $auth_params = "-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password)" }} +{{ end }} apiVersion: v1 kind: ConfigMap metadata: @@ -5,11 +9,11 @@ metadata: namespace: {{ .Namespace }} data: node-drain.sh: | - nodetool {{ if .Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} drain + nodetool {{ $auth_params }} drain node-readiness-probe.sh: | - nodetool {{ if .Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} status -p {{ .Params.JMX_PORT }} | grep -q "UN ${POD_IP}" + nodetool {{ $auth_params }} status -p {{ .Params.JMX_PORT }} | grep -q "UN ${POD_IP}" node-liveness-probe.sh: | - nodetool {{ if .Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} info + nodetool {{ $auth_params }} info generate-rackdc-properties.sh: | # Generate the rackdc-properties RACK=`kubectl get node -L$RACKLABEL | grep ${NODE_NAME} | awk '{print $6}'` diff --git a/operator/templates/repair-job.yaml b/operator/templates/repair-job.yaml index 71d3f1d3..24a95862 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -12,20 +12,20 @@ spec: spec: containers: - name: repair-job - image: bitnami/kubectl:latest + image: bitnami/kubectl:{{ $.Params.KUBECTL_VERSION }} command: ["/bin/bash"] - args: [ "-c", "kubectl exec {{ $.Params.REPAIR_POD }} -- nodetool {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }}-u $SECRET_USERNAME -pw $SECRET_PASSWORD{{ end }} repair"] - {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + args: [ "-c", "kubectl exec {{ $.Params.REPAIR_POD }} -- nodetool {{ if $.Params.AUTHENTICATION_SECRET_NAME }}-u $SECRET_USERNAME -pw $SECRET_PASSWORD{{ end }} repair"] + {{ if $.Params.AUTHENTICATION_SECRET_NAME }} env: - name: SECRET_USERNAME valueFrom: secretKeyRef: - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + name: {{ $.Params.AUTHENTICATION_SECRET_NAME }} key: username - name: SECRET_PASSWORD valueFrom: secretKeyRef: - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + name: {{ $.Params.AUTHENTICATION_SECRET_NAME }} key: password {{ end }} restartPolicy: Never diff --git a/operator/templates/stateful-set.yaml b/operator/templates/stateful-set.yaml index c9e7383a..21c352a0 100644 --- a/operator/templates/stateful-set.yaml +++ b/operator/templates/stateful-set.yaml @@ -242,8 +242,8 @@ spec: - name: generate-tls-artifacts mountPath: /etc/tls/bin {{ end }} - {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} - - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + {{ if $.Params.AUTHENTICATION_SECRET_NAME }} + - name: authentication-secret mountPath: /etc/cassandra/authentication readOnly: true {{ end }} @@ -291,7 +291,7 @@ spec: {{ if $.Params.NODE_TOPOLOGY }} initContainers: - name: node-resolver - image: bitnami/kubectl:latest + image: bitnami/kubectl:{{ $.Params.KUBECTL_VERSION }} command: - "sh" - "-c" @@ -364,10 +364,10 @@ spec: name: {{ $.Name }}-generate-tls-artifacts-sh defaultMode: 0755 {{ end }} - {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} - - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + {{ if $.Params.AUTHENTICATION_SECRET_NAME }} + - name: authentication-secret secret: - secretName: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + secretName: {{ $.Params.AUTHENTICATION_SECRET_NAME }} {{ end }} volumeClaimTemplates: - metadata: diff --git a/templates/operator/params.yaml.template b/templates/operator/params.yaml.template index e857b495..d9347f9e 100644 --- a/templates/operator/params.yaml.template +++ b/templates/operator/params.yaml.template @@ -273,8 +273,8 @@ parameters: description: "Authentication backend, implementing IAuthenticator; used to identify users." default: "AllowAllAuthenticator" - - name: AUTHENTICATION_CREDENTIALS_SECRET - description: "Secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry." + - name: AUTHENTICATION_SECRET_NAME + description: "Name of the secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry." default: "" - name: AUTHORIZER @@ -781,6 +781,10 @@ parameters: description: "Base64-encoded Cassandra properties appended to cassandra.yaml." default: "" + - name: KUBECTL_VERSION + description: "Version of 'bitnami/kubectl' image. This image is used for some functionality of the operator." + default: "1.18.2" + ################################################################################ ################################ JVM Options ################################### ################################################################################ diff --git a/templates/operator/templates/stateful-set.yaml.template b/templates/operator/templates/stateful-set.yaml.template index c9e7383a..21c352a0 100644 --- a/templates/operator/templates/stateful-set.yaml.template +++ b/templates/operator/templates/stateful-set.yaml.template @@ -242,8 +242,8 @@ spec: - name: generate-tls-artifacts mountPath: /etc/tls/bin {{ end }} - {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} - - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + {{ if $.Params.AUTHENTICATION_SECRET_NAME }} + - name: authentication-secret mountPath: /etc/cassandra/authentication readOnly: true {{ end }} @@ -291,7 +291,7 @@ spec: {{ if $.Params.NODE_TOPOLOGY }} initContainers: - name: node-resolver - image: bitnami/kubectl:latest + image: bitnami/kubectl:{{ $.Params.KUBECTL_VERSION }} command: - "sh" - "-c" @@ -364,10 +364,10 @@ spec: name: {{ $.Name }}-generate-tls-artifacts-sh defaultMode: 0755 {{ end }} - {{ if $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} - - name: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + {{ if $.Params.AUTHENTICATION_SECRET_NAME }} + - name: authentication-secret secret: - secretName: {{ $.Params.AUTHENTICATION_CREDENTIALS_SECRET }} + secretName: {{ $.Params.AUTHENTICATION_SECRET_NAME }} {{ end }} volumeClaimTemplates: - metadata: diff --git a/tests/suites/authentication/authentication_test.go b/tests/suites/authentication/authentication_test.go index 5e8a8933..d1af6bd6 100644 --- a/tests/suites/authentication/authentication_test.go +++ b/tests/suites/authentication/authentication_test.go @@ -71,8 +71,8 @@ var _ = Describe("Authentication tests", func() { By("Installing the operator with 'PasswordAuthenticator'") parameters := map[string]string{ - "AUTHENTICATOR": "PasswordAuthenticator", - "AUTHENTICATION_CREDENTIALS_SECRET": secretName, + "AUTHENTICATOR": "PasswordAuthenticator", + "AUTHENTICATION_SECRET_NAME": secretName, } operator, err = kudo.InstallOperator(operatorDirectory). From 604b041a1100b5c3091f8a0b07a50dab0bdcfdd2 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Mon, 20 Apr 2020 09:46:12 +0200 Subject: [PATCH 3/9] SSL support for node drain --- operator/templates/node-scripts.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/operator/templates/node-scripts.yaml b/operator/templates/node-scripts.yaml index 46b54ca8..f261db71 100644 --- a/operator/templates/node-scripts.yaml +++ b/operator/templates/node-scripts.yaml @@ -9,7 +9,11 @@ metadata: namespace: {{ .Namespace }} data: node-drain.sh: | + {{ if ne $.Params.JMX_LOCAL_ONLY "true" }} + nodetool {{ $auth_params }} --ssl drain + {{ else }} nodetool {{ $auth_params }} drain + {{ end }} node-readiness-probe.sh: | {{ if ne $.Params.JMX_LOCAL_ONLY "true" }} nodetool {{ $auth_params }} --ssl status -p {{ .Params.JMX_PORT }} | grep -q "UN ${POD_IP}" From 43c6c75b3a8eb8753bfe6f7ff757d10e8af43a57 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Mon, 20 Apr 2020 10:42:49 +0200 Subject: [PATCH 4/9] Docs --- docs/parameters.md | 3 +++ docs/security.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/parameters.md b/docs/parameters.md index 6c161126..f21cc3a6 100644 --- a/docs/parameters.md +++ b/docs/parameters.md @@ -65,6 +65,7 @@ | **MAX_HINTS_DELIVERY_THREADS** | The maximum number of delivery threads for hinted handoff. | 2 | | **BATCHLOG_REPLAY_THROTTLE_IN_KB** | The total maximum throttle for replaying failed logged batches in KBs per second. | 1024 | | **AUTHENTICATOR** | Authentication backend, implementing IAuthenticator; used to identify users. | AllowAllAuthenticator | +| **AUTHENTICATION_SECRET_NAME** | Name of the secret containing the credentials used by the operator when running 'nodetool' for its functionality. Only relevant if AUTHENTICATOR is set to 'PasswordAuthenticator'. The secret needs to have a 'username' and a 'password' entry. | | | **AUTHORIZER** | Authorization backend, implementing IAuthorizer; used to limit access/provide permissions. | AllowAllAuthorizer | | **ROLE_MANAGER** | Part of the Authentication & Authorization backend that implements IRoleManager to maintain grants and memberships between roles, By default, the value set is Apache Cassandra's out of the box Role Manager: CassandraRoleManager | CassandraRoleManager | | **ROLES_VALIDITY_IN_MS** | Validity period for roles cache; set to 0 to disable | 2000 | @@ -191,6 +192,7 @@ | **REPAIR_SESSION_MAX_TREE_DEPTH** | Limits the maximum Merkle tree depth to avoid consuming too much memory during repairs. | | | **ENABLE_SASI_INDEXES** | Enables SASI index creation on this node. SASI indexes are considered experimental and are not recommended for production use. | | | **CUSTOM_CASSANDRA_YAML_BASE64** | Base64-encoded Cassandra properties appended to cassandra.yaml. | | +| **KUBECTL_VERSION** | Version of 'bitnami/kubectl' image. This image is used for some functionality of the operator. | 1.18.2 | | **JVM_OPT_AVAILABLE_PROCESSORS** | In a multi-instance deployment, multiple Cassandra instances will independently assume that all CPU processors are available to it. This setting allows you to specify a smaller set of processors and perhaps have affinity. | | | **JVM_OPT_JOIN_RING** | Set to false to start Cassandra on a node but not have the node join the cluster. | | | **JVM_OPT_LOAD_RING_STATE** | Set to false to clear all gossip state for the node on restart. Use when you have changed node information in cassandra.yaml (such as listen_address). | | @@ -219,3 +221,4 @@ | **JVM_OPT_G1R_SET_UPDATING_PAUSE_TIME_PERCENT** | Have the JVM do less remembered set work during STW, instead preferring concurrent GC. Reduces p99.9 latency. | | | **CUSTOM_JVM_OPTIONS_BASE64** | Base64-encoded JVM options appended to jvm.options. | | | **POD_MANAGEMENT_POLICY** | podManagementPolicy of the Cassandra Statefulset | OrderedReady | +| **REPAIR_POD** | Name of the pod on which 'nodetool repair' should be run. | | diff --git a/docs/security.md b/docs/security.md index 39715f93..fe22b122 100644 --- a/docs/security.md +++ b/docs/security.md @@ -82,3 +82,34 @@ kubectl kudo install cassandra \ Check out the [parameters reference](./parameters.md) for a complete list of all configurable settings available for KUDO Cassandra security. + +## Authentication and Authorization + +The KUDO Cassandra operator can be configured to authenticate and authorize access to the Cassandra cluster. The `AUTHENTICATOR` parameter sets the [authenticator](http://cassandra.apache.org/doc/3.11/operating/security.html#authentication), the `AUTHORIZER` parameter sets the [authorizer](http://cassandra.apache.org/doc/3.11/operating/security.html#authorization). + +### Authentication credentials + +Some functionality of the operator use `nodetool`, thus these calls need to be authenticated as well. With enabled password authentication, create a [secret](https://kubernetes.io/docs/concepts/configuration/secret/) that contains the credentials of the user the operator should use and set the `AUTHENTICATION_SECRET_NAME` parameter accordingly. + +Here's an example of a secret that uses the default cassandra/cassandra credentials: + +``` +apiVersion: v1 +kind: Secret +metadata: + name: cassandra-credential +type: Opaque +data: + username: Y2Fzc2FuZHJh + password: Y2Fzc2FuZHJh +``` + +Reference this when installing the Cassandra operator with authentication. + +``` +kubectl kudo install cassandra \ + --instance=cassandra \ + --namespace=kudo-cassandra \ + -p AUTHENTICATOR=PasswordAuthenticator \ + -p AUTHENTICATION_SECRET_NAME=cassandra-credential +``` From 261485400bb234de915c159d0b73cb92457d2674 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Mon, 20 Apr 2020 15:19:53 +0200 Subject: [PATCH 5/9] Use a pseudo-file for passwords --- operator/templates/node-scripts.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/templates/node-scripts.yaml b/operator/templates/node-scripts.yaml index f261db71..298a7315 100644 --- a/operator/templates/node-scripts.yaml +++ b/operator/templates/node-scripts.yaml @@ -1,6 +1,6 @@ {{ $auth_params := "" }} {{ if .Params.AUTHENTICATION_SECRET_NAME }} -{{ $auth_params = "-u $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password)" }} +{{ $auth_params = "-u $(cat /etc/cassandra/authentication/username) -pwf <(paste -d ' ' /etc/cassandra/authentication/username /etc/cassandra/authentication/password)" }} {{ end }} apiVersion: v1 kind: ConfigMap From 4ba6b5d52fd9bd4791fb7abe4302ea6a260390d1 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Mon, 20 Apr 2020 15:34:53 +0200 Subject: [PATCH 6/9] Use password file for repair job --- operator/templates/repair-job.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/operator/templates/repair-job.yaml b/operator/templates/repair-job.yaml index 24a95862..310b3666 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -1,3 +1,7 @@ +{{ $auth_params := "" }} +{{ if $.Params.AUTHENTICATION_SECRET_NAME }} +{{ $auth_params = "-u ${SECRET_USERNAME} -pwf <(echo \"${SECRET_USERNAME} ${SECRET_PASSWORD}\")" }} +{{ end }} --- apiVersion: batch/v1 kind: Job @@ -14,7 +18,7 @@ spec: - name: repair-job image: bitnami/kubectl:{{ $.Params.KUBECTL_VERSION }} command: ["/bin/bash"] - args: [ "-c", "kubectl exec {{ $.Params.REPAIR_POD }} -- nodetool {{ if $.Params.AUTHENTICATION_SECRET_NAME }}-u $SECRET_USERNAME -pw $SECRET_PASSWORD{{ end }} repair"] + args: [ "-c", "kubectl exec {{ $.Params.REPAIR_POD }} -- nodetool {{ $auth_params }} repair"] {{ if $.Params.AUTHENTICATION_SECRET_NAME }} env: - name: SECRET_USERNAME From db219ec2b51a8f896a8bd75044b909508ed55be8 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Mon, 20 Apr 2020 16:38:28 +0200 Subject: [PATCH 7/9] Fix escaping --- operator/templates/repair-job.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/templates/repair-job.yaml b/operator/templates/repair-job.yaml index 310b3666..7ecd17fc 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -1,6 +1,6 @@ {{ $auth_params := "" }} {{ if $.Params.AUTHENTICATION_SECRET_NAME }} -{{ $auth_params = "-u ${SECRET_USERNAME} -pwf <(echo \"${SECRET_USERNAME} ${SECRET_PASSWORD}\")" }} +{{ $auth_params = "-u ${SECRET_USERNAME} -pwf <(echo \\\"${SECRET_USERNAME} ${SECRET_PASSWORD}\\\")" }} {{ end }} --- apiVersion: batch/v1 From 8c5cd43be0f286876f112b0cbc1fcd10826fade7 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Tue, 21 Apr 2020 08:19:23 +0200 Subject: [PATCH 8/9] Let's not bother with escaping echo --- operator/templates/repair-job.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/templates/repair-job.yaml b/operator/templates/repair-job.yaml index 7ecd17fc..11ed196f 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -1,6 +1,6 @@ {{ $auth_params := "" }} {{ if $.Params.AUTHENTICATION_SECRET_NAME }} -{{ $auth_params = "-u ${SECRET_USERNAME} -pwf <(echo \\\"${SECRET_USERNAME} ${SECRET_PASSWORD}\\\")" }} +{{ $auth_params = "-u ${SECRET_USERNAME} -pwf <(echo ${SECRET_USERNAME} ${SECRET_PASSWORD})" }} {{ end }} --- apiVersion: batch/v1 From 91a2f0a169ca61e00d30b7c10991030ab82de515 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Tue, 21 Apr 2020 10:20:05 +0200 Subject: [PATCH 9/9] Can't use pseudo files with 'kubectl exec' --- operator/templates/repair-job.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/templates/repair-job.yaml b/operator/templates/repair-job.yaml index 11ed196f..2869df65 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -1,6 +1,6 @@ {{ $auth_params := "" }} {{ if $.Params.AUTHENTICATION_SECRET_NAME }} -{{ $auth_params = "-u ${SECRET_USERNAME} -pwf <(echo ${SECRET_USERNAME} ${SECRET_PASSWORD})" }} +{{ $auth_params = "-u ${SECRET_USERNAME} -pw ${SECRET_PASSWORD}" }} {{ end }} --- apiVersion: batch/v1