From b3752d81358369cbf67227df950946be6ed85213 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Thu, 16 Apr 2020 10:20:49 +0200 Subject: [PATCH] Support password authentication --- operator/params.yaml | 4 + operator/templates/node-scripts.yaml | 6 +- operator/templates/repair-job.yaml | 6 +- 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, 161 insertions(+), 9 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..0341bdd4 100644 --- a/operator/templates/repair-job.yaml +++ b/operator/templates/repair-job.yaml @@ -12,7 +12,7 @@ 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 $(cat /etc/cassandra/authentication/username) -pw $(cat /etc/cassandra/authentication/password){{ end }} repair"] 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}) +}