diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 777440d1dc..c9011ea6b8 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -48,7 +48,7 @@ as well as the global.name setting. {{- define "consul.serverTLSAltNames" -}} {{- $name := include "consul.fullname" . -}} {{- $ns := .Release.Namespace -}} -{{ printf "localhost,%s-server,*.%s-server,*.%s-server.%s,*.%s-server.%s.svc,*.server.%s.%s" $name $name $name $ns $name $ns (.Values.global.datacenter ) (.Values.global.domain) }}{{ include "consul.serverAdditionalDNSSANs" . }} +{{ printf "localhost,%s-server,*.%s-server,*.%s-server.%s,%s-server.%s,*.%s-server.%s.svc,%s-server.%s.svc,*.server.%s.%s" $name $name $name $ns $name $ns $name $ns $name $ns (.Values.global.datacenter ) (.Values.global.domain) }}{{ include "consul.serverAdditionalDNSSANs" . }} {{- end -}} {{- define "consul.serverAdditionalDNSSANs" -}} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index d5d4a07de0..7d5cdb2407 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -10,6 +10,7 @@ {{- if and .Values.global.federation.enabled .Values.global.adminPartitions.enabled }}{{ fail "If global.federation.enabled is true, global.adminPartitions.enabled must be false because they are mutually exclusive" }}{{ end }} {{- if (and .Values.global.enterpriseLicense.secretName (not .Values.global.enterpriseLicense.secretKey)) }}{{fail "enterpriseLicense.secretKey and secretName must both be specified." }}{{ end -}} {{- if (and (not .Values.global.enterpriseLicense.secretName) .Values.global.enterpriseLicense.secretKey) }}{{fail "enterpriseLicense.secretKey and secretName must both be specified." }}{{ end -}} +{{- if and .Values.externalServers.enabled (not .Values.externalServers.hosts) }}{{ fail "externalServers.hosts must be set if externalServers.enabled is true" }}{{ end -}} # DaemonSet to run the Consul clients on every node. apiVersion: apps/v1 kind: DaemonSet @@ -48,6 +49,7 @@ spec: annotations: {{- if .Values.global.secretsBackend.vault.enabled }} "vault.hashicorp.com/agent-inject": "true" + "vault.hashicorp.com/agent-init-first": "true" "vault.hashicorp.com/role": "{{ .Values.global.secretsBackend.vault.consulClientRole }}" {{- if and .Values.global.secretsBackend.vault.ca.secretName .Values.global.secretsBackend.vault.ca.secretKey }} "vault.hashicorp.com/agent-extra-secret": "{{ .Values.global.secretsBackend.vault.ca.secretName }}" @@ -124,6 +126,9 @@ spec: - name: config configMap: name: {{ template "consul.fullname" . }}-client-config + - name: consul-data + emptyDir: + medium: "Memory" {{- if .Values.global.tls.enabled }} {{- if not .Values.global.secretsBackend.vault.enabled }} - name: consul-ca-cert @@ -136,7 +141,8 @@ spec: items: - key: {{ default "tls.crt" .Values.global.tls.caCert.secretKey }} path: tls.crt - {{ if not .Values.global.tls.enableAutoEncrypt }} + {{- end }} + {{- if (and (not .Values.global.secretsBackend.vault.enabled) (not .Values.global.tls.enableAutoEncrypt)) }} - name: consul-ca-key secret: {{- if .Values.global.tls.caKey.secretName }} @@ -154,7 +160,6 @@ spec: medium: "Memory" {{- end }} {{- end }} - {{- end }} {{- range .Values.client.extraVolumes }} - name: userconfig-{{ .name }} {{ .type }}: @@ -177,7 +182,21 @@ spec: containers: - name: consul image: "{{ default .Values.global.image .Values.client.image }}" + {{- if .Values.global.acls.manageSystemACLs }} + lifecycle: + preStop: + exec: + command: + - "/bin/sh" + - "-ec" + - | + consul logout + {{- end }} env: + {{- if .Values.global.acls.manageSystemACLs }} + - name: CONSUL_HTTP_TOKEN_FILE + value: "/consul/login/acl-token" + {{- end }} - name: ADVERTISE_IP valueFrom: fieldRef: @@ -340,6 +359,9 @@ spec: mountPath: /consul/data - name: config mountPath: /consul/config + - mountPath: /consul/login + name: consul-data + readOnly: true {{- if .Values.global.tls.enabled }} {{- if not .Values.global.secretsBackend.vault.enabled }} - name: consul-ca-cert @@ -435,17 +457,57 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: client-acl-init image: {{ .Values.global.imageK8S }} + env: + - name: CONSUL_HTTP_ADDR + {{- if .Values.global.tls.enabled }} + value: https://{{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc:8501 + {{- else }} + value: http://{{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc:8500 + {{- end }} + {{- if (and .Values.global.tls.enabled (not .Values.externalServers.useSystemRoots)) }} + - name: CONSUL_CACERT + {{- if .Values.global.secretsBackend.vault.enabled }} + value: "/vault/secrets/serverca.crt" + {{- else }} + value: "/consul/tls/ca/tls.crt" + {{- end }} + {{- end }} command: - "/bin/sh" - "-ec" - | consul-k8s-control-plane acl-init \ - -secret-name="{{ template "consul.fullname" . }}-client-acl-token" \ - -k8s-namespace={{ .Release.Namespace }} \ + -component-name=client \ + -acl-auth-method="{{ template "consul.fullname" . }}-k8s-component-auth-method" \ + {{- if .Values.global.adminPartitions.enabled }} + -partition={{ .Values.global.adminPartitions.name }} \ + {{- end }} + -log-level={{ default .Values.global.logLevel .Values.client.logLevel }} \ + -log-json={{ .Values.global.logJSON }} \ + {{- if .Values.externalServers.enabled }} + {{- if .Values.global.tls.enabled }} + -use-https \ + {{- end }} + {{- range .Values.externalServers.hosts }} + -server-address={{ quote . }} \ + {{- end }} + -server-port={{ .Values.externalServers.httpsPort }} \ + {{- if .Values.externalServers.tlsServerName }} + -tls-server-name={{ .Values.externalServers.tlsServerName }} \ + {{- end }} + {{- end }} -init-type="client" volumeMounts: - name: aclconfig mountPath: /consul/aclconfig + - mountPath: /consul/login + name: consul-data + readOnly: false + {{- if (and (not .Values.global.secretsBackend.vault.enabled) (not .Values.externalServers.useSystemRoots)) }} + - name: consul-ca-cert + mountPath: /consul/tls/ca + readOnly: false + {{- end }} resources: requests: memory: "25Mi" diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index fe5f26fd86..492160da0b 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -86,10 +86,10 @@ spec: {{- if .Values.global.tls.enabled }} -use-https \ {{- if not .Values.externalServers.useSystemRoots }} - -consul-ca-cert=/consul/tls/ca/tls.crt \ + -ca-file=/consul/tls/ca/tls.crt \ {{- end }} {{- if .Values.externalServers.tlsServerName }} - -consul-tls-server-name={{ .Values.externalServers.tlsServerName }} \ + -tls-server-name={{ .Values.externalServers.tlsServerName }} \ {{- end }} {{- end }} -partition-name={{ .Values.global.adminPartitions.name }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index fd221cf91f..cf137d388f 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -244,7 +244,7 @@ spec: {{- end }} {{- if not (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} - -create-client-token=false \ + -client=false \ {{- end }} {{- if .Values.global.acls.createReplicationToken }} diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index ba75d94460..47dd6462b0 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -77,7 +77,9 @@ spec: -additional-dnsname="{{ template "consul.fullname" . }}-server" \ -additional-dnsname="*.{{ template "consul.fullname" . }}-server" \ -additional-dnsname="*.{{ template "consul.fullname" . }}-server.${NAMESPACE}" \ + -additional-dnsname="{{ template "consul.fullname" . }}-server.${NAMESPACE}" \ -additional-dnsname="*.{{ template "consul.fullname" . }}-server.${NAMESPACE}.svc" \ + -additional-dnsname="{{ template "consul.fullname" . }}-server.${NAMESPACE}.svc" \ -additional-dnsname="*.server.{{ .Values.global.datacenter }}.{{ .Values.global.domain }}" \ {{- range .Values.global.tls.serverAdditionalIPSANs }} -additional-ipaddress={{ . }} \ diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index eb1c2b5048..b4d732feb9 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -114,6 +114,7 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'server.enabled=false' \ --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ --set 'client.join[0]=1.1.1.1' \ --set 'client.join[1]=2.2.2.2' \ . | tee /dev/stderr | @@ -132,6 +133,7 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'server.enabled=false' \ --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ --set 'client.join[0]=provider=my-cloud config=val' \ . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].command') @@ -804,6 +806,57 @@ load _helpers [ "${actual}" = "true" ] } +@test "client/DaemonSet: Adds consul envvars CONSUL_HTTP_ADDR on acl-init init container when ACLs are enabled and tls is enabled" { + cd `chart_dir` + local env=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].env[]' | tee /dev/stderr) + + local actual + actual=$(echo $env | jq -r '. | select(.name == "CONSUL_HTTP_ADDR") | .value' | tee /dev/stderr) + [ "${actual}" = "https://RELEASE-NAME-consul-server.default.svc:8501" ] +} + +@test "client/DaemonSet: Adds consul envvars CONSUL_HTTP_ADDR on acl-init init container when ACLs are enabled and tls is not enabled" { + cd `chart_dir` + local env=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].env[]' | tee /dev/stderr) + + local actual + actual=$(echo $env | jq -r '. | select(.name == "CONSUL_HTTP_ADDR") | .value' | tee /dev/stderr) + [ "${actual}" = "http://RELEASE-NAME-consul-server.default.svc:8500" ] +} + +@test "client/DaemonSet: Does not add consul envvars CONSUL_CACERT on acl-init init container when ACLs are enabled and tls is not enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[0].env[] | select(.name == "CONSUL_CACERT")' | tee /dev/stderr) + + [ "${actual}" = "" ] +} + +@test "client/DaemonSet: Adds consul envvars CONSUL_CACERT on acl-init init container when ACLs are enabled and tls is enabled" { + cd `chart_dir` + local env=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].env[]' | tee /dev/stderr) + + local actual=$(echo $env | jq -r '. | select(.name == "CONSUL_CACERT") | .value' | tee /dev/stderr) + [ "${actual}" = "/consul/tls/ca/tls.crt" ] +} + @test "client/DaemonSet: both ACL and TLS init containers are created when global.tls.enabled=true and global.acls.manageSystemACLs=true" { cd `chart_dir` local has_acl_init_container=$(helm template \ @@ -1023,7 +1076,7 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq '.spec.template.spec.volumes[2].name == "aclconfig"' | tee /dev/stderr) + yq '.spec.template.spec.volumes[3].name == "aclconfig"' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -1033,7 +1086,7 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].volumeMounts[2]' | tee /dev/stderr) + yq '.spec.template.spec.containers[0].volumeMounts[3]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.name' | tee /dev/stderr) @@ -1054,7 +1107,7 @@ load _helpers [ "${actual}" = "true" ] } -@test "client/DaemonSet: init container is created when global.acls.manageSystemACLs=true" { +@test "client/DaemonSet: init container is created when global.acls.manageSystemACLs=true and command args are properly set" { cd `chart_dir` local object=$(helm template \ -s templates/client-daemonset.yaml \ @@ -1065,6 +1118,350 @@ load _helpers local actual=$(echo $object | yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) [ "${actual}" = "true" ] + +local actual=$(echo $object | + yq -r '.command | any(contains("secret-name"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("k8s-namespace"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("component-name=client"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("init-type=\"client\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("acl-auth-method=\"RELEASE-NAME-consul-k8s-component-auth-method\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("log-json=false"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: init container is created when global.acls.manageSystemACLs=true and has correct command with Partitions enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=default' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "client-acl-init")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("secret-name"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("k8s-namespace"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("component-name=client"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("init-type=\"client\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("acl-auth-method=\"RELEASE-NAME-consul-k8s-component-auth-method\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("log-json=false"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("partition=default"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: CONSUL_HTTP_TOKEN_FILE is not set when acls are disabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=false' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[0].name] | any(contains("CONSUL_HTTP_TOKEN_FILE"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: CONSUL_HTTP_TOKEN_FILE is set when acls are enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[0].name] | any(contains("CONSUL_HTTP_TOKEN_FILE"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: consul-logout preStop hook is added when ACLs are enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].lifecycle.preStop.exec.command[2]] | any(contains("consul logout"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: Adds consul login volume when ACLs are enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | yq '.spec.template.spec.volumes[2]' | tee /dev/stderr) + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "consul-data" ] + + local actual=$(echo $object | + yq -r '.emptyDir.medium' | tee /dev/stderr) + [ "${actual}" = "Memory" ] +} + +@test "client/DaemonSet: Adds consul login volumeMount to client container when ACLs are enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | yq '.spec.template.spec.containers[0].volumeMounts[2]' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "consul-data" ] + + local actual=$(echo $object | + yq -r '.mountPath' | tee /dev/stderr) + [ "${actual}" = "/consul/login" ] + + local actual=$(echo $object | + yq -r '.readOnly' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: Adds consul login volumeMount to acl-init init container when ACLs are enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | yq '.spec.template.spec.initContainers[0].volumeMounts[1]' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "consul-data" ] + + local actual=$(echo $object | + yq -r '.mountPath' | tee /dev/stderr) + [ "${actual}" = "/consul/login" ] + + local actual=$(echo $object | + yq -r '.readOnly' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: Adds consul ca cert volumeMount to acl-init init container when ACLs and tls are enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + . | yq '.spec.template.spec.initContainers[0].volumeMounts[2]' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "consul-ca-cert" ] + + local actual=$(echo $object | + yq -r '.mountPath' | tee /dev/stderr) + [ "${actual}" = "/consul/tls/ca" ] + + local actual=$(echo $object | + yq -r '.readOnly' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: Does not add consul ca cert volumeMount to acl-init init container when tls is not enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=false' \ + . | yq '.spec.template.spec.initContainers[0].volumeMounts[2]' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "consul-ca-cert" ] + + local actual=$(echo $object | + yq -r '.mountPath' | tee /dev/stderr) + [ "${actual}" = "/consul/tls/ca" ] + + local actual=$(echo $object | + yq -r '.readOnly' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: fail when externalServers is enabled but the externalServers.hosts is not provided" { + cd `chart_dir` + run helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + . + echo "status:$status" + [ "$status" -eq 1 ] + [[ "$output" =~ "externalServers.hosts must be set if externalServers.enabled is true" ]] +} + +@test "client/DaemonSet: server-address flag is set with hosts when externalServers.hosts are provided" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=foo' \ + --set 'externalServers.hosts[1]=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-server-address=\"foo\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $command | jq -r ' . | any(contains("-server-address=\"bar\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: tls-server-name flag is set when externalServers.tlsServerName is provided" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=computer' \ + --set 'externalServers.tlsServerName=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-tls-server-name=foo"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: tls-server-name flag is not set when externalServers.tlsServerName is not provided" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=computer' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-tls-server-name"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: use-https flag is not set when global.tls.enabled is not provided" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=computer' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-use-https"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: use-https flag is set when global.tls.enabled is provided and externalServers.enabled is true" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=computer' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-use-https"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: use-https flag is not set when global.tls.enabled is enabled but externalServers.enabled is false" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=false' \ + --set 'server.enabled=false' \ + --set 'global.tls.enabled=true' \ + --set 'externalServers.hosts[0]=computer' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-use-https"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: server-port flag is not set when externalServers.enabled is false" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=false' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=computer' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-server-port"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "client/DaemonSet: server-port flag is set when externalServers.enabled is true" { + cd `chart_dir` + local command=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'externalServers.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=computer' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo $command | jq -r ' . | any(contains("-server-port"))' | tee /dev/stderr) + [ "${actual}" = "true" ] } #-------------------------------------------------------------------- @@ -1926,6 +2323,49 @@ rollingUpdate: [ "${actual}" = "" ] } +@test "client/DaemonSet: vault adds consul envvars CONSUL_CACERT on acl-init init container when ACLs are enabled and tls is enabled" { + cd `chart_dir` + local env=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.secretsBackend.vault.manageSystemACLsRole=true' \ + --set 'global.acls.replicationToken.secretName=replication' \ + --set 'global.acls.replicationToken.secretKey=key' \ + --set 'global.tls.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'server.serverCert.secretName=pki_int/issue/test' \ + --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].env[]' | tee /dev/stderr) + + local actual=$(echo $env | jq -r '. | select(.name == "CONSUL_CACERT") | .value' | tee /dev/stderr) + [ "${actual}" = "/vault/secrets/serverca.crt" ] +} + +@test "client/DaemonSet: Vault does not add consul ca cert volumeMount to acl-init init container when ACLs are enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'server.serverCert.secretName=pki_int/issue/test' \ + --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + . | yq '.spec.template.spec.initContainers[0].volumeMounts[] | select(.name=="consul-ca-cert")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "" ] +} + #-------------------------------------------------------------------- # Vault agent annotations @@ -1937,7 +2377,7 @@ rollingUpdate: --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-init-first")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1966,4 +2406,4 @@ rollingUpdate: [ "$status" -eq 1 ] [[ "$output" =~ "global.imageK8s is not a valid key, use global.imageK8S (note the capital 'S')" ]] -} \ No newline at end of file +} diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index ca1a9a6d37..78b91436f0 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -91,7 +91,7 @@ load _helpers actual=$(echo $command | jq -r '. | any(contains("-use-https"))' | tee /dev/stderr) [ "${actual}" = "true" ] - actual=$(echo $command | jq -r '. | any(contains("-consul-ca-cert=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + actual=$(echo $command | jq -r '. | any(contains("-ca-file=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) [ "${actual}" = "true" ] actual=$(echo $command | jq -r '. | any(contains("-server-port=8501"))' | tee /dev/stderr) @@ -113,7 +113,7 @@ load _helpers yq -r '.spec.template.spec.containers[0].command' | tee /dev/stderr) local actual - actual=$(echo $command | jq -r '. | any(contains("-consul-ca-cert=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + actual=$(echo $command | jq -r '. | any(contains("-ca-file=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) [ "${actual}" = "false" ] } diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 66300c3e15..9864ee7df4 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -99,25 +99,25 @@ load _helpers [[ "$output" =~ "global.bootstrapACLs was removed, use global.acls.manageSystemACLs instead" ]] } -@test "serverACLInit/Job: does not set -create-client-token=false when client is enabled (the default)" { +@test "serverACLInit/Job: does not set -client=false when client is enabled (the default)" { cd `chart_dir` local actual=$(helm template \ -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command[2] | contains("-create-client-token=false")' | + yq '.spec.template.spec.containers[0].command[2] | contains("-client=false")' | tee /dev/stderr) [ "${actual}" = "false" ] } -@test "serverACLInit/Job: sets -create-client-token=false when client is disabled" { +@test "serverACLInit/Job: sets -client=false when client is disabled" { cd `chart_dir` local actual=$(helm template \ -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ --set 'client.enabled=false' \ . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command[2] | contains("-create-client-token=false")' | + yq '.spec.template.spec.containers[0].command[2] | contains("-client=false")' | tee /dev/stderr) [ "${actual}" = "true" ] } diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 9bf1951aff..6811a09d91 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -1786,7 +1786,7 @@ load _helpers local actual=$(echo $object | yq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-servercert.crt"]' | tee /dev/stderr) - local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] local actual="$(echo $object | @@ -1795,7 +1795,7 @@ load _helpers local actual="$(echo $object | yq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-servercert.key"]' | tee /dev/stderr)" - local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' + local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] local actual=$(echo $object | @@ -1846,12 +1846,12 @@ load _helpers local actual=$(echo $object | yq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-servercert.crt"]' | tee /dev/stderr) - local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul,*.foo.com,*.bar.com\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul,*.foo.com,*.bar.com\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] local actual="$(echo $object | yq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-servercert.key"]' | tee /dev/stderr)" - local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul,*.foo.com,*.bar.com\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' + local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul,*.foo.com,*.bar.com\" \"ip_sans=127.0.0.1\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] } @@ -1875,12 +1875,12 @@ load _helpers local actual=$(echo $object | yq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-servercert.crt"]' | tee /dev/stderr) - local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1,1.1.1.1,2.2.2.2\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1,1.1.1.1,2.2.2.2\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] local actual="$(echo $object | yq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-servercert.key"]' | tee /dev/stderr)" - local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1,1.1.1.1,2.2.2.2\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' + local expected=$'{{- with secret \"pki_int/issue/test\" \"common_name=server.dc2.consul\"\n\"alt_names=localhost,RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server,*.RELEASE-NAME-consul-server.default,RELEASE-NAME-consul-server.default,*.RELEASE-NAME-consul-server.default.svc,RELEASE-NAME-consul-server.default.svc,*.server.dc2.consul\" \"ip_sans=127.0.0.1,1.1.1.1,2.2.2.2\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] } diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index 8db52fcfbd..bf4d116574 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -70,6 +70,37 @@ load _helpers [ "${actual}" = "true" ] } +@test "tlsInit/Job: sets additional DNS SANs by default when global.tls.enabled=true" { + cd `chart_dir` + local command=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"RELEASE-NAME-consul-server\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"*.RELEASE-NAME-consul-server\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"*.RELEASE-NAME-consul-server.${NAMESPACE}\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"RELEASE-NAME-consul-server.${NAMESPACE}\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"*.RELEASE-NAME-consul-server.${NAMESPACE}.svc\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"RELEASE-NAME-consul-server.${NAMESPACE}.svc\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo "$command" | + yq 'any(contains("additional-dnsname=\"*.server.dc1.consul\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + @test "tlsInit/Job: sets additional DNS SANs when provided and global.tls.enabled=true" { cd `chart_dir` local actual=$(helm template \ diff --git a/control-plane/subcommand/acl-init/command.go b/control-plane/subcommand/acl-init/command.go index 9f19708778..24eef5bfcc 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" corev1 "k8s.io/api/core/v1" @@ -50,11 +51,18 @@ type Command struct { bearerTokenFile string // Location of the bearer token. Default is defaultBearerTokenFile. flagComponentName string // Name of the component to be used as metadata to ACL Login. + // Flags to configure Consul connection + flagServerAddresses []string + flagServerPort uint + flagConsulCACert string + flagUseHTTPS bool + k8sClient kubernetes.Interface - once sync.Once - help string - logger hclog.Logger + once sync.Once + help string + logger hclog.Logger + providers map[string]discover.Provider ctx context.Context consulClient *api.Client @@ -78,6 +86,14 @@ func (c *Command) init() { c.flags.StringVar(&c.flagACLAuthMethod, "acl-auth-method", "", "Name of the auth method to login with.") c.flags.StringVar(&c.flagComponentName, "component-name", "", "Name of the component to pass to ACL Login as metadata.") + c.flags.Var((*flags.AppendSliceValue)(&c.flagServerAddresses), "server-address", + "The IP, DNS name or the cloud auto-join string of the Consul server(s). If providing IPs or DNS names, may be specified multiple times. "+ + "At least one value is required.") + c.flags.UintVar(&c.flagServerPort, "server-port", 8500, "The HTTP or HTTPS port of the Consul server. Defaults to 8500.") + c.flags.StringVar(&c.flagConsulCACert, "consul-ca-cert", "", + "Path to the PEM-encoded CA certificate of the Consul cluster.") + c.flags.BoolVar(&c.flagUseHTTPS, "use-https", false, + "Toggle for using HTTPS for all API calls to Consul.") c.flags.StringVar(&c.flagLogLevel, "log-level", "info", "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ "\"debug\", \"info\", \"warn\", and \"error\".") @@ -143,9 +159,28 @@ func (c *Command) Run(args []string) int { } } + var secret string if c.flagACLAuthMethod != "" { cfg := api.DefaultConfig() c.http.MergeOntoConfig(cfg) + + if len(c.flagServerAddresses) > 0 { + serverAddresses, err := common.GetResolvedServerAddresses(c.flagServerAddresses, c.providers, c.logger) + if err != nil { + c.UI.Error(fmt.Sprintf("Unable to discover any Consul addresses from %q: %s", c.flagServerAddresses[0], err)) + return 1 + } + + scheme := "http" + if c.flagUseHTTPS { + scheme = "https" + } + + serverAddr := fmt.Sprintf("%s:%d", serverAddresses[0], c.flagServerPort) + cfg.Address = serverAddr + cfg.Scheme = scheme + } + c.consulClient, err = consul.NewClient(cfg) if err != nil { c.logger.Error("Unable to get client connection", "error", err) @@ -155,28 +190,29 @@ func (c *Command) Run(args []string) int { meta := map[string]string{ "component": c.flagComponentName, } - err := common.ConsulLogin(c.consulClient, cfg, c.flagACLAuthMethod, c.flagPrimaryDatacenter, "", c.bearerTokenFile, "", c.flagTokenSinkFile, meta, c.logger) + secret, err = common.ConsulLogin(c.consulClient, cfg, c.flagACLAuthMethod, c.flagPrimaryDatacenter, "", c.bearerTokenFile, "", c.flagTokenSinkFile, meta, c.logger) if err != nil { c.logger.Error("Consul login failed", "error", err) return 1 } - c.logger.Info("Consul login succeeded") - return 0 - } - // Check if the client secret exists yet - // If not, wait until it does - var secret string - for { - var err error - secret, err = c.getSecret(c.flagSecretName) - if err != nil { - c.logger.Error("Error getting Kubernetes secret", "error", err) - } - if err == nil { - c.logger.Info("Successfully read Kubernetes secret") - break + c.logger.Info("Successfully read ACL token from the server") + } else { + // Use k8s secret to obtain token + + // Check if the client secret exists yet + // If not, wait until it does + for { + var err error + secret, err = c.getSecret(c.flagSecretName) + if err != nil { + c.logger.Error("Error getting Kubernetes secret: ", "error", err) + } + if err == nil { + c.logger.Info("Successfully read Kubernetes secret") + break + } + time.Sleep(1 * time.Second) } - time.Sleep(1 * time.Second) } if c.flagInitType == "client" { diff --git a/control-plane/subcommand/acl-init/command_test.go b/control-plane/subcommand/acl-init/command_test.go index e0804b64c8..2f392d8155 100644 --- a/control-plane/subcommand/acl-init/command_test.go +++ b/control-plane/subcommand/acl-init/command_test.go @@ -1,12 +1,15 @@ package aclinit import ( + "bytes" "context" "fmt" "io/ioutil" "os" "path/filepath" + "strings" "testing" + "text/template" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" @@ -210,3 +213,137 @@ func TestRun_PerformsConsulLogin(t *testing.T) { require.NoError(t, err) require.Equal(t, "token created via login: {\"component\":\"foo\"}", tok.Description) } + +// TestRun_WithAclAuthMethodDefinedWritesConfigJsonWithTokenMatchingSinkFile +// executes the consul login path and validates the token is written to +// acl-config.json and matches the token written to sink file. +func TestRun_WithAclAuthMethodDefined_WritesConfigJson_WithTokenMatchingSinkFile(t *testing.T) { + tokenFile := common.WriteTempFile(t, "") + bearerFile := common.WriteTempFile(t, test.ServiceAccountJWTToken) + tmpDir, err := ioutil.TempDir("", "") + require.NoError(t, err) + t.Cleanup(func() { + os.Remove(tokenFile) + os.RemoveAll(tmpDir) + }) + + k8s := fake.NewSimpleClientset() + + // Start Consul server with ACLs enabled and default deny policy. + masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" + + server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + c.ACL.Enabled = true + c.ACL.DefaultPolicy = "deny" + c.ACL.Tokens.InitialManagement = masterToken + }) + require.NoError(t, err) + defer server.Stop() + server.WaitForLeader(t) + cfg := &api.Config{ + Scheme: "http", + Address: server.HTTPAddr, + Token: masterToken, + } + consulClient, err := api.NewClient(cfg) + require.NoError(t, err) + + // Set up the Component Auth Method, this pre-loads Consul with bindingrule, + // roles and an acl:write policy so we can issue an ACL.Login(). + test.SetupK8sComponentAuthMethod(t, consulClient, "test-sa", "default") + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: k8s, + bearerTokenFile: bearerFile, + } + + code := cmd.Run([]string{ + "-token-sink-file", tokenFile, + "-acl-auth-method", componentAuthMethod, + "-component-name", "foo", + "-http-addr", fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Address), + "-init-type", "client", + "-acl-dir", tmpDir, + }) + require.Equal(t, 0, code, ui.ErrorWriter.String()) + // Validate the ACL Config file got written. + aclConfigBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/acl-config.json", tmpDir)) + require.NoError(t, err) + // Validate the Token Sink File got written. + sinkFileToken, err := ioutil.ReadFile(tokenFile) + require.NoError(t, err) + // Validate the Token Sink File Matches the ACL Cconfig Token by injecting + // the token secret into the template used by the ACL config file. + var buf bytes.Buffer + tpl := template.Must(template.New("root").Parse(strings.TrimSpace(clientACLConfigTpl))) + err = tpl.Execute(&buf, string(sinkFileToken)) + require.NoError(t, err) + expectedAclConfig := buf.String() + + require.Equal(t, expectedAclConfig, string(aclConfigBytes)) +} + +// TestRun_WithAclAuthMethodDefinedWritesConfigJsonWithTokenMatchingSinkFile +// executes the k8s secret path and validates the token is written to +// acl-config.json and matches the token written to sink file. +func TestRun_WithoutAclAuthMethodDefined_WritesConfigJsonWithTokenMatchingSinkFile(t *testing.T) { + t.Parallel() + require := require.New(t) + tmpDir, err := ioutil.TempDir("", "") + require.NoError(err) + + t.Cleanup(func() { + os.RemoveAll(tmpDir) + }) + + // Set up k8s with the secret. + token := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + k8sNS := "default" + secretName := "secret-name" + k8s := fake.NewSimpleClientset() + _, err = k8s.CoreV1().Secrets(k8sNS).Create( + context.Background(), + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + "token": []byte(token), + }, + }, + metav1.CreateOptions{}) + + require.NoError(err) + + sinkFile := filepath.Join(tmpDir, "acl-token") + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: k8s, + } + code := cmd.Run([]string{ + "-token-sink-file", sinkFile, + "-secret-name", secretName, + "-init-type", "client", + "-acl-dir", tmpDir, + }) + // Validate the ACL Config file got written. + aclConfigBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/acl-config.json", tmpDir)) + require.NoError(err) + // Validate the Token Sink File got written. + require.Equal(0, code, ui.ErrorWriter.String()) + sinkFileToken, err := ioutil.ReadFile(sinkFile) + require.NoError(err) + // Validate the Token Sink File Matches the ACL Cconfig Token by injecting + // the token secret into the template used by the ACL config file. + var buf bytes.Buffer + tpl := template.Must(template.New("root").Parse(strings.TrimSpace(clientACLConfigTpl))) + err = tpl.Execute(&buf, string(sinkFileToken)) + require.NoError(err) + expectedAclConfig := buf.String() + + require.Equal(expectedAclConfig, string(aclConfigBytes)) +} diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index dabbe9f4d9..eb9c561c16 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -88,15 +88,15 @@ func ValidateUnprivilegedPort(flagName, flagValue string) error { // ConsulLogin issues an ACL().Login to Consul and writes out the token to tokenSinkFile. // The logic of this is taken from the `consul login` command. -func ConsulLogin(client *api.Client, cfg *api.Config, authMethodName, datacenter, namespace, bearerTokenFile, serviceAccountName, tokenSinkFile string, meta map[string]string, log hclog.Logger) error { +func ConsulLogin(client *api.Client, cfg *api.Config, authMethodName, datacenter, namespace, bearerTokenFile, serviceAccountName, tokenSinkFile string, meta map[string]string, log hclog.Logger) (string, error) { // Read the bearerTokenFile. data, err := ioutil.ReadFile(bearerTokenFile) if err != nil { - return fmt.Errorf("unable to read bearerTokenFile: %v, err: %v", bearerTokenFile, err) + return "", fmt.Errorf("unable to read bearerTokenFile: %v, err: %v", bearerTokenFile, err) } bearerToken := strings.TrimSpace(string(data)) if bearerToken == "" { - return fmt.Errorf("no bearer token found in %s", bearerTokenFile) + return "", fmt.Errorf("no bearer token found in %s", bearerTokenFile) } err = backoff.Retry(func() error { // Do the login. @@ -113,9 +113,13 @@ func ConsulLogin(client *api.Client, cfg *api.Config, authMethodName, datacenter log.Error("unable to login", "error", err) return fmt.Errorf("error logging in: %s", err) } - // Write out the resultant token file. - if err := WriteFileWithPerms(tokenSinkFile, tok.SecretID, 0444); err != nil { - return fmt.Errorf("error writing token to file sink: %v", err) + if tokenSinkFile != "" { + // Write out the resultant token file. + // Must be 0644 because this is written by the consul-k8s user but needs + // to be readable by the consul user + if err := WriteFileWithPerms(tokenSinkFile, tok.SecretID, 0644); err != nil { + return fmt.Errorf("error writing token to file sink: %v", err) + } } return err }, backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), numLoginRetries)) @@ -127,14 +131,14 @@ func ConsulLogin(client *api.Client, cfg *api.Config, authMethodName, datacenter " or the consul.hashicorp.com/connect-service annotation.") } log.Error("Hit maximum retries for consul login", "error", err) - return err + return "", err } // Now update the client so that it will read the ACL token we just fetched. cfg.TokenFile = tokenSinkFile client, err = consul.NewClient(cfg) if err != nil { log.Error("Unable to update client connection", "error", err) - return err + return "", err } log.Info("Consul login complete") @@ -167,8 +171,9 @@ func ConsulLogin(client *api.Client, cfg *api.Config, authMethodName, datacenter log.Info("Checking that the ACL token exists when reading it in the stale consistency mode") // Use raft timeout and polling interval to determine the number of retries. numTokenReadRetries := uint64(raftReplicationTimeout.Milliseconds() / tokenReadPollingInterval.Milliseconds()) + var aclLoginToken *api.ACLToken err = backoff.Retry(func() error { - _, _, err := client.ACL().TokenReadSelf(&api.QueryOptions{AllowStale: true}) + aclLoginToken, _, err = client.ACL().TokenReadSelf(&api.QueryOptions{AllowStale: true}) if err != nil { log.Error("Unable to read ACL token; retrying", "err", err) } @@ -177,10 +182,10 @@ func ConsulLogin(client *api.Client, cfg *api.Config, authMethodName, datacenter if err != nil { log.Error("Unable to read ACL token from a Consul server; "+ "please check that your server cluster is healthy", "err", err) - return err + return "", err } log.Info("Successfully read ACL token from the server") - return nil + return aclLoginToken.SecretID, nil } // WriteFileWithPerms will write payload as the contents of the outputFile and set permissions after writing the contents. This function is necessary since using ioutil.WriteFile() alone will create the new file with the requested permissions prior to actually writing the file, so you can't set read-only permissions. diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 12cfa092b5..102aa2a2e9 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -64,7 +64,7 @@ func TestConsulLogin(t *testing.T) { log, err := Logger("INFO", false) require.NoError(err) client, cfg := startMockServer(t, &counter) - err = ConsulLogin(client, cfg, testAuthMethod, "dc1", "", bearerTokenFile, "", tokenFile, testPodMeta, log) + _, err = ConsulLogin(client, cfg, testAuthMethod, "dc1", "", bearerTokenFile, "", tokenFile, testPodMeta, log) require.NoError(err) require.Equal(counter, 1) // Validate that the token file was written to disk. @@ -78,7 +78,7 @@ func TestConsulLogin_EmptyBearerTokenFile(t *testing.T) { require := require.New(t) bearerTokenFile := WriteTempFile(t, "") - err := ConsulLogin(nil, nil, testAuthMethod, "", "", bearerTokenFile, "", "", testPodMeta, hclog.NewNullLogger()) + _, err := ConsulLogin(nil, nil, testAuthMethod, "", "", bearerTokenFile, "", "", testPodMeta, hclog.NewNullLogger()) require.EqualError(err, fmt.Sprintf("no bearer token found in %s", bearerTokenFile)) } @@ -86,7 +86,7 @@ func TestConsulLogin_BearerTokenFileDoesNotExist(t *testing.T) { t.Parallel() require := require.New(t) randFileName := fmt.Sprintf("/foo/%d/%d", rand.Int(), rand.Int()) - err := ConsulLogin(nil, nil, testAuthMethod, "", "", randFileName, "", "", testPodMeta, hclog.NewNullLogger()) + _, err := ConsulLogin(nil, nil, testAuthMethod, "", "", randFileName, "", "", testPodMeta, hclog.NewNullLogger()) require.Error(err) require.Contains(err.Error(), "unable to read bearerTokenFile") } @@ -101,7 +101,7 @@ func TestConsulLogin_TokenFileUnwritable(t *testing.T) { log, err := Logger("INFO", false) require.NoError(err) randFileName := fmt.Sprintf("/foo/%d/%d", rand.Int(), rand.Int()) - err = ConsulLogin(client, cfg, testAuthMethod, "", "", bearerTokenFile, "", randFileName, testPodMeta, log) + _, err = ConsulLogin(client, cfg, testAuthMethod, "", "", bearerTokenFile, "", randFileName, testPodMeta, log) require.Error(err) require.Contains(err.Error(), "error writing token to file sink") } diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 9ae479eea2..4a6e793987 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -123,7 +123,7 @@ func (c *Command) Run(args []string) int { if c.flagACLAuthMethod != "" { // loginMeta is the default metadata that we pass to the consul login API. loginMeta := map[string]string{"pod": fmt.Sprintf("%s/%s", c.flagPodNamespace, c.flagPodName)} - err = common.ConsulLogin(consulClient, cfg, c.flagACLAuthMethod, "", c.flagAuthMethodNamespace, c.flagBearerTokenFile, c.flagServiceAccountName, c.flagACLTokenSink, loginMeta, c.logger) + _, err = common.ConsulLogin(consulClient, cfg, c.flagACLAuthMethod, "", c.flagAuthMethodNamespace, c.flagBearerTokenFile, c.flagServiceAccountName, c.flagACLTokenSink, loginMeta, c.logger) if err != nil { c.logger.Error("unable to complete login", "error", err) return 1 diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 8846d42efc..6ef0471fe2 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -28,11 +28,9 @@ type Command struct { flagPartitionName string // Flags to configure Consul connection - flagServerAddresses []string - flagServerPort uint - flagConsulCACert string - flagConsulTLSServerName string - flagUseHTTPS bool + flagServerAddresses []string + flagServerPort uint + flagUseHTTPS bool flagLogLevel string flagLogJSON bool @@ -60,10 +58,6 @@ func (c *Command) init() { "The IP, DNS name or the cloud auto-join string of the Consul server(s). If providing IPs or DNS names, may be specified multiple times. "+ "At least one value is required.") c.flags.UintVar(&c.flagServerPort, "server-port", 8500, "The HTTP or HTTPS port of the Consul server. Defaults to 8500.") - c.flags.StringVar(&c.flagConsulCACert, "consul-ca-cert", "", - "Path to the PEM-encoded CA certificate of the Consul cluster.") - c.flags.StringVar(&c.flagConsulTLSServerName, "consul-tls-server-name", "", - "The server name to set as the SNI header when sending HTTPS requests to Consul.") c.flags.BoolVar(&c.flagUseHTTPS, "use-https", false, "Toggle for using HTTPS for all API calls to Consul.") c.flags.DurationVar(&c.flagTimeout, "timeout", 10*time.Minute, @@ -134,14 +128,11 @@ func (c *Command) Run(args []string) int { } // For all of the next operations we'll need a Consul client. serverAddr := fmt.Sprintf("%s:%d", serverAddresses[0], c.flagServerPort) - consulClient, err := consul.NewClient(&api.Config{ - Address: serverAddr, - Scheme: scheme, - TLSConfig: api.TLSConfig{ - Address: c.flagConsulTLSServerName, - CAFile: c.flagConsulCACert, - }, - }) + cfg := api.DefaultConfig() + cfg.Address = serverAddr + cfg.Scheme = scheme + c.http.MergeOntoConfig(cfg) + consulClient, err := consul.NewClient(cfg) if err != nil { c.UI.Error(fmt.Sprintf("Error creating Consul client for addr %q: %s", serverAddr, err)) return 1 diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 400b69a9b8..c487790ddd 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -39,7 +39,7 @@ type Command struct { flagSetServerTokens bool - flagCreateClientToken bool + flagClient bool flagSyncCatalog bool flagSyncConsulNodeName string @@ -122,7 +122,7 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagAllowDNS, "allow-dns", false, "Toggle for updating the anonymous token to allow DNS queries to work") - c.flags.BoolVar(&c.flagCreateClientToken, "create-client-token", true, + c.flags.BoolVar(&c.flagClient, "client", true, "Toggle for creating a client agent token. Default is true.") c.flags.BoolVar(&c.flagSyncCatalog, "sync-catalog", false, @@ -444,14 +444,15 @@ func (c *Command) Run(args []string) int { } } - if c.flagCreateClientToken { + if c.flagClient { agentRules, err := c.agentRules() if err != nil { c.log.Error("Error templating client agent rules", "err", err) return 1 } - err = c.createLocalACL("client", agentRules, consulDC, primary, consulClient) + serviceAccountName := c.withPrefix("client") + err = c.createACLPolicyRoleAndBindingRule("client", agentRules, consulDC, primaryDC, false, primary, localComponentAuthMethodName, serviceAccountName, consulClient) if err != nil { c.log.Error(err.Error()) return 1 diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index c49a423bf5..a0a0025fc1 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -285,7 +285,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "-server-port=" + strings.Split(testAgent.HTTPAddr, ":")[1], "-resource-prefix=" + resourcePrefix, "-k8s-namespace", k8sNamespaceFlag, - "-create-client-token", + "-client", "-allow-dns", "-mesh-gateway", "-sync-catalog", @@ -325,7 +325,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { // Check that the expected policies were created. firstRunExpectedPolicies := []string{ "anonymous-token-policy", - "client-token", + "client-policy", "sync-catalog-policy", "mesh-gateway-policy", "snapshot-agent-policy", @@ -376,7 +376,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { // Check that the policies have all been updated. secondRunExpectedPolicies := []string{ "anonymous-token-policy", - "client-token", + "client-policy", "sync-catalog-policy", "connect-inject-policy", "mesh-gateway-policy", @@ -669,13 +669,6 @@ func TestRun_TokensWithNamespacesEnabled(t *testing.T) { SecretNames []string LocalToken bool }{ - "client token": { - TokenFlags: []string{"-create-client-token"}, - PolicyNames: []string{"client-token"}, - PolicyDCs: []string{"dc1"}, - SecretNames: []string{resourcePrefix + "-client-acl-token"}, - LocalToken: true, - }, "enterprise-license token": { TokenFlags: []string{"-create-enterprise-license-token"}, PolicyNames: []string{"enterprise-license-token"}, diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index f10fe2de79..9ac9a6ab8a 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -162,14 +162,6 @@ func TestRun_TokensPrimaryDC(t *testing.T) { SecretNames []string LocalToken bool }{ - { - TestName: "Client token", - TokenFlags: []string{"-create-client-token"}, - PolicyNames: []string{"client-token"}, - PolicyDCs: []string{"dc1"}, - SecretNames: []string{resourcePrefix + "-client-acl-token"}, - LocalToken: true, - }, { TestName: "Enterprise license token", TokenFlags: []string{"-create-enterprise-license-token"}, @@ -356,14 +348,6 @@ func TestRun_TokensReplicatedDC(t *testing.T) { SecretNames []string LocalToken bool }{ - { - TestName: "Client token", - TokenFlags: []string{"-create-client-token"}, - PolicyNames: []string{"client-token-dc2"}, - PolicyDCs: []string{"dc2"}, - SecretNames: []string{resourcePrefix + "-client-acl-token"}, - LocalToken: true, - }, { TestName: "Enterprise license token", TokenFlags: []string{"-create-enterprise-license-token"}, @@ -464,12 +448,6 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { PolicyNames []string SecretNames []string }{ - { - TestName: "Client token", - TokenFlags: []string{"-create-client-token"}, - PolicyNames: []string{"client-token"}, - SecretNames: []string{resourcePrefix + "-client-acl-token"}, - }, { TestName: "Enterprise license token", TokenFlags: []string{"-create-enterprise-license-token"}, @@ -1194,8 +1172,27 @@ func TestRun_NoLeader(t *testing.T) { fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) case "/v1/acl/tokens": fmt.Fprintln(w, `[]`) + case "/v1/acl/token": + fmt.Fprintln(w, `{}`) + case "/v1/agent/token/agent": + fmt.Fprintln(w, `{}`) + case "/v1/acl/policy": + fmt.Fprintln(w, `{}`) + case "/v1/acl/auth-method": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role/name/": + w.WriteHeader(404) + case "/v1/acl/role/name/release-name-consul-client-acl-role": + w.WriteHeader(404) + case "/v1/acl/binding-rules": + fmt.Fprintln(w, `[]`) + case "/v1/acl/binding-rule": + fmt.Fprintln(w, `{}`) default: - fmt.Fprintln(w, "{}") + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) } })) defer consulServer.Close() @@ -1276,9 +1273,17 @@ func TestRun_NoLeader(t *testing.T) { "PUT", "/v1/acl/policy", }, + { + "GET", + "/v1/acl/role/name/release-name-consul-client-acl-role", + }, { "PUT", - "/v1/acl/token", + "/v1/acl/role", + }, + { + "PUT", + "/v1/acl/binding-rule", }, }, consulAPICalls) } @@ -1380,8 +1385,8 @@ func TestConsulDatacenterList(t *testing.T) { } } -// Test that if creating client tokens fails at first, we retry. -func TestRun_ClientTokensRetry(t *testing.T) { +// Test that if creating client policy and binding rule fails at first, we retry. +func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { t.Parallel() k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) @@ -1416,8 +1421,27 @@ func TestRun_ClientTokensRetry(t *testing.T) { fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) case "/v1/acl/tokens": fmt.Fprintln(w, `[]`) + case "/v1/acl/token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/bootstrap": + fmt.Fprintln(w, `{}`) + case "/v1/agent/token/agent": + fmt.Fprintln(w, `{}`) + case "/v1/acl/auth-method": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role/name/": + w.WriteHeader(404) + case "/v1/acl/role/name/release-name-consul-client-acl-role": + w.WriteHeader(404) + case "/v1/acl/binding-rules": + fmt.Fprintln(w, `[]`) + case "/v1/acl/binding-rule": + fmt.Fprintln(w, `{}`) default: - fmt.Fprintln(w, "{}") + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) } })) defer consulServer.Close() @@ -1479,9 +1503,17 @@ func TestRun_ClientTokensRetry(t *testing.T) { "PUT", "/v1/acl/policy", }, + { + "GET", + "/v1/acl/role/name/release-name-consul-client-acl-role", + }, { "PUT", - "/v1/acl/token", + "/v1/acl/role", + }, + { + "PUT", + "/v1/acl/binding-rule", }, }, consulAPICalls) } @@ -1517,9 +1549,25 @@ func TestRun_AlreadyBootstrapped(t *testing.T) { fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) case "/v1/acl/tokens": fmt.Fprintln(w, `[]`) + case "/v1/acl/token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/policy": + fmt.Fprintln(w, `{}`) + case "/v1/agent/token/acl_agent_token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/auth-method": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role/name/release-name-consul-client-acl-role": + w.WriteHeader(404) + case "/v1/acl/role": + fmt.Fprintln(w, `{}`) + case "/v1/acl/binding-rules": + fmt.Fprintln(w, `[]`) + case "/v1/acl/binding-rule": + fmt.Fprintln(w, `{}`) default: - // Send an empty JSON response with code 200 to all calls. - fmt.Fprintln(w, "{}") + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) } })) defer consulServer.Close() @@ -1602,6 +1650,10 @@ func TestRun_AlreadyBootstrapped(t *testing.T) { "PUT", "/v1/agent/token/agent", }, + { + "PUT", + "/v1/agent/token/acl_agent_token", + }, { "GET", "/v1/agent/self", @@ -1614,9 +1666,17 @@ func TestRun_AlreadyBootstrapped(t *testing.T) { "PUT", "/v1/acl/policy", }, + { + "GET", + "/v1/acl/role/name/release-name-consul-client-acl-role", + }, { "PUT", - "/v1/acl/token", + "/v1/acl/role", + }, + { + "PUT", + "/v1/acl/binding-rule", }, }, consulAPICalls) }) @@ -1773,7 +1833,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { "-server-port=" + serverURL.Port(), "-bootstrap-token-file=" + tokenFile, "-set-server-tokens=false", - "-create-client-token=false", // disable client token, so there are fewer calls + "-client=false", // disable client token, so there are fewer calls }) require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) @@ -1885,7 +1945,7 @@ func TestRun_ACLReplicationTokenValid(t *testing.T) { "-resource-prefix=" + resourcePrefix, "-acl-replication-token-file", tokenFile, "-auth-method-host=" + "https://my-kube.com", - "-create-client-token", + "-client", "-mesh-gateway", } responseCode := secondaryCmd.Run(secondaryCmdArgs) @@ -1901,7 +1961,7 @@ func TestRun_ACLReplicationTokenValid(t *testing.T) { // Test that the client policy was created. retry.Run(t, func(r *retry.R) { - p := policyExists(r, "client-token-dc2", secondaryConsulClient) + p := policyExists(r, "client-policy-dc2", secondaryConsulClient) require.Equal(r, []string{"dc2"}, p.Datacenters) }) @@ -1909,7 +1969,7 @@ func TestRun_ACLReplicationTokenValid(t *testing.T) { // so replication has to have worked for it to exist. retry.Run(t, func(r *retry.R) { p := policyExists(r, "mesh-gateway-policy-dc2", secondaryConsulClient) - require.Len(r, p.Datacenters, 2) + require.Len(r, p.Datacenters, 0) }) } @@ -2114,6 +2174,12 @@ func TestRun_PoliciesAndBindingRulesForACLLogin_PrimaryDatacenter(t *testing.T) PolicyNames: []string{"mesh-gateway-policy"}, Roles: []string{resourcePrefix + "-mesh-gateway-acl-role"}, }, + { + TestName: "Client", + TokenFlags: []string{"-client"}, + PolicyNames: []string{"client-policy"}, + Roles: []string{resourcePrefix + "-client-acl-role"}, + }, } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { @@ -2180,6 +2246,7 @@ func TestRun_PoliciesAndBindingRulesForACLLogin_PrimaryDatacenter(t *testing.T) } require.True(t, found) } + }) } } @@ -2246,6 +2313,13 @@ func TestRun_PoliciesAndBindingRulesACLLogin_SecondaryDatacenter(t *testing.T) { Roles: []string{resourcePrefix + "-mesh-gateway-acl-role-" + secondaryDatacenter}, GlobalAuthMethod: true, }, + { + TestName: "Client", + TokenFlags: []string{"-client"}, + PolicyNames: []string{"client-policy-" + secondaryDatacenter}, + Roles: []string{resourcePrefix + "-client-acl-role-" + secondaryDatacenter}, + GlobalAuthMethod: false, + }, } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { @@ -2373,6 +2447,12 @@ func TestRun_ValidateLoginToken_PrimaryDatacenter(t *testing.T) { Roles: []string{resourcePrefix + "-mesh-gateway-acl-role"}, GlobalToken: false, }, + { + ComponentName: "client", + TokenFlags: []string{"-client"}, + Roles: []string{resourcePrefix + "-client-acl-role"}, + GlobalToken: false, + }, } for _, c := range cases { t.Run(c.ComponentName, func(t *testing.T) { @@ -2489,6 +2569,13 @@ func TestRun_ValidateLoginToken_SecondaryDatacenter(t *testing.T) { GlobalAuthMethod: true, GlobalToken: true, }, + { + ComponentName: "client", + TokenFlags: []string{"-client"}, + Roles: []string{resourcePrefix + "-client-acl-role-dc2"}, + GlobalAuthMethod: false, + GlobalToken: false, + }, } for _, c := range cases { t.Run(c.ComponentName, func(t *testing.T) { diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index 9ee02786a9..a1934a8b01 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -22,11 +22,8 @@ func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, pr policyName += fmt.Sprintf("-%s", dc) } var datacenters []string - if global { + if !global && dc != "" { datacenters = append(datacenters, dc) - if !primary { - datacenters = append(datacenters, primaryDC) - } } policyTmpl := api.ACLPolicy{ Name: policyName,