diff --git a/.changelog/1920.txt b/.changelog/1920.txt new file mode 100644 index 0000000000..4b1f151fe4 --- /dev/null +++ b/.changelog/1920.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: When the `global.acls.bootstrapToken` field is set and the content of the secret is empty, the bootstrap ACL token is written to that secret after bootstrapping ACLs. This applies to both the Vault and Consul secrets backends. +``` diff --git a/acceptance/framework/vault/helpers.go b/acceptance/framework/vault/helpers.go index 4726e246ae..2280aee8b2 100644 --- a/acceptance/framework/vault/helpers.go +++ b/acceptance/framework/vault/helpers.go @@ -167,6 +167,20 @@ func (config *KV2Secret) SaveSecretAndAddReadPolicy(t *testing.T, vaultClient *v path "%s" { capabilities = ["read"] }`, config.Path) + config.saveSecretAndAddPolicy(t, vaultClient, policy) +} + +// SaveSecretAndAddUpdatePolicy will create an update policy for the PolicyName +// on the KV2Secret and then will save the secret in the KV2 store. +func (config *KV2Secret) SaveSecretAndAddUpdatePolicy(t *testing.T, vaultClient *vapi.Client) { + policy := fmt.Sprintf(` + path "%s" { + capabilities = ["read", "update"] + }`, config.Path) + config.saveSecretAndAddPolicy(t, vaultClient, policy) +} + +func (config *KV2Secret) saveSecretAndAddPolicy(t *testing.T, vaultClient *vapi.Client, policy string) { // Create the Vault Policy for the secret. logger.Log(t, "Creating policy") err := vaultClient.Sys().PutPolicy(config.PolicyName, policy) diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index cf0c926b22..4d43d8bb5b 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" "github.com/hashicorp/consul-k8s/acceptance/framework/vault" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" @@ -31,6 +32,29 @@ const ( // TestVault installs Vault, bootstraps it with secrets, policies, and Kube Auth Method. // It then configures Consul to use vault as the backend and checks that it works. func TestVault(t *testing.T) { + cases := map[string]struct { + autoBootstrap bool + }{ + "manual ACL bootstrap": {}, + "automatic ACL bootstrap": { + autoBootstrap: true, + }, + } + for name, c := range cases { + c := c + t.Run(name, func(t *testing.T) { + testVault(t, c.autoBootstrap) + }) + } +} + +// testVault is the implementation for TestVault: +// +// - testAutoBootstrap = false. Test when ACL bootstrapping has already occurred. +// The test pre-populates a Vault secret with the bootstrap token. +// - testAutoBootstrap = true. Test that server-acl-init automatically ACL bootstraps +// consul and writes the bootstrap token to Vault. +func testVault(t *testing.T, testAutoBootstrap bool) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) kubectlOptions := ctx.KubectlOptions(t) @@ -123,16 +147,22 @@ func TestVault(t *testing.T) { licenseSecret.SaveSecretAndAddReadPolicy(t, vaultClient) } - // Bootstrap Token - bootstrapToken, err := uuid.GenerateUUID() - require.NoError(t, err) bootstrapTokenSecret := &vault.KV2Secret{ Path: "consul/data/secret/bootstrap", Key: "token", - Value: bootstrapToken, + Value: "", PolicyName: "bootstrap", } - bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient) + if testAutoBootstrap { + bootstrapTokenSecret.SaveSecretAndAddUpdatePolicy(t, vaultClient) + } else { + id, err := uuid.GenerateUUID() + require.NoError(t, err) + bootstrapTokenSecret.Value = id + bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient) + } + + bootstrapToken := bootstrapTokenSecret.Value // ------------------------- // Additional Auth Roles @@ -265,6 +295,26 @@ func TestVault(t *testing.T) { logger.Logf(t, "Wait %d seconds for certificates to rotate....", expirationInSeconds) time.Sleep(time.Duration(expirationInSeconds) * time.Second) + if testAutoBootstrap { + logger.Logf(t, "Validating the ACL bootstrap token was stored in Vault.") + timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 1 * time.Second} + retry.RunWith(timer, t, func(r *retry.R) { + secret, err := vaultClient.Logical().Read("consul/data/secret/bootstrap") + require.NoError(r, err) + + data, ok := secret.Data["data"].(map[string]interface{}) + require.True(r, ok) + require.NotNil(r, data) + + tok, ok := data["token"].(string) + require.True(r, ok) + require.NotEmpty(r, tok) + + // Set bootstrapToken for subsequent validations. + bootstrapToken = tok + }) + } + // Validate that the gossip encryption key is set correctly. logger.Log(t, "Validating the gossip key has been set correctly.") consulCluster.ACLToken = bootstrapToken diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 440ab8bee0..e62db41ec2 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -47,14 +47,23 @@ spec: annotations: "consul.hashicorp.com/connect-inject": "false" {{- if .Values.global.secretsBackend.vault.enabled }} - "vault.hashicorp.com/agent-pre-populate-only": "true" + + {{- /* Run the Vault agent as both an init container and sidecar. + The Vault agent sidecar is needed when server-acl-init bootstraps ACLs + and writes the bootstrap token back to Vault. + * agent-pre-populate: true - Run the Vault agent init container. + * agent-pre-populate-only: false - Also, run the Vault agent sidecar. + * agent-cache-enable: true - Enable the Agent cache listener. + * agent-cache-listener-port: 8200 - (optional) Listen on 127.0.0.1:8200. + * agent-enable-quit: true - Enable a "quit" endpoint. server-acl-init + tells the Vault agent to stop (without this the Job will not complete). + */}} + "vault.hashicorp.com/agent-pre-populate": "true" + "vault.hashicorp.com/agent-pre-populate-only": "false" + "vault.hashicorp.com/agent-cache-enable": "true" + "vault.hashicorp.com/agent-cache-listener-port": "8200" + "vault.hashicorp.com/agent-enable-quit": "true" "vault.hashicorp.com/agent-inject": "true" - {{- if .Values.global.acls.bootstrapToken.secretName }} - {{- with .Values.global.acls.bootstrapToken }} - "vault.hashicorp.com/agent-inject-secret-bootstrap-token": "{{ .secretName }}" - "vault.hashicorp.com/agent-inject-template-bootstrap-token": {{ template "consul.vaultSecretTemplate" . }} - {{- end }} - {{- end }} {{- if .Values.global.acls.partitionToken.secretName }} {{- with .Values.global.acls.partitionToken }} "vault.hashicorp.com/agent-inject-secret-partition-token": "{{ .secretName }}" @@ -101,14 +110,7 @@ spec: path: tls.crt {{- end }} {{- end }} - {{- if (and .Values.global.acls.bootstrapToken.secretName (not .Values.global.secretsBackend.vault.enabled)) }} - - name: bootstrap-token - secret: - secretName: {{ .Values.global.acls.bootstrapToken.secretName }} - items: - - key: {{ .Values.global.acls.bootstrapToken.secretKey }} - path: bootstrap-token - {{- else if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} + {{- if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} - name: acl-replication-token secret: secretName: {{ .Values.global.acls.replicationToken.secretName }} @@ -129,6 +131,13 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + # Extract the Vault namespace from the Vault agent annotations. + {{- if .Values.global.secretsBackend.vault.enabled }} + {{- if .Values.global.secretsBackend.vault.agentAnnotations }} + - name: VAULT_NAMESPACE + value: {{ get (tpl .Values.global.secretsBackend.vault.agentAnnotations . | fromYaml) "vault.hashicorp.com/namespace" }} + {{- end }} + {{- end }} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} {{- if (or .Values.global.tls.enabled .Values.global.acls.replicationToken.secretName .Values.global.acls.bootstrapToken.secretName) }} volumeMounts: @@ -139,11 +148,7 @@ spec: readOnly: true {{- end }} {{- end }} - {{- if (and .Values.global.acls.bootstrapToken.secretName (not .Values.global.secretsBackend.vault.enabled)) }} - - name: bootstrap-token - mountPath: /consul/acl/tokens - readOnly: true - {{- else if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} + {{- if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} - name: acl-replication-token mountPath: /consul/acl/tokens readOnly: true @@ -161,13 +166,15 @@ spec: -resource-prefix=${CONSUL_FULLNAME} \ -k8s-namespace={{ .Release.Namespace }} \ -set-server-tokens={{ $serverEnabled }} \ - - {{- if .Values.global.acls.bootstrapToken.secretName }} {{- if .Values.global.secretsBackend.vault.enabled }} - -bootstrap-token-file=/vault/secrets/bootstrap-token \ + -secrets-backend=vault \ {{- else }} - -bootstrap-token-file=/consul/acl/tokens/bootstrap-token \ + -secrets-backend=kubernetes \ {{- end }} + + {{- if .Values.global.acls.bootstrapToken.secretName }} + -bootstrap-token-secret-name={{ .Values.global.acls.bootstrapToken.secretName }} \ + -bootstrap-token-secret-key={{ .Values.global.acls.bootstrapToken.secretKey }} \ {{- end }} {{- if .Values.syncCatalog.enabled }} diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 63450aa4c2..81064c95eb 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -634,7 +634,19 @@ load _helpers yq -r '.spec.template' | tee /dev/stderr) # Check annotations + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) + [ "${actual}" = "false" ] + + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-enable"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-listener-port"]' | tee /dev/stderr) + [ "${actual}" = "8200" ] + + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-enable-quit"]' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) @@ -643,15 +655,13 @@ load _helpers local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) [ "${actual}" = "aclrole" ] - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-bootstrap-token"]' | tee /dev/stderr) - [ "${actual}" = "foo" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-secrets-backend=vault"))') + [ "${actual}" = "true" ] - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-bootstrap-token"]' | tee /dev/stderr) - local expected=$'{{- with secret \"foo\" -}}\n{{- .Data.data.bar -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=foo"))') + [ "${actual}" = "true" ] - # Check that the bootstrap token flag is set to the path of the Vault secret. - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-file=/vault/secrets/bootstrap-token"))') + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=bar"))') [ "${actual}" = "true" ] # Check that no (secret) volumes are not attached @@ -682,20 +692,31 @@ load _helpers yq -r '.spec.template' | tee /dev/stderr) # Check annotations - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate"]' | tee /dev/stderr) [ "${actual}" = "true" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) + [ "${actual}" = "false" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-enable"]' | tee /dev/stderr) [ "${actual}" = "true" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-listener-port"]' | tee /dev/stderr) + [ "${actual}" = "8200" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-enable-quit"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) [ "${actual}" = "aclrole" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) [ "${actual}" = "foo" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr) [ "${actual}" = $'{{- with secret \"foo\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' ] # Check that the consul-ca-cert volume is not attached @@ -881,12 +902,14 @@ load _helpers local expected=$'{{- with secret \"/vault/replication\" -}}\n{{- .Data.data.token -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] - local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-inject-secret-bootstrap-token"') - [ "${actual}" = "/vault/bootstrap" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-secrets-backend=vault"))') + [ "${actual}" = "true" ] - local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-inject-template-bootstrap-token"') - local expected=$'{{- with secret \"/vault/bootstrap\" -}}\n{{- .Data.data.token -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=/vault/bootstrap"))') + [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=token"))') + [ "${actual}" = "true" ] # Check that replication token Kubernetes secret volumes and volumeMounts are not attached. local actual=$(echo $object | jq -r '.spec.volumes') @@ -895,12 +918,9 @@ load _helpers local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").volumeMounts') [ "${actual}" = "null" ] - # Check that the replication and bootstrap token flags are set to the path of the Vault secret. + # Replication token file is passed. local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-acl-replication-token-file=/vault/secrets/replication-token"))') [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-file=/vault/secrets/bootstrap-token"))') - [ "${actual}" = "true" ] } #-------------------------------------------------------------------- @@ -953,7 +973,7 @@ load _helpers #-------------------------------------------------------------------- # Vault agent annotations -@test "serverACLInit/Job: no vault agent annotations defined by default" { +@test "serverACLInit/Job: default vault agent annotations" { cd `chart_dir` local actual=$(helm template \ -s templates/server-acl-init-job.yaml \ @@ -967,8 +987,21 @@ load _helpers --set 'global.secretsBackend.vault.consulCARole=carole' \ --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/agent-pre-populate-only") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-inject-secret-bootstrap-token") | del(."vault.hashicorp.com/agent-inject-template-bootstrap-token")' | tee /dev/stderr) - [ "${actual}" = "{}" ] + yq -r .spec.template.metadata.annotations | tee /dev/stderr) + + local expected=$(echo '{ + "consul.hashicorp.com/connect-inject": "false", + "vault.hashicorp.com/agent-inject": "true", + "vault.hashicorp.com/agent-pre-populate": "true", + "vault.hashicorp.com/agent-pre-populate-only": "false", + "vault.hashicorp.com/agent-cache-enable": "true", + "vault.hashicorp.com/agent-cache-listener-port": "8200", + "vault.hashicorp.com/agent-enable-quit": "true", + "vault.hashicorp.com/role": "aclrole" + }' | tee /dev/stderr) + + local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') + [ "$equal" = "true" ] } @test "serverACLInit/Job: vault agent annotations can be set" { @@ -1807,55 +1840,23 @@ load _helpers [[ "$output" =~ "both global.acls.bootstrapToken.secretKey and global.acls.bootstrapToken.secretName must be set if one of them is provided" ]] } -@test "serverACLInit/Job: -bootstrap-token-file is not set by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr) - - # Test the flag is not set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) - [ "${actual}" = "false" ] - - # Test the volume doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "serverACLInit/Job: -bootstrap-token-file is set when acls.bootstrapToken.secretKey and secretName are set" { +@test "serverACLInit/Job: bootstrap token secret is passed when acls.bootstrapToken.secretKey and secretName are set" { cd `chart_dir` local object=$(helm template \ -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ --set 'global.acls.bootstrapToken.secretName=name' \ --set 'global.acls.bootstrapToken.secretKey=key' \ - . | tee /dev/stderr) + . | yq .spec.template | tee /dev/stderr) - # Test the -bootstrap-token-file flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=name"))') [ "${actual}" = "true" ] - # Test the volume exists - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount exists - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=key"))') [ "${actual}" = "true" ] } -@test "serverACLInit/Job: -bootstrap-token-file is preferred when both acls.bootstrapToken and acls.replicationToken are set" { +@test "serverACLInit/Job: bootstrap token secret is passed when both acl.bootstrapToken and acls.replicationToken are set" { cd `chart_dir` local object=$(helm template \ -s templates/server-acl-init-job.yaml \ @@ -1864,21 +1865,12 @@ load _helpers --set 'global.acls.bootstrapToken.secretKey=key' \ --set 'global.acls.replicationToken.secretName=replication' \ --set 'global.acls.replicationToken.secretKey=token' \ - . | tee /dev/stderr) + . | yq .spec.template | tee /dev/stderr) - # Test the -bootstrap-token-file flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=name"))') [ "${actual}" = "true" ] - # Test the volume exists - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount exists - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=key"))') [ "${actual}" = "true" ] } diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index dbc5935211..009e851e1b 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -392,14 +392,20 @@ global: # This requires Consul >= 1.4. manageSystemACLs: false - # A Kubernetes or Vault secret containing the bootstrap token to use for - # creating policies and tokens for all Consul and consul-k8s-control-plane components. - # If set, we will skip ACL bootstrapping of the servers and will only - # initialize ACLs for the Consul clients and consul-k8s-control-plane system components. + # A Kubernetes or Vault secret containing the bootstrap token to use for creating policies and + # tokens for all Consul and consul-k8s-control-plane components. If `secretName` and `secretKey` + # are unset, a default secret name and secret key are used. If the secret is populated, then + # we will skip ACL bootstrapping of the servers and will only initialize ACLs for the Consul + # clients and consul-k8s-control-plane system components. + # If the secret is empty, then we will bootstrap ACLs on the Consul servers, and write the + # bootstrap token to this secret. If ACLs are already bootstrapped on the servers, then the + # secret must contain the bootstrap token. bootstrapToken: # The name of the Kubernetes or Vault secret that holds the bootstrap token. + # If unset, this defaults to `{{ global.name }}-bootstrap-acl-token`. secretName: null # The key within the Kubernetes or Vault secret that holds the bootstrap token. + # If unset, this defaults to `token`. secretKey: null # If true, an ACL token will be created that can be used in secondary diff --git a/control-plane/go.mod b/control-plane/go.mod index 51e4ad39a0..a60079cc53 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -19,14 +19,15 @@ require ( github.com/hashicorp/go-rootcerts v1.0.2 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/serf v0.10.1 + github.com/hashicorp/vault/api v1.8.3 github.com/kr/text v0.2.0 github.com/miekg/dns v1.1.41 github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/mapstructure v1.4.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.7.2 go.uber.org/zap v1.19.0 - golang.org/x/text v0.3.7 + golang.org/x/text v0.3.8 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 gomodules.xyz/jsonpatch/v2 v2.2.0 k8s.io/api v0.22.2 @@ -55,6 +56,7 @@ require ( github.com/aws/aws-sdk-go v1.25.41 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -68,6 +70,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect @@ -75,13 +78,22 @@ require ( github.com/googleapis/gnostic v0.5.5 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/hashicorp/consul/proto-public v0.1.0 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-immutable-radix v1.3.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-plugin v1.4.5 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect + github.com/hashicorp/vault/sdk v0.7.0 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect @@ -90,10 +102,15 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect + github.com/oklog/run v1.0.0 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect + github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect @@ -102,6 +119,7 @@ require ( github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -109,7 +127,7 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible // indirect github.com/vmware/govmomi v0.18.0 // indirect go.opencensus.io v0.23.0 // indirect - go.uber.org/atomic v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect @@ -125,6 +143,7 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.22.2 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 1fbb30b7ff..b4ed16e150 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -117,6 +117,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -197,10 +199,12 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -235,6 +239,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -276,6 +281,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -352,20 +359,22 @@ github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3us github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f h1:7WFMVeuJQp6BkzuTv9O52pzwtEFVUJubKYN+zez8eTI= github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f/go.mod h1:D4eo8/CN92vm9/9UDG+ldX1/fMFa4kpl8qzyTolus8o= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -374,12 +383,24 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= +github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= +github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -392,6 +413,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -404,8 +426,14 @@ github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= +github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= +github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= +github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -419,6 +447,7 @@ github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGk github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -449,6 +478,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -486,15 +516,23 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -516,6 +554,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -542,6 +582,8 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -588,6 +630,9 @@ github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKk github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= @@ -683,8 +728,9 @@ go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -915,8 +961,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -982,8 +1028,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1121,6 +1167,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 698da2a25c..a444f65aaa 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -22,12 +22,11 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-netaddrs" + vaultApi "github.com/hashicorp/vault/api" "github.com/mitchellh/cli" "github.com/mitchellh/mapstructure" "golang.org/x/text/cases" "golang.org/x/text/language" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) @@ -86,8 +85,10 @@ type Command struct { flagEnableInjectK8SNSMirroring bool // Enables mirroring of k8s namespaces into Consul for Connect inject flagInjectK8SNSMirroringPrefix string // Prefix added to Consul namespaces created when mirroring injected services - // Flag to support a custom bootstrap token. - flagBootstrapTokenFile string + // Flags for the secrets backend. + flagSecretsBackend SecretsBackendType + flagBootstrapTokenSecretName string + flagBootstrapTokenSecretKey string flagLogLevel string flagLogJSON bool @@ -98,7 +99,9 @@ type Command struct { // flagFederation indicates if federation has been enabled in the cluster. flagFederation bool - clientset kubernetes.Interface + backend SecretsBackend // for unit testing. + clientset kubernetes.Interface + vaultClient *vaultApi.Client watcher consul.ServerConnectionManager @@ -194,9 +197,14 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagFederation, "federation", false, "Toggle for when federation has been enabled.") - c.flags.StringVar(&c.flagBootstrapTokenFile, "bootstrap-token-file", "", - "Path to file containing ACL token for creating policies and tokens. This token must have 'acl:write' permissions."+ - "When provided, servers will not be bootstrapped and their policies and tokens will not be updated.") + c.flags.StringVar((*string)(&c.flagSecretsBackend), "secrets-backend", "kubernetes", + `The secrets backend to use. Either "vault" or "kubernetes". Defaults to "kubernetes"`) + c.flags.StringVar(&c.flagBootstrapTokenSecretName, "bootstrap-token-secret-name", "", + "The name of the Vault or Kuberenetes secret for the bootstrap token. This token must have `ac::write` permission "+ + "in order to create policies and tokens. If not provided or if the secret is empty, then this command will "+ + "bootstrap ACLs and write the bootstrap token to this secret.") + c.flags.StringVar(&c.flagBootstrapTokenSecretKey, "bootstrap-token-secret-key", "", + "The key within the Vault or Kuberenetes secret containing the bootstrap token.") c.flags.DurationVar(&c.flagTimeout, "timeout", 10*time.Minute, "How long we'll try to bootstrap ACLs for before timing out, e.g. 1ms, 2s, 3m") @@ -231,6 +239,7 @@ func (c *Command) Help() string { // The function will retry its tasks indefinitely until they are complete. func (c *Command) Run(args []string) int { c.once.Do(c.init) + defer c.quitVaultAgent() if err := c.flags.Parse(args); err != nil { return 1 } @@ -264,16 +273,6 @@ func (c *Command) Run(args []string) int { } } - var providedBootstrapToken string - if c.flagBootstrapTokenFile != "" { - var err error - providedBootstrapToken, err = loadTokenFromFile(c.flagBootstrapTokenFile) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - var cancel context.CancelFunc c.ctx, cancel = context.WithTimeout(context.Background(), c.flagTimeout) // The context will only ever be intentionally ended by the timeout. @@ -306,8 +305,12 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) } - var bootstrapToken string + if err := c.configureSecretsBackend(); err != nil { + c.log.Error(err.Error()) + return 1 + } + var bootstrapToken string if c.flagACLReplicationTokenFile != "" && !c.flagCreateACLReplicationToken { // If ACL replication is enabled, we don't need to ACL bootstrap the servers // since they will be performing replication. @@ -316,21 +319,7 @@ func (c *Command) Run(args []string) int { c.log.Info("ACL replication is enabled so skipping Consul server ACL bootstrapping") bootstrapToken = aclReplicationToken } else { - // Check if we've already been bootstrapped. - var bootTokenSecretName string - if providedBootstrapToken != "" { - c.log.Info("Using provided bootstrap token") - bootstrapToken = providedBootstrapToken - } else { - bootTokenSecretName = c.withPrefix("bootstrap-acl-token") - bootstrapToken, err = c.getBootstrapToken(bootTokenSecretName) - if err != nil { - c.log.Error(fmt.Sprintf("Unexpected error looking for preexisting bootstrap Secret: %s", err)) - return 1 - } - } - - bootstrapToken, err = c.bootstrapServers(ipAddrs, bootstrapToken, bootTokenSecretName) + bootstrapToken, err = c.bootstrapServers(ipAddrs, c.backend) if err != nil { c.log.Error(err.Error()) return 1 @@ -806,24 +795,6 @@ func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, consulC return nil } -// getBootstrapToken returns the existing bootstrap token if there is one by -// reading the Kubernetes Secret with name secretName. -// If there is no bootstrap token yet, then it returns an empty string (not an error). -func (c *Command) getBootstrapToken(secretName string) (string, error) { - secret, err := c.clientset.CoreV1().Secrets(c.flagK8sNamespace).Get(c.ctx, secretName, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - return "", nil - } - return "", err - } - token, ok := secret.Data[common.ACLTokenSecretKey] - if !ok { - return "", fmt.Errorf("secret %q does not have data key 'token'", secretName) - } - return string(token), nil -} - func (c *Command) configureKubeClient() error { config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) if err != nil { @@ -836,6 +807,55 @@ func (c *Command) configureKubeClient() error { return nil } +// configureSecretsBackend configures either the Kubernetes or Vault +// secrets backend based on flags. +func (c *Command) configureSecretsBackend() error { + if c.backend != nil { + // support a fake backend in unit tests + return nil + } + secretName := c.flagBootstrapTokenSecretName + if secretName == "" { + secretName = c.withPrefix("bootstrap-acl-token") + } + + secretKey := c.flagBootstrapTokenSecretKey + if secretKey == "" { + secretKey = common.ACLTokenSecretKey + } + + switch c.flagSecretsBackend { + case SecretsBackendTypeKubernetes: + c.backend = &KubernetesSecretsBackend{ + ctx: c.ctx, + clientset: c.clientset, + k8sNamespace: c.flagK8sNamespace, + secretName: secretName, + secretKey: secretKey, + } + return nil + case SecretsBackendTypeVault: + cfg := vaultApi.DefaultConfig() + cfg.Address = "" + cfg.AgentAddress = "http://127.0.0.1:8200" + vaultClient, err := vaultApi.NewClient(cfg) + if err != nil { + return fmt.Errorf("Error initializing Vault client: %w", err) + } + + c.vaultClient = vaultClient // must set this for c.quitVaultAgent. + c.backend = &VaultSecretsBackend{ + vaultClient: c.vaultClient, + secretName: secretName, + secretKey: secretKey, + } + return nil + default: + validValues := []SecretsBackendType{SecretsBackendTypeKubernetes, SecretsBackendTypeVault} + return fmt.Errorf("Invalid value for -secrets-backend: %q. Valid values are %v.", c.flagSecretsBackend, validValues) + } +} + // untilSucceeds runs op until it returns a nil error. // If c.cmdTimeout is cancelled it will exit. func (c *Command) untilSucceeds(opName string, op func() error) error { @@ -962,6 +982,10 @@ func (c *Command) validateFlags() error { return errors.New("-consul-api-timeout must be set to a value greater than 0") } + //if c.flagVaultNamespace != "" && c.flagSecretsBackend != SecretsBackendTypeVault { + // return fmt.Errorf("-vault-namespace not supported for -secrets-backend=%q", c.flagSecretsBackend) + //} + return nil } @@ -977,6 +1001,28 @@ func loadTokenFromFile(tokenFile string) (string, error) { return strings.TrimSpace(string(tokenBytes)), nil } +func (c *Command) quitVaultAgent() { + if c.vaultClient == nil { + return + } + + // Tell the Vault agent sidecar to quit. Without this, the Job does not + // complete because the Vault agent does not stop. This retries because it + // does not know exactly when the Vault agent sidecar will start. + err := c.untilSucceeds("tell Vault agent to quit", func() error { + // TODO: RawRequest is deprecated, but there is also not a high level + // method for this in the Vault client. + // nolint:staticcheck // SA1004 ignore + _, err := c.vaultClient.RawRequest( + c.vaultClient.NewRequest("POST", "/agent/v1/quit"), + ) + return err + }) + if err != nil { + c.log.Error("Error telling Vault agent to quit", "error", err) + } +} + const ( consulDefaultNamespace = "default" consulDefaultPartition = "default" 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 e31b787e4b..fa56f3bb3a 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -222,7 +222,6 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { // a non-default partition. func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) server := partitionedSetup(t, bootToken, "test") k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) @@ -231,6 +230,7 @@ func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmd.init() args := []string{ @@ -239,7 +239,6 @@ func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], "-resource-prefix=" + resourcePrefix, "-k8s-namespace=" + ns, - "-bootstrap-token-file", tokenFile, "-allow-dns", "-partition=test", "-enable-namespaces", diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index 3111f58820..ffe60af593 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -60,13 +60,6 @@ func TestRun_FlagValidation(t *testing.T) { "-resource-prefix=prefix"}, ExpErr: "unable to read token from file \"/notexist\": open /notexist: no such file or directory", }, - { - Flags: []string{ - "-bootstrap-token-file=/notexist", - "-addresses=localhost", - "-resource-prefix=prefix"}, - ExpErr: "unable to read token from file \"/notexist\": open /notexist: no such file or directory", - }, { Flags: []string{ "-addresses=localhost", @@ -407,7 +400,6 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) k8s, testAgent := completeBootstrappedSetup(t, bootToken) setUpK8sServiceAccount(t, k8s, ns) @@ -417,11 +409,11 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmdArgs := append([]string{ "-timeout=1m", "-k8s-namespace", ns, - "-bootstrap-token-file", tokenFile, "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], @@ -915,7 +907,6 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { // Create Consul with ACLs already bootstrapped so that we can // then seed it with our manually created policy. bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) k8s, testAgent := completeBootstrappedSetup(t, bootToken) setUpK8sServiceAccount(t, k8s, ns) @@ -938,11 +929,11 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmdArgs := []string{ "-timeout=1s", "-k8s-namespace", ns, - "-bootstrap-token-file", tokenFile, "-resource-prefix=" + resourcePrefix, "-k8s-namespace=" + ns, "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], @@ -1453,272 +1444,223 @@ func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { // Test if there is an old bootstrap Secret we still try to create and set // server tokens. func TestRun_AlreadyBootstrapped(t *testing.T) { - t.Parallel() - cases := map[string]bool{ - "token saved in k8s secret": true, - "token provided via file": false, - } - - for name, tokenFromK8sSecret := range cases { - t.Run(name, func(t *testing.T) { - k8s := fake.NewSimpleClientset() - - type APICall struct { - Method string - Path string - } - var consulAPICalls []APICall - - // Start the Consul server. - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Record all the API calls made. - consulAPICalls = append(consulAPICalls, APICall{ - Method: r.Method, - Path: r.URL.Path, - }) - switch r.URL.Path { - case "/v1/agent/self": - 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: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - defer consulServer.Close() + k8s := fake.NewSimpleClientset() - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - setUpK8sServiceAccount(t, k8s, ns) + type APICall struct { + Method string + Path string + } + var consulAPICalls []APICall - cmdArgs := []string{ - "-timeout=500ms", - "-resource-prefix=" + resourcePrefix, - "-k8s-namespace=" + ns, - "-addresses=" + serverURL.Hostname(), - "-http-port=" + serverURL.Port(), - } + // Start the Consul server. + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Record all the API calls made. + consulAPICalls = append(consulAPICalls, APICall{ + Method: r.Method, + Path: r.URL.Path, + }) + switch r.URL.Path { + case "/v1/agent/self": + 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: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + defer consulServer.Close() - // Create the bootstrap secret. - if tokenFromK8sSecret { - _, err = k8s.CoreV1().Secrets(ns).Create( - context.Background(), - &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcePrefix + "-bootstrap-acl-token", - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - "token": []byte("old-token"), - }, - }, - metav1.CreateOptions{}) - require.NoError(t, err) - } else { - // Write token to a file. - bootTokenFile, err := os.CreateTemp("", "") - require.NoError(t, err) - defer os.RemoveAll(bootTokenFile.Name()) + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + setUpK8sServiceAccount(t, k8s, ns) - _, err = bootTokenFile.WriteString("old-token") - require.NoError(t, err) + cmdArgs := []string{ + "-timeout=500ms", + "-resource-prefix=" + resourcePrefix, + "-k8s-namespace=" + ns, + "-addresses=" + serverURL.Hostname(), + "-http-port=" + serverURL.Port(), + } - require.NoError(t, err) - cmdArgs = append(cmdArgs, "-bootstrap-token-file", bootTokenFile.Name()) - } + // Create the bootstrap secret. + _, err = k8s.CoreV1().Secrets(ns).Create( + context.Background(), + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePrefix + "-bootstrap-acl-token", + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + "token": []byte("old-token"), + }, + }, + metav1.CreateOptions{}) + require.NoError(t, err) - // Run the command. - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), - } + // Run the command. + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + } - responseCode := cmd.Run(cmdArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - // Test that the Secret is the same. - if tokenFromK8sSecret { - secret, err := k8s.CoreV1().Secrets(ns).Get(context.Background(), resourcePrefix+"-bootstrap-acl-token", metav1.GetOptions{}) - require.NoError(t, err) - require.Contains(t, secret.Data, "token") - require.Equal(t, "old-token", string(secret.Data["token"])) - } + // Test that the Secret is the same. + secret, err := k8s.CoreV1().Secrets(ns).Get(context.Background(), resourcePrefix+"-bootstrap-acl-token", metav1.GetOptions{}) + require.NoError(t, err) + require.Contains(t, secret.Data, "token") + require.Equal(t, "old-token", string(secret.Data["token"])) - // Test that the expected API calls were made. - require.Equal(t, []APICall{ - // We expect calls for updating the server policy, setting server tokens, - // and updating client policy. - { - "PUT", - "/v1/acl/policy", - }, - { - "GET", - "/v1/acl/tokens", - }, - { - "PUT", - "/v1/acl/token", - }, - { - "PUT", - "/v1/agent/token/agent", - }, - { - "PUT", - "/v1/agent/token/acl_agent_token", - }, - { - "GET", - "/v1/agent/self", - }, - { - "PUT", - "/v1/acl/auth-method", - }, - { - "PUT", - "/v1/acl/policy", - }, - { - "GET", - "/v1/acl/role/name/release-name-consul-client-acl-role", - }, - { - "PUT", - "/v1/acl/role", - }, - { - "GET", - "/v1/acl/binding-rules", - }, - { - "PUT", - "/v1/acl/binding-rule", - }, - }, consulAPICalls) - }) - } + // Test that the expected API calls were made. + require.Equal(t, []APICall{ + // We expect calls for updating the server policy, setting server tokens, + // and updating client policy. + { + "PUT", + "/v1/acl/policy", + }, + { + "GET", + "/v1/acl/tokens", + }, + { + "PUT", + "/v1/acl/token", + }, + { + "PUT", + "/v1/agent/token/agent", + }, + { + "PUT", + "/v1/agent/token/acl_agent_token", + }, + { + "GET", + "/v1/agent/self", + }, + { + "PUT", + "/v1/acl/auth-method", + }, + { + "PUT", + "/v1/acl/policy", + }, + { + "GET", + "/v1/acl/role/name/release-name-consul-client-acl-role", + }, + { + "PUT", + "/v1/acl/role", + }, + { + "GET", + "/v1/acl/binding-rules", + }, + { + "PUT", + "/v1/acl/binding-rule", + }, + }, consulAPICalls) } // Test if there is an old bootstrap Secret and the server token exists // that we don't try and recreate the token. func TestRun_AlreadyBootstrapped_ServerTokenExists(t *testing.T) { - t.Parallel() - cases := map[string]bool{ - "token saved in k8s secret": true, - "token provided via file": false, - } - - for name, tokenInK8sSecret := range cases { - t.Run(name, func(t *testing.T) { - - // First set everything up with ACLs bootstrapped. - bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - k8s, testAgent := completeBootstrappedSetup(t, bootToken) - setUpK8sServiceAccount(t, k8s, ns) - - cmdArgs := []string{ - "-timeout=1m", - "-k8s-namespace", ns, - "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], - "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], - "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], - "-resource-prefix", resourcePrefix, - } - - if tokenInK8sSecret { - _, err := k8s.CoreV1().Secrets(ns).Create(context.Background(), &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcePrefix + "-bootstrap-acl-token", - }, - Data: map[string][]byte{ - "token": []byte(bootToken), - }, - }, metav1.CreateOptions{}) - require.NoError(t, err) - } else { - // Write token to a file. - bootTokenFile, err := os.CreateTemp("", "") - require.NoError(t, err) - defer os.RemoveAll(bootTokenFile.Name()) + // First set everything up with ACLs bootstrapped. + bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + k8s, testAgent := completeBootstrappedSetup(t, bootToken) + setUpK8sServiceAccount(t, k8s, ns) - _, err = bootTokenFile.WriteString(bootToken) - require.NoError(t, err) + cmdArgs := []string{ + "-timeout=1m", + "-k8s-namespace", ns, + "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], + "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], + "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], + "-resource-prefix", resourcePrefix, + } - require.NoError(t, err) - cmdArgs = append(cmdArgs, "-bootstrap-token-file", bootTokenFile.Name()) - } + _, err := k8s.CoreV1().Secrets(ns).Create(context.Background(), &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePrefix + "-bootstrap-acl-token", + }, + Data: map[string][]byte{ + "token": []byte(bootToken), + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) - consulClient, err := api.NewClient(&api.Config{ - Address: testAgent.TestServer.HTTPAddr, - Token: bootToken, - }) - require.NoError(t, err) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - } + consulClient, err := api.NewClient(&api.Config{ + Address: testAgent.TestServer.HTTPAddr, + Token: bootToken, + }) + require.NoError(t, err) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + } - cmd.init() - // Create the server policy and token _before_ we run the command. - agentPolicyRules, err := cmd.agentRules() - require.NoError(t, err) - policy, _, err := consulClient.ACL().PolicyCreate(&api.ACLPolicy{ - Name: "agent-token", - Description: "Agent Token Policy", - Rules: agentPolicyRules, - }, nil) - require.NoError(t, err) - _, _, err = consulClient.ACL().TokenCreate(&api.ACLToken{ - Description: fmt.Sprintf("Server Token for %s", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0]), - Policies: []*api.ACLTokenPolicyLink{ - { - Name: policy.Name, - }, - }, - }, nil) - require.NoError(t, err) + cmd.init() + // Create the server policy and token _before_ we run the command. + agentPolicyRules, err := cmd.agentRules() + require.NoError(t, err) + policy, _, err := consulClient.ACL().PolicyCreate(&api.ACLPolicy{ + Name: "agent-token", + Description: "Agent Token Policy", + Rules: agentPolicyRules, + }, nil) + require.NoError(t, err) + _, _, err = consulClient.ACL().TokenCreate(&api.ACLToken{ + Description: fmt.Sprintf("Server Token for %s", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0]), + Policies: []*api.ACLTokenPolicyLink{ + { + Name: policy.Name, + }, + }, + }, nil) + require.NoError(t, err) - // Run the command. - responseCode := cmd.Run(cmdArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + // Run the command. + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - // Check that only one server token exists, i.e. it didn't create an - // extra token. - tokens, _, err := consulClient.ACL().TokenList(nil) - require.NoError(t, err) - count := 0 - for _, token := range tokens { - if len(token.Policies) == 1 && token.Policies[0].Name == policy.Name { - count++ - } - } - require.Equal(t, 1, count) - }) + // Check that only one server token exists, i.e. it didn't create an + // extra token. + tokens, _, err := consulClient.ACL().TokenList(nil) + require.NoError(t, err) + count := 0 + for _, token := range tokens { + if len(token.Policies) == 1 && token.Policies[0].Name == policy.Name { + count++ + } } + require.Equal(t, 1, count) } // Test if -set-server-tokens is false (i.e. servers are disabled), we skip bootstrapping of the servers @@ -1728,7 +1670,6 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { k8s := fake.NewSimpleClientset() bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) type APICall struct { Method string @@ -1766,6 +1707,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { UI: ui, clientset: k8s, watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } responseCode := cmd.Run([]string{ "-timeout=500ms", @@ -1773,7 +1715,6 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { "-k8s-namespace=" + ns, "-addresses=" + serverURL.Hostname(), "-http-port=" + serverURL.Port(), - "-bootstrap-token-file=" + tokenFile, "-set-server-tokens=false", "-client=false", // disable client token, so there are fewer calls }) diff --git a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go new file mode 100644 index 0000000000..4e3efcf65b --- /dev/null +++ b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go @@ -0,0 +1,62 @@ +package serveraclinit + +import ( + "context" + "fmt" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + apiv1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const SecretsBackendTypeKubernetes SecretsBackendType = "kubernetes" + +type KubernetesSecretsBackend struct { + ctx context.Context + clientset kubernetes.Interface + k8sNamespace string + secretName string + secretKey string +} + +var _ SecretsBackend = (*KubernetesSecretsBackend)(nil) + +// BootstrapToken returns the existing bootstrap token if there is one by +// reading the Kubernetes Secret. If there is no bootstrap token yet, then +// it returns an empty string (not an error). +func (b *KubernetesSecretsBackend) BootstrapToken() (string, error) { + secret, err := b.clientset.CoreV1().Secrets(b.k8sNamespace).Get(b.ctx, b.secretName, metav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return "", nil + } + return "", err + } + token, ok := secret.Data[b.secretKey] + if !ok { + return "", fmt.Errorf("secret %q does not have data key %q", b.secretName, b.secretKey) + } + return string(token), nil + +} + +// WriteBootstrapToken writes the given bootstrap token to the Kubernetes Secret. +func (b *KubernetesSecretsBackend) WriteBootstrapToken(bootstrapToken string) error { + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.secretName, + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + b.secretKey: []byte(bootstrapToken), + }, + } + _, err := b.clientset.CoreV1().Secrets(b.k8sNamespace).Create(b.ctx, secret, metav1.CreateOptions{}) + return err +} + +func (b *KubernetesSecretsBackend) BootstrapTokenSecretName() string { + return b.secretName +} diff --git a/control-plane/subcommand/server-acl-init/secrets_backend.go b/control-plane/subcommand/server-acl-init/secrets_backend.go new file mode 100644 index 0000000000..4b4d5c2fc4 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/secrets_backend.go @@ -0,0 +1,18 @@ +package serveraclinit + +type SecretsBackendType string + +type SecretsBackend interface { + // BootstrapToken fetches the bootstrap token from the backend. If the + // token is not found or empty, implementations should return an empty + // string (not an error). + BootstrapToken() (string, error) + + // WriteBootstrapToken writes the given bootstrap token to the backend. + // Implementations of this method do not need to retry the write until + // successful. + WriteBootstrapToken(string) error + + // BootstrapTokenSecretName returns the name of the bootstrap token secret. + BootstrapTokenSecretName() string +} diff --git a/control-plane/subcommand/server-acl-init/servers.go b/control-plane/subcommand/server-acl-init/servers.go index 2dc8f8ab67..01e9a58145 100644 --- a/control-plane/subcommand/server-acl-init/servers.go +++ b/control-plane/subcommand/server-acl-init/servers.go @@ -1,7 +1,6 @@ package serveraclinit import ( - "errors" "fmt" "net" "net/http" @@ -9,29 +8,30 @@ import ( "time" "github.com/hashicorp/consul/api" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // bootstrapServers bootstraps ACLs and ensures each server has an ACL token. -// If bootstrapToken is not empty then ACLs are already bootstrapped. -func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, bootstrapToken, bootTokenSecretName string) (string, error) { +// If a bootstrap is found in the secrets backend, then skip ACL bootstrapping. +// Otherwise, bootstrap ACLs and write the bootstrap token to the secrets backend. +func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, backend SecretsBackend) (string, error) { // Pick the first server address to connect to for bootstrapping and set up connection. firstServerAddr := fmt.Sprintf("%s:%d", serverAddresses[0].IP.String(), c.consulFlags.HTTPPort) - if bootstrapToken == "" { - c.log.Info("No bootstrap token from previous installation found, continuing on to bootstrapping") + bootstrapToken, err := backend.BootstrapToken() + if err != nil { + return "", fmt.Errorf("Unexpected error fetching bootstrap token secret: %w", err) + } - var err error - bootstrapToken, err = c.bootstrapACLs(firstServerAddr, bootTokenSecretName) + if bootstrapToken != "" { + c.log.Info("Found bootstrap token in secrets backend", "secret", backend.BootstrapTokenSecretName()) + } else { + c.log.Info("No bootstrap token found in secrets backend, continuing to ACL bootstrapping", "secret", backend.BootstrapTokenSecretName()) + bootstrapToken, err = c.bootstrapACLs(firstServerAddr, backend) if err != nil { return "", err } - } else { - c.log.Info(fmt.Sprintf("ACLs already bootstrapped - retrieved bootstrap token from Secret %q", bootTokenSecretName)) } // We should only create and set server tokens when servers are running within this cluster. @@ -47,7 +47,7 @@ func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, bootstrapToken, // bootstrapACLs makes the ACL bootstrap API call and writes the bootstrap token // to a kube secret. -func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (string, error) { +func (c *Command) bootstrapACLs(firstServerAddr string, backend SecretsBackend) (string, error) { config := c.consulFlags.ConsulClientConfig().APIClientConfig config.Address = firstServerAddr // Exempting this particular use of the http client from using global.consulAPITimeout @@ -78,9 +78,12 @@ func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (st // Check if already bootstrapped. if strings.Contains(err.Error(), "Unexpected response code: 403") { - unrecoverableErr = errors.New("ACLs already bootstrapped but the ACL token was not written to a Kubernetes secret." + - " We can't proceed because the bootstrap token is lost." + - " You must reset ACLs.") + unrecoverableErr = fmt.Errorf( + "ACLs already bootstrapped but unable to find the bootstrap token in the secrets backend."+ + " We can't proceed without a bootstrap token."+ + " Store a token with `acl:write` permission in the secret %q.", + backend.BootstrapTokenSecretName(), + ) return nil } @@ -98,21 +101,12 @@ func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (st return "", err } - // Write bootstrap token to a Kubernetes secret. - err = c.untilSucceeds(fmt.Sprintf("writing bootstrap Secret %q", bootTokenSecretName), + // Write bootstrap token to the secrets backend. + err = c.untilSucceeds(fmt.Sprintf("writing bootstrap Secret %q", backend.BootstrapTokenSecretName()), func() error { - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: bootTokenSecretName, - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - common.ACLTokenSecretKey: []byte(bootstrapToken), - }, - } - _, err := c.clientset.CoreV1().Secrets(c.flagK8sNamespace).Create(c.ctx, secret, metav1.CreateOptions{}) - return err - }) + return backend.WriteBootstrapToken(bootstrapToken) + }, + ) return bootstrapToken, err } diff --git a/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go new file mode 100644 index 0000000000..5c9a63f1a5 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go @@ -0,0 +1,20 @@ +package serveraclinit + +type FakeSecretsBackend struct { + bootstrapToken string +} + +func (b *FakeSecretsBackend) BootstrapToken() (string, error) { + return b.bootstrapToken, nil +} + +func (*FakeSecretsBackend) BootstrapTokenSecretName() string { + return "fake-bootstrap-token" +} + +func (b *FakeSecretsBackend) WriteBootstrapToken(token string) error { + b.bootstrapToken = token + return nil +} + +var _ SecretsBackend = (*FakeSecretsBackend)(nil) diff --git a/control-plane/subcommand/server-acl-init/vault_secrets_backend.go b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go new file mode 100644 index 0000000000..2706567878 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go @@ -0,0 +1,66 @@ +package serveraclinit + +import ( + "fmt" + + "github.com/hashicorp/vault/api" +) + +const SecretsBackendTypeVault SecretsBackendType = "vault" + +type VaultSecretsBackend struct { + vaultClient *api.Client + secretName string + secretKey string +} + +var _ SecretsBackend = (*VaultSecretsBackend)(nil) + +// BootstrapToken returns the bootstrap token stored in Vault. +// If not found this returns an empty string (not an error). +func (b *VaultSecretsBackend) BootstrapToken() (string, error) { + secret, err := b.vaultClient.Logical().Read(b.secretName) + if err != nil { + return "", err + } + if secret == nil || secret.Data == nil { + // secret not found or empty. + return "", nil + } + // Grab secret.Data["data"][secretKey]. + dataRaw, found := secret.Data["data"] + if !found { + return "", nil + } + data, ok := dataRaw.(map[string]interface{}) + if !ok { + return "", nil + } + tokRaw, found := data[b.secretKey] + if !found { + return "", nil + } + if tok, ok := tokRaw.(string); ok { + return tok, nil + } + return "", fmt.Errorf("Unexpected data. To resolve this, "+ + "`vault kv put %[1]s=` if Consul is already ACL bootstrapped. "+ + "If not ACL bootstrapped, `vault kv put %[1]s=\"\"`", b.secretKey, b.secretKey) +} + +// BootstrapTokenSecretName returns the name of the bootstrap token secret. +func (b *VaultSecretsBackend) BootstrapTokenSecretName() string { + return b.secretName +} + +// WriteBootstrapToken writes the bootstrap token to Vault. +func (b *VaultSecretsBackend) WriteBootstrapToken(bootstrapToken string) error { + _, err := b.vaultClient.Logical().Write(b.secretName, + map[string]interface{}{ + "data": map[string]interface{}{ + b.secretKey: bootstrapToken, + }, + }, + ) + return err +}