mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	VAULT-33758: IPv6 address conformance for proxy and agent (#29517)
This is a follow-up to our initial work[0] to address RFC-5952 §4 conformance for IPv6 addresses in Vault. The initial pass focused on the vault server configuration and start-up routines. This follow-up focuses on Agent and Proxy, with a few minor improvements for server. The approach generally mirrors the server implementation but also adds support for normalization with CLI configuration overrides. One aspect we do not normalize currently is Agent/Proxy client creation to the Vault server with credentials taken from environment variables, as it would require larger changes to the `api` module. In practice this ought to be fine for the majority of cases. [0]: https://github.com/hashicorp/vault/pull/29228
This commit is contained in:
		| @@ -939,10 +939,11 @@ func (c *AgentCommand) applyConfigOverrides(f *FlagSets, config *agentConfig.Con | ||||
| 	}) | ||||
|  | ||||
| 	c.setStringFlag(f, config.Vault.Address, &StringVar{ | ||||
| 		Name:    flagNameAddress, | ||||
| 		Target:  &c.flagAddress, | ||||
| 		Default: "https://127.0.0.1:8200", | ||||
| 		EnvVar:  api.EnvVaultAddress, | ||||
| 		Name:        flagNameAddress, | ||||
| 		Target:      &c.flagAddress, | ||||
| 		Default:     "https://127.0.0.1:8200", | ||||
| 		EnvVar:      api.EnvVaultAddress, | ||||
| 		Normalizers: []func(string) string{configutil.NormalizeAddr}, | ||||
| 	}) | ||||
| 	config.Vault.Address = c.flagAddress | ||||
| 	c.setStringFlag(f, config.Vault.CACert, &StringVar{ | ||||
| @@ -1031,13 +1032,13 @@ func (c *AgentCommand) setStringFlag(f *FlagSets, configVal string, fVar *String | ||||
| 		// Don't do anything as the flag is already set from the command line | ||||
| 	case flagEnvSet: | ||||
| 		// Use value from env var | ||||
| 		*fVar.Target = flagEnvValue | ||||
| 		fVar.SetTarget(flagEnvValue) | ||||
| 	case configVal != "": | ||||
| 		// Use value from config | ||||
| 		*fVar.Target = configVal | ||||
| 		fVar.SetTarget(configVal) | ||||
| 	default: | ||||
| 		// Use the default value | ||||
| 		*fVar.Target = fVar.Default | ||||
| 		fVar.SetTarget(fVar.Default) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -751,6 +751,10 @@ func parseVault(result *Config, list *ast.ObjectList) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if v.Address != "" { | ||||
| 		v.Address = configutil.NormalizeAddr(v.Address) | ||||
| 	} | ||||
|  | ||||
| 	if v.TLSSkipVerifyRaw != nil { | ||||
| 		v.TLSSkipVerify, err = parseutil.ParseBool(v.TLSSkipVerifyRaw) | ||||
| 		if err != nil { | ||||
| @@ -1038,10 +1042,63 @@ func parseMethod(result *Config, list *ast.ObjectList) error { | ||||
| 	// Canonicalize namespace path if provided | ||||
| 	m.Namespace = namespace.Canonicalize(m.Namespace) | ||||
|  | ||||
| 	// Normalize any configuration addresses | ||||
| 	if len(m.Config) > 0 { | ||||
| 		var err error | ||||
| 		for k, v := range m.Config { | ||||
| 			vStr, ok := v.(string) | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			m.Config[k], err = normalizeAutoAuthMethod(m.Type, k, vStr) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	result.AutoAuth.Method = &m | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // autoAuthMethodKeys maps an auto-auth method type to its associated | ||||
| // configuration whose values are URLs, IP addresses, or host:port style | ||||
| // addresses. All auto-auth types must have an entry in this map, otherwise our | ||||
| // normalization check will fail when parsing the storage entry config. | ||||
| // Auto-auth method types which don't contain such keys should include an empty | ||||
| // array. | ||||
| var autoAuthMethodKeys = map[string][]string{ | ||||
| 	"alicloud":   {""}, | ||||
| 	"approle":    {""}, | ||||
| 	"aws":        {""}, | ||||
| 	"azure":      {"resource"}, | ||||
| 	"cert":       {""}, | ||||
| 	"cf":         {""}, | ||||
| 	"gcp":        {"service_account"}, | ||||
| 	"jwt":        {""}, | ||||
| 	"ldap":       {""}, | ||||
| 	"kerberos":   {""}, | ||||
| 	"kubernetes": {""}, | ||||
| 	"oci":        {""}, | ||||
| 	"token_file": {""}, | ||||
| } | ||||
|  | ||||
| // normalizeAutoAuthMethod takes a storage name, a configuration key | ||||
| // and its associated value and will normalize any URLs, IP addresses, or | ||||
| // host:port style addresses. | ||||
| func normalizeAutoAuthMethod(method string, key string, value string) (string, error) { | ||||
| 	keys, ok := autoAuthMethodKeys[method] | ||||
| 	if !ok { | ||||
| 		return "", fmt.Errorf("unknown auto-auth method type %s", method) | ||||
| 	} | ||||
|  | ||||
| 	if slices.Contains(keys, key) { | ||||
| 		return configutil.NormalizeAddr(value), nil | ||||
| 	} | ||||
|  | ||||
| 	return value, nil | ||||
| } | ||||
|  | ||||
| func parseSinks(result *Config, list *ast.ObjectList) error { | ||||
| 	name := "sink" | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import ( | ||||
| 	"github.com/hashicorp/vault/command/agentproxyshared" | ||||
| 	"github.com/hashicorp/vault/internalshared/configutil" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/pointerutil" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	"golang.org/x/exp/slices" | ||||
| ) | ||||
|  | ||||
| @@ -230,6 +231,9 @@ func TestLoadConfigDir_AgentCache(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	config2, err := LoadConfigFile("./test-fixtures/config-dir-cache/config-cache2.hcl") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	mergedConfig := config.Merge(config2) | ||||
|  | ||||
| @@ -441,77 +445,117 @@ func TestLoadConfigFile_AgentCache_NoListeners(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestLoadConfigFile(t *testing.T) { | ||||
| 	if err := os.Setenv("TEST_AAD_ENV", "aad"); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if err := os.Unsetenv("TEST_AAD_ENV"); err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 	}() | ||||
| // Test_LoadConfigFile_AutoAuth_AddrConformance verifies basic config file | ||||
| // loading in addition to RFC-5942 §4 normalization of auto-auth methods. | ||||
| // See: https://rfc-editor.org/rfc/rfc5952.html | ||||
| func Test_LoadConfigFile_AutoAuth_AddrConformance(t *testing.T) { | ||||
| 	t.Setenv("TEST_AAD_ENV", "aad") | ||||
|  | ||||
| 	config, err := LoadConfigFile("./test-fixtures/config.hcl") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	expected := &Config{ | ||||
| 		SharedConfig: &configutil.SharedConfig{ | ||||
| 			PidFile: "./pidfile", | ||||
| 			LogFile: "/var/log/vault/vault-agent.log", | ||||
| 		}, | ||||
| 		AutoAuth: &AutoAuth{ | ||||
| 			Method: &Method{ | ||||
| 				Type:      "aws", | ||||
| 				MountPath: "auth/aws", | ||||
| 				Namespace: "my-namespace/", | ||||
| 				Config: map[string]interface{}{ | ||||
| 					"role": "foobar", | ||||
| 				}, | ||||
| 				MaxBackoff: 0, | ||||
| 			}, | ||||
| 			Sinks: []*Sink{ | ||||
| 				{ | ||||
| 					Type:   "file", | ||||
| 					DHType: "curve25519", | ||||
| 					DHPath: "/tmp/file-foo-dhpath", | ||||
| 					AAD:    "foobar", | ||||
| 					Config: map[string]interface{}{ | ||||
| 						"path": "/tmp/file-foo", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Type:      "file", | ||||
| 					WrapTTL:   5 * time.Minute, | ||||
| 					DHType:    "curve25519", | ||||
| 					DHPath:    "/tmp/file-foo-dhpath2", | ||||
| 					AAD:       "aad", | ||||
| 					DeriveKey: true, | ||||
| 					Config: map[string]interface{}{ | ||||
| 						"path": "/tmp/file-bar", | ||||
| 					}, | ||||
| 				}, | ||||
| 	for name, method := range map[string]*Method{ | ||||
| 		"aws": { | ||||
| 			Type:      "aws", | ||||
| 			MountPath: "auth/aws", | ||||
| 			Namespace: "aws-namespace/", | ||||
| 			Config: map[string]any{ | ||||
| 				"role": "foobar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		TemplateConfig: &TemplateConfig{ | ||||
| 			MaxConnectionsPerHost: DefaultTemplateConfigMaxConnsPerHost, | ||||
| 		"azure": { | ||||
| 			Type:      "azure", | ||||
| 			MountPath: "auth/azure", | ||||
| 			Namespace: "azure-namespace/", | ||||
| 			Config: map[string]any{ | ||||
| 				"authenticate_from_environment": true, | ||||
| 				"role":                          "dev-role", | ||||
| 				"resource":                      "https://[2001:0:0:1::1]", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 		"gcp": { | ||||
| 			Type:      "gcp", | ||||
| 			MountPath: "auth/gcp", | ||||
| 			Namespace: "gcp-namespace/", | ||||
| 			Config: map[string]any{ | ||||
| 				"role":            "dev-role", | ||||
| 				"service_account": "https://[2001:db8:ac3:fe4::1]", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			config, err := LoadConfigFile("./test-fixtures/config-auto-auth-" + name + ".hcl") | ||||
| 			require.NoError(t, err) | ||||
|  | ||||
| 	config.Prune() | ||||
| 	if diff := deep.Equal(config, expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| 			expected := &Config{ | ||||
| 				SharedConfig: &configutil.SharedConfig{ | ||||
| 					PidFile: "./pidfile", | ||||
| 					Listeners: []*configutil.Listener{ | ||||
| 						{ | ||||
| 							Type:       "unix", | ||||
| 							Address:    "/path/to/socket", | ||||
| 							TLSDisable: true, | ||||
| 							AgentAPI: &configutil.AgentAPI{ | ||||
| 								EnableQuit: true, | ||||
| 							}, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:       "tcp", | ||||
| 							Address:    "2001:db8::1:8200", // Normalized | ||||
| 							TLSDisable: true, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:       "tcp", | ||||
| 							Address:    "[2001:0:0:1::1]:3000", // Normalized | ||||
| 							Role:       "metrics_only", | ||||
| 							TLSDisable: true, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:        "tcp", | ||||
| 							Role:        "default", | ||||
| 							Address:     "2001:db8:0:1:1:1:1:1:8400", // Normalized | ||||
| 							TLSKeyFile:  "/path/to/cakey.pem", | ||||
| 							TLSCertFile: "/path/to/cacert.pem", | ||||
| 						}, | ||||
| 					}, | ||||
| 					LogFile: "/var/log/vault/vault-agent.log", | ||||
| 				}, | ||||
| 				Vault: &Vault{ | ||||
| 					Address: "https://[2001:db8::1]:8200", // Address is normalized | ||||
| 					Retry: &Retry{ | ||||
| 						NumRetries: 12, // Default number of retries when a vault stanza is set | ||||
| 					}, | ||||
| 				}, | ||||
| 				AutoAuth: &AutoAuth{ | ||||
| 					Method: method, // Method properties are normalized correctly | ||||
| 					Sinks: []*Sink{ | ||||
| 						{ | ||||
| 							Type:   "file", | ||||
| 							DHType: "curve25519", | ||||
| 							DHPath: "/tmp/file-foo-dhpath", | ||||
| 							AAD:    "foobar", | ||||
| 							Config: map[string]interface{}{ | ||||
| 								"path": "/tmp/file-foo", | ||||
| 							}, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:      "file", | ||||
| 							WrapTTL:   5 * time.Minute, | ||||
| 							DHType:    "curve25519", | ||||
| 							DHPath:    "/tmp/file-foo-dhpath2", | ||||
| 							AAD:       "aad", | ||||
| 							DeriveKey: true, | ||||
| 							Config: map[string]interface{}{ | ||||
| 								"path": "/tmp/file-bar", | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				TemplateConfig: &TemplateConfig{ | ||||
| 					MaxConnectionsPerHost: DefaultTemplateConfigMaxConnsPerHost, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 	config, err = LoadConfigFile("./test-fixtures/config-embedded-type.hcl") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	config.Prune() | ||||
| 	if diff := deep.Equal(config, expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 			config.Prune() | ||||
| 			require.EqualValues(t, expected, config) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										69
									
								
								command/agent/config/test-fixtures/config-auto-auth-aws.hcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								command/agent/config/test-fixtures/config-auto-auth-aws.hcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
| log_file = "/var/log/vault/vault-agent.log" | ||||
|  | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
|  | ||||
| auto_auth { | ||||
|   method { | ||||
|     type      = "aws" | ||||
|     namespace = "/aws-namespace" | ||||
|     config = { | ||||
|       role = "foobar" | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type = "file" | ||||
|     config = { | ||||
|       path = "/tmp/file-foo" | ||||
|     } | ||||
|     aad     = "foobar" | ||||
|     dh_type = "curve25519" | ||||
|     dh_path = "/tmp/file-foo-dhpath" | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type        = "file" | ||||
|     wrap_ttl    = "5m" | ||||
|     aad_env_var = "TEST_AAD_ENV" | ||||
|     dh_type     = "curve25519" | ||||
|     dh_path     = "/tmp/file-foo-dhpath2" | ||||
|     derive_key  = true | ||||
|     config = { | ||||
|       path = "/tmp/file-bar" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "unix" { | ||||
|   address     = "/path/to/socket" | ||||
|   tls_disable = true | ||||
|  | ||||
|   agent_api { | ||||
|     enable_quit = true | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   address     = "2001:0db8::0001:8200" | ||||
|   tls_disable = true | ||||
| } | ||||
|  | ||||
| listener { | ||||
|   type        = "tcp" | ||||
|   address     = "[2001:0:0:1:0:0:0:1]:3000" | ||||
|   tls_disable = true | ||||
|   role        = "metrics_only" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   role          = "default" | ||||
|   address       = "2001:db8:0:1:1:1:1:1:8400" | ||||
|   tls_key_file  = "/path/to/cakey.pem" | ||||
|   tls_cert_file = "/path/to/cacert.pem" | ||||
| } | ||||
| @@ -0,0 +1,71 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
| log_file = "/var/log/vault/vault-agent.log" | ||||
|  | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
|  | ||||
| auto_auth { | ||||
|   method { | ||||
|     type      = "azure" | ||||
|     namespace = "/azure-namespace" | ||||
|     config = { | ||||
|       authenticate_from_environment = true | ||||
|       role                          = "dev-role" | ||||
|       resource                      = "https://[2001:0:0:1:0:0:0:1]", | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type = "file" | ||||
|     config = { | ||||
|       path = "/tmp/file-foo" | ||||
|     } | ||||
|     aad     = "foobar" | ||||
|     dh_type = "curve25519" | ||||
|     dh_path = "/tmp/file-foo-dhpath" | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type        = "file" | ||||
|     wrap_ttl    = "5m" | ||||
|     aad_env_var = "TEST_AAD_ENV" | ||||
|     dh_type     = "curve25519" | ||||
|     dh_path     = "/tmp/file-foo-dhpath2" | ||||
|     derive_key  = true | ||||
|     config = { | ||||
|       path = "/tmp/file-bar" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "unix" { | ||||
|   address     = "/path/to/socket" | ||||
|   tls_disable = true | ||||
|  | ||||
|   agent_api { | ||||
|     enable_quit = true | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   address     = "2001:0db8::0001:8200" | ||||
|   tls_disable = true | ||||
| } | ||||
|  | ||||
| listener { | ||||
|   type        = "tcp" | ||||
|   address     = "[2001:0:0:1:0:0:0:1]:3000" | ||||
|   tls_disable = true | ||||
|   role        = "metrics_only" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   role          = "default" | ||||
|   address       = "2001:db8:0:1:1:1:1:1:8400" | ||||
|   tls_key_file  = "/path/to/cakey.pem" | ||||
|   tls_cert_file = "/path/to/cacert.pem" | ||||
| } | ||||
							
								
								
									
										70
									
								
								command/agent/config/test-fixtures/config-auto-auth-gcp.hcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								command/agent/config/test-fixtures/config-auto-auth-gcp.hcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
| log_file = "/var/log/vault/vault-agent.log" | ||||
|  | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
|  | ||||
| auto_auth { | ||||
|   method { | ||||
|     type      = "gcp" | ||||
|     namespace = "/gcp-namespace" | ||||
|     config = { | ||||
|       role            = "dev-role" | ||||
|       service_account = "https://[2001:DB8:AC3:FE4::1]" | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type = "file" | ||||
|     config = { | ||||
|       path = "/tmp/file-foo" | ||||
|     } | ||||
|     aad     = "foobar" | ||||
|     dh_type = "curve25519" | ||||
|     dh_path = "/tmp/file-foo-dhpath" | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type        = "file" | ||||
|     wrap_ttl    = "5m" | ||||
|     aad_env_var = "TEST_AAD_ENV" | ||||
|     dh_type     = "curve25519" | ||||
|     dh_path     = "/tmp/file-foo-dhpath2" | ||||
|     derive_key  = true | ||||
|     config = { | ||||
|       path = "/tmp/file-bar" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "unix" { | ||||
|   address     = "/path/to/socket" | ||||
|   tls_disable = true | ||||
|  | ||||
|   agent_api { | ||||
|     enable_quit = true | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   address     = "2001:0db8::0001:8200" | ||||
|   tls_disable = true | ||||
| } | ||||
|  | ||||
| listener { | ||||
|   type        = "tcp" | ||||
|   address     = "[2001:0:0:1:0:0:0:1]:3000" | ||||
|   tls_disable = true | ||||
|   role        = "metrics_only" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   role          = "default" | ||||
|   address       = "2001:db8:0:1:1:1:1:1:8400" | ||||
|   tls_key_file  = "/path/to/cakey.pem" | ||||
|   tls_cert_file = "/path/to/cacert.pem" | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
| log_file = "/var/log/vault/vault-agent.log" | ||||
|  | ||||
| auto_auth { | ||||
| 	method "aws" { | ||||
| 		mount_path = "auth/aws" | ||||
| 		namespace = "my-namespace" | ||||
| 		config = { | ||||
| 			role = "foobar" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	sink "file" { | ||||
| 		config = { | ||||
| 			path = "/tmp/file-foo" | ||||
| 		} | ||||
| 		aad = "foobar" | ||||
| 		dh_type = "curve25519" | ||||
| 		dh_path = "/tmp/file-foo-dhpath" | ||||
| 	} | ||||
|  | ||||
| 	sink "file" { | ||||
| 		wrap_ttl = "5m"  | ||||
| 		aad_env_var = "TEST_AAD_ENV" | ||||
| 		dh_type = "curve25519" | ||||
| 		dh_path = "/tmp/file-foo-dhpath2" | ||||
| 		derive_key = true | ||||
| 		config = { | ||||
| 			path = "/tmp/file-bar" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
| log_file = "/var/log/vault/vault-agent.log" | ||||
|  | ||||
| auto_auth { | ||||
| 	method { | ||||
| 		type = "aws" | ||||
| 		namespace = "/my-namespace" | ||||
| 		config = { | ||||
| 			role = "foobar" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	sink { | ||||
| 		type = "file" | ||||
| 		config = { | ||||
| 			path = "/tmp/file-foo" | ||||
| 		} | ||||
| 		aad = "foobar" | ||||
| 		dh_type = "curve25519" | ||||
| 		dh_path = "/tmp/file-foo-dhpath" | ||||
| 	} | ||||
|  | ||||
| 	sink { | ||||
| 		type = "file" | ||||
| 		wrap_ttl = "5m"  | ||||
| 		aad_env_var = "TEST_AAD_ENV" | ||||
| 		dh_type = "curve25519" | ||||
| 		dh_path = "/tmp/file-foo-dhpath2" | ||||
| 		derive_key = true | ||||
| 		config = { | ||||
| 			path = "/tmp/file-bar" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -3569,6 +3569,113 @@ template { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestAgent_Config_AddrConformance verifies that the vault address is correctly | ||||
| // normalized to conform to RFC-5942 §4 when configured by a config file, | ||||
| // environment variables, or CLI flags. | ||||
| // See: https://rfc-editor.org/rfc/rfc5952.html | ||||
| func TestAgent_Config_AddrConformance(t *testing.T) { | ||||
| 	for name, test := range map[string]struct { | ||||
| 		args     []string | ||||
| 		envVars  map[string]string | ||||
| 		cfg      string | ||||
| 		expected *agentConfig.Config | ||||
| 	}{ | ||||
| 		"ipv4 config": { | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://127.0.0.1:8200" | ||||
| }`, | ||||
| 			expected: &agentConfig.Config{ | ||||
| 				Vault: &agentConfig.Vault{ | ||||
| 					Address: "https://127.0.0.1:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 config": { | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| }`, | ||||
| 			expected: &agentConfig.Config{ | ||||
| 				Vault: &agentConfig.Vault{ | ||||
| 					// Use the normalized version in the config | ||||
| 					Address: "https://[2001:db8::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 cli arg overrides": { | ||||
| 			args: []string{"-address=https://[2001:0:0:1:0:0:0:1]:8200"}, | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| }`, | ||||
| 			expected: &agentConfig.Config{ | ||||
| 				Vault: &agentConfig.Vault{ | ||||
| 					// Use a normalized version of the args address | ||||
| 					Address: "https://[2001:0:0:1::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 env var overrides": { | ||||
| 			envVars: map[string]string{ | ||||
| 				"VAULT_ADDR": "https://[2001:DB8:AC3:FE4::1]:8200", | ||||
| 			}, | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| }`, | ||||
| 			expected: &agentConfig.Config{ | ||||
| 				Vault: &agentConfig.Vault{ | ||||
| 					// Use a normalized version of the env var address | ||||
| 					Address: "https://[2001:db8:ac3:fe4::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 all uses cli overrides": { | ||||
| 			args: []string{"-address=https://[2001:0:0:1:0:0:0:1]:8200"}, | ||||
| 			envVars: map[string]string{ | ||||
| 				"VAULT_ADDR": "https://[2001:DB8:AC3:FE4::1]:8200", | ||||
| 			}, | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| }`, | ||||
| 			expected: &agentConfig.Config{ | ||||
| 				Vault: &agentConfig.Vault{ | ||||
| 					// Use a normalized version of the args address | ||||
| 					Address: "https://[2001:0:0:1::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			// In CI our tests are run with VAULT_ADDR=, which will break our tests | ||||
| 			// because it'll default to an unset address. Ensure that's cleared out | ||||
| 			// of the environment. | ||||
| 			t.Cleanup(func() { | ||||
| 				os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) | ||||
| 			}) | ||||
| 			os.Unsetenv(api.EnvVaultAddress) | ||||
| 			for k, v := range test.envVars { | ||||
| 				t.Setenv(k, v) | ||||
| 			} | ||||
|  | ||||
| 			configFile := populateTempFile(t, "agent-"+strings.ReplaceAll(name, " ", "-"), test.cfg) | ||||
| 			cfg, err := agentConfig.LoadConfigFile(configFile.Name()) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotEmptyf(t, cfg.Vault.Address, "agent config is missing address: %+v", cfg.Vault) | ||||
|  | ||||
| 			cmd := &AgentCommand{BaseCommand: &BaseCommand{}} | ||||
| 			f := cmd.Flags() | ||||
| 			args := append([]string{}, test.args...) | ||||
| 			require.NoError(t, f.Parse(args)) | ||||
|  | ||||
| 			cmd.applyConfigOverrides(f, cfg) | ||||
| 			require.Equalf(t, test.expected.Vault.Address, cfg.Vault.Address, "agent config is missing address: config: %+v, flags: %+v", cfg.Vault, f) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Get a randomly assigned port and then free it again before returning it. | ||||
| // There is still a race when trying to use it, but should work better | ||||
| // than a static port. | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import ( | ||||
| 	"github.com/hashicorp/vault/api/tokenhelper" | ||||
| 	"github.com/hashicorp/vault/command/config" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| 	"github.com/hashicorp/vault/internalshared/configutil" | ||||
| 	"github.com/mattn/go-isatty" | ||||
| 	"github.com/mitchellh/go-homedir" | ||||
| 	"github.com/pkg/errors" | ||||
| @@ -387,11 +388,12 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets { | ||||
| 			f := set.NewFlagSet("HTTP Options") | ||||
|  | ||||
| 			addrStringVar := &StringVar{ | ||||
| 				Name:       flagNameAddress, | ||||
| 				Target:     &c.flagAddress, | ||||
| 				EnvVar:     api.EnvVaultAddress, | ||||
| 				Completion: complete.PredictAnything, | ||||
| 				Usage:      "Address of the Vault server.", | ||||
| 				Name:        flagNameAddress, | ||||
| 				Target:      &c.flagAddress, | ||||
| 				EnvVar:      api.EnvVaultAddress, | ||||
| 				Completion:  complete.PredictAnything, | ||||
| 				Normalizers: []func(string) string{configutil.NormalizeAddr}, | ||||
| 				Usage:       "Address of the Vault server.", | ||||
| 			} | ||||
|  | ||||
| 			if c.flagAddress != "" { | ||||
| @@ -403,11 +405,12 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets { | ||||
| 			f.StringVar(addrStringVar) | ||||
|  | ||||
| 			agentAddrStringVar := &StringVar{ | ||||
| 				Name:       "agent-address", | ||||
| 				Target:     &c.flagAgentProxyAddress, | ||||
| 				EnvVar:     api.EnvVaultAgentAddr, | ||||
| 				Completion: complete.PredictAnything, | ||||
| 				Usage:      "Address of the Agent.", | ||||
| 				Name:        "agent-address", | ||||
| 				Target:      &c.flagAgentProxyAddress, | ||||
| 				EnvVar:      api.EnvVaultAgentAddr, | ||||
| 				Completion:  complete.PredictAnything, | ||||
| 				Normalizers: []func(string) string{configutil.NormalizeAddr}, | ||||
| 				Usage:       "Address of the Agent.", | ||||
| 			} | ||||
| 			f.StringVar(agentAddrStringVar) | ||||
|  | ||||
|   | ||||
| @@ -460,56 +460,89 @@ func (i *uint64Value) Hidden() bool     { return i.hidden } | ||||
|  | ||||
| // -- StringVar and stringValue | ||||
| type StringVar struct { | ||||
| 	Name       string | ||||
| 	Aliases    []string | ||||
| 	Usage      string | ||||
| 	Default    string | ||||
| 	Hidden     bool | ||||
| 	EnvVar     string | ||||
| 	Target     *string | ||||
| 	Completion complete.Predictor | ||||
| 	Name        string | ||||
| 	Aliases     []string | ||||
| 	Usage       string | ||||
| 	Default     string | ||||
| 	Hidden      bool | ||||
| 	EnvVar      string | ||||
| 	Target      *string | ||||
| 	Normalizers []func(string) string | ||||
| 	Completion  complete.Predictor | ||||
| } | ||||
|  | ||||
| func (s *StringVar) SetTarget(val string) { | ||||
| 	if s == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, f := range s.Normalizers { | ||||
| 		val = f(val) | ||||
| 	} | ||||
|  | ||||
| 	*s.Target = val | ||||
| } | ||||
|  | ||||
| func (f *FlagSet) StringVar(i *StringVar) { | ||||
| 	initial := i.Default | ||||
| 	if v, exist := os.LookupEnv(i.EnvVar); exist { | ||||
| 		initial = v | ||||
| 	if i == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	def := "" | ||||
| 	if i.Default != "" { | ||||
| 		def = i.Default | ||||
| 	val := i.Default | ||||
| 	envVar, ok := os.LookupEnv(i.EnvVar) | ||||
| 	if ok { | ||||
| 		val = envVar | ||||
| 	} | ||||
|  | ||||
| 	f.VarFlag(&VarFlag{ | ||||
| 		Name:       i.Name, | ||||
| 		Aliases:    i.Aliases, | ||||
| 		Usage:      i.Usage, | ||||
| 		Default:    def, | ||||
| 		Default:    i.Default, | ||||
| 		EnvVar:     i.EnvVar, | ||||
| 		Value:      newStringValue(initial, i.Target, i.Hidden), | ||||
| 		Value:      newStringValue(val, i.Target, i.Hidden, i.Normalizers), | ||||
| 		Completion: i.Completion, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| type stringValue struct { | ||||
| 	hidden bool | ||||
| 	target *string | ||||
| 	hidden      bool | ||||
| 	target      *string | ||||
| 	normalizers []func(string) string | ||||
| } | ||||
|  | ||||
| func newStringValue(def string, target *string, hidden bool) *stringValue { | ||||
| 	*target = def | ||||
| 	return &stringValue{ | ||||
| 		hidden: hidden, | ||||
| 		target: target, | ||||
| func newStringValue(val string, target *string, hidden bool, normalizers []func(string) string) *stringValue { | ||||
| 	sv := &stringValue{ | ||||
| 		hidden:      hidden, | ||||
| 		target:      target, | ||||
| 		normalizers: append(normalizers, strings.TrimSpace), | ||||
| 	} | ||||
| 	sv.set(val) | ||||
|  | ||||
| 	return sv | ||||
| } | ||||
|  | ||||
| func (s *stringValue) Set(val string) error { | ||||
| 	*s.target = val | ||||
| 	s.set(val) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *stringValue) set(val string) { | ||||
| 	*s.target = s.normalize(val) | ||||
| } | ||||
|  | ||||
| func (s *stringValue) normalize(in string) string { | ||||
| 	if s == nil || len(s.normalizers) < 1 { | ||||
| 		return in | ||||
| 	} | ||||
|  | ||||
| 	for _, f := range s.normalizers { | ||||
| 		in = f(in) | ||||
| 	} | ||||
|  | ||||
| 	return in | ||||
| } | ||||
|  | ||||
| func (s *stringValue) Get() interface{} { return *s.target } | ||||
| func (s *stringValue) String() string   { return *s.target } | ||||
| func (s *stringValue) Example() string  { return "string" } | ||||
|   | ||||
| @@ -24,60 +24,64 @@ func getDefaultCliHeaders(t *testing.T) http.Header { | ||||
| } | ||||
|  | ||||
| func TestClient_FlagHeader(t *testing.T) { | ||||
| 	defaultHeaders := getDefaultCliHeaders(t) | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	cases := []struct { | ||||
| 	cases := map[string]struct { | ||||
| 		Input map[string]string | ||||
| 		Valid bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 		"empty": { | ||||
| 			map[string]string{}, | ||||
| 			true, | ||||
| 		}, | ||||
| 		{ | ||||
| 		"valid": { | ||||
| 			map[string]string{"foo": "bar", "header2": "value2"}, | ||||
| 			true, | ||||
| 		}, | ||||
| 		{ | ||||
| 		"invalid": { | ||||
| 			map[string]string{"X-Vault-foo": "bar", "header2": "value2"}, | ||||
| 			false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range cases { | ||||
| 		expectedHeaders := defaultHeaders.Clone() | ||||
| 		for key, val := range tc.Input { | ||||
| 			expectedHeaders.Add(key, val) | ||||
| 		} | ||||
|  | ||||
| 		bc := &BaseCommand{flagHeader: tc.Input} | ||||
| 		cli, err := bc.Client() | ||||
|  | ||||
| 		if err == nil && !tc.Valid { | ||||
| 			t.Errorf("No error for input[%#v], but not valid", tc.Input) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			if tc.Valid { | ||||
| 				t.Errorf("Error[%v] with input[%#v], but valid", err, tc.Input) | ||||
| 	for name, tc := range cases { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
| 			expectedHeaders := getDefaultCliHeaders(t) | ||||
| 			for key, val := range tc.Input { | ||||
| 				expectedHeaders.Add(key, val) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if cli == nil { | ||||
| 			t.Error("client should not be nil") | ||||
| 		} | ||||
| 			bc := &BaseCommand{flagHeader: tc.Input} | ||||
| 			cli, err := bc.Client() | ||||
|  | ||||
| 		actualHeaders := cli.Headers() | ||||
| 		if !reflect.DeepEqual(expectedHeaders, actualHeaders) { | ||||
| 			t.Errorf("expected [%#v] but got [%#v]", expectedHeaders, actualHeaders) | ||||
| 		} | ||||
| 			if err == nil && !tc.Valid { | ||||
| 				t.Errorf("No error for input[%#v], but not valid", tc.Input) | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				if tc.Valid { | ||||
| 					t.Errorf("Error[%v] with input[%#v], but valid", err, tc.Input) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			if cli == nil { | ||||
| 				t.Error("client should not be nil") | ||||
| 			} | ||||
|  | ||||
| 			actualHeaders := cli.Headers() | ||||
| 			if !reflect.DeepEqual(expectedHeaders, actualHeaders) { | ||||
| 				t.Errorf("expected [%#v] but got [%#v]", expectedHeaders, actualHeaders) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestClient_HCPConfiguration tests that the HCP configuration is applied correctly when it exists in cache. | ||||
| func TestClient_HCPConfiguration(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	cases := map[string]struct { | ||||
| 		Valid        bool | ||||
| 		ExpectedAddr string | ||||
| @@ -94,7 +98,8 @@ func TestClient_HCPConfiguration(t *testing.T) { | ||||
|  | ||||
| 	for n, tst := range cases { | ||||
| 		t.Run(n, func(t *testing.T) { | ||||
| 			bc := &BaseCommand{hcpTokenHelper: &hcpvlib.TestingHCPTokenHelper{tst.Valid}} | ||||
| 			t.Parallel() | ||||
| 			bc := &BaseCommand{hcpTokenHelper: &hcpvlib.TestingHCPTokenHelper{ValidCache: tst.Valid}} | ||||
| 			cli, err := bc.Client() | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| @@ -109,3 +114,80 @@ func TestClient_HCPConfiguration(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test_FlagSet_StringVar_Normalizers verifies that the normalizer callbacks | ||||
| // works as expected. | ||||
| func Test_FlagSet_StringVar_Normalizers(t *testing.T) { | ||||
| 	appendA := func(in string) string { return in + "a" } | ||||
| 	prependB := func(in string) string { return "b" + in } | ||||
|  | ||||
| 	for name, test := range map[string]struct { | ||||
| 		in            func() *StringVar | ||||
| 		envVars       map[string]string | ||||
| 		expectedValue string | ||||
| 	}{ | ||||
| 		"no normalizers no env vars uses default value": { | ||||
| 			in: func() *StringVar { | ||||
| 				resT := "" | ||||
| 				return &StringVar{ | ||||
| 					Name:    "test", | ||||
| 					Target:  &resT, | ||||
| 					EnvVar:  "VAULT_TEST", | ||||
| 					Default: "default", | ||||
| 				} | ||||
| 			}, | ||||
| 			expectedValue: "default", | ||||
| 		}, | ||||
| 		"one normalizer no env vars normalizes default value": { | ||||
| 			in: func() *StringVar { | ||||
| 				resT := "" | ||||
| 				return &StringVar{ | ||||
| 					Name:        "test", | ||||
| 					Target:      &resT, | ||||
| 					EnvVar:      "VAULT_TEST", | ||||
| 					Default:     "default", | ||||
| 					Normalizers: []func(string) string{appendA}, | ||||
| 				} | ||||
| 			}, | ||||
| 			expectedValue: "defaulta", | ||||
| 		}, | ||||
| 		"two normalizers no env vars normalizes default value with both": { | ||||
| 			in: func() *StringVar { | ||||
| 				resT := "" | ||||
| 				return &StringVar{ | ||||
| 					Name:        "test", | ||||
| 					Target:      &resT, | ||||
| 					EnvVar:      "VAULT_TEST", | ||||
| 					Default:     "default", | ||||
| 					Normalizers: []func(string) string{appendA, prependB}, | ||||
| 				} | ||||
| 			}, | ||||
| 			expectedValue: "bdefaulta", | ||||
| 		}, | ||||
| 		"two normalizers with env vars normalizes env var value with both": { | ||||
| 			in: func() *StringVar { | ||||
| 				resT := "" | ||||
| 				return &StringVar{ | ||||
| 					Name:        "test", | ||||
| 					Target:      &resT, | ||||
| 					EnvVar:      "VAULT_TEST", | ||||
| 					Default:     "default", | ||||
| 					Normalizers: []func(string) string{appendA, prependB}, | ||||
| 				} | ||||
| 			}, | ||||
| 			envVars:       map[string]string{"VAULT_TEST": "env_override"}, | ||||
| 			expectedValue: "benv_overridea", | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			for k, v := range test.envVars { | ||||
| 				t.Setenv(k, v) | ||||
| 			} | ||||
| 			fsets := NewFlagSets(nil) | ||||
| 			fs := fsets.NewFlagSet("test") | ||||
| 			sv := test.in() | ||||
| 			fs.StringVar(sv) | ||||
| 			require.Equal(t, test.expectedValue, *sv.Target) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import ( | ||||
| 	"github.com/hashicorp/go-secure-stdlib/strutil" | ||||
| 	"github.com/hashicorp/vault/api" | ||||
| 	"github.com/hashicorp/vault/helper/osutil" | ||||
| 	"github.com/hashicorp/vault/internalshared/configutil" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/jsonutil" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/logging" | ||||
| 	"github.com/hashicorp/vault/version" | ||||
| @@ -211,7 +212,7 @@ Usage: vault debug [options] | ||||
|   output. The command uses the Vault address and token as specified via | ||||
|   the login command, environment variables, or CLI flags. | ||||
|  | ||||
|   To create a debug package using default duration and interval values in the  | ||||
|   To create a debug package using default duration and interval values in the | ||||
|   current directory that captures all applicable targets: | ||||
|  | ||||
|   $ vault debug | ||||
| @@ -495,7 +496,7 @@ func (c *DebugCommand) preflight(rawArgs []string) (string, error) { | ||||
|  | ||||
| 	// Populate initial index fields | ||||
| 	c.debugIndex = &debugIndex{ | ||||
| 		VaultAddress:           client.Address(), | ||||
| 		VaultAddress:           configutil.NormalizeAddr(client.Address()), | ||||
| 		ClientVersion:          version.GetVersion().VersionNumber(), | ||||
| 		ServerVersion:          serverHealth.Version, | ||||
| 		Compress:               c.flagCompress, | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import ( | ||||
|  | ||||
| 	"github.com/hashicorp/cli" | ||||
| 	"github.com/hashicorp/vault/api" | ||||
| 	"github.com/hashicorp/vault/internalshared/configutil" | ||||
| 	"github.com/posener/complete" | ||||
| ) | ||||
|  | ||||
| @@ -45,7 +46,7 @@ Usage: vault operator raft join [options] <leader-api-addr|auto-join-configurati | ||||
|   configuration. | ||||
|  | ||||
|       $ vault operator raft join "provider=aws region=eu-west-1 ..." | ||||
| 			 | ||||
|  | ||||
|   Join the current node as a peer to the Raft cluster by providing cloud auto-join | ||||
|   configuration with an explicit URI scheme and port. | ||||
|  | ||||
| @@ -195,7 +196,7 @@ func (c *OperatorRaftJoinCommand) Run(args []string) int { | ||||
| 		joinReq.AutoJoinScheme = c.flagAutoJoinScheme | ||||
| 		joinReq.AutoJoinPort = c.flagAutoJoinPort | ||||
| 	} else { | ||||
| 		joinReq.LeaderAPIAddr = leaderInfo | ||||
| 		joinReq.LeaderAPIAddr = configutil.NormalizeAddr(leaderInfo) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := client.Sys().RaftJoin(joinReq) | ||||
|   | ||||
| @@ -865,10 +865,11 @@ func (c *ProxyCommand) applyConfigOverrides(f *FlagSets, config *proxyConfig.Con | ||||
| 	}) | ||||
|  | ||||
| 	c.setStringFlag(f, config.Vault.Address, &StringVar{ | ||||
| 		Name:    flagNameAddress, | ||||
| 		Target:  &c.flagAddress, | ||||
| 		Default: "https://127.0.0.1:8200", | ||||
| 		EnvVar:  api.EnvVaultAddress, | ||||
| 		Name:        flagNameAddress, | ||||
| 		Target:      &c.flagAddress, | ||||
| 		Default:     "https://127.0.0.1:8200", | ||||
| 		EnvVar:      api.EnvVaultAddress, | ||||
| 		Normalizers: []func(string) string{configutil.NormalizeAddr}, | ||||
| 	}) | ||||
| 	config.Vault.Address = c.flagAddress | ||||
| 	c.setStringFlag(f, config.Vault.CACert, &StringVar{ | ||||
| @@ -942,13 +943,13 @@ func (c *ProxyCommand) setStringFlag(f *FlagSets, configVal string, fVar *String | ||||
| 		// Don't do anything as the flag is already set from the command line | ||||
| 	case flagEnvSet: | ||||
| 		// Use value from env var | ||||
| 		*fVar.Target = flagEnvValue | ||||
| 		fVar.SetTarget(flagEnvValue) | ||||
| 	case configVal != "": | ||||
| 		// Use value from config | ||||
| 		*fVar.Target = configVal | ||||
| 		fVar.SetTarget(configVal) | ||||
| 	default: | ||||
| 		// Use the default value | ||||
| 		*fVar.Target = fVar.Default | ||||
| 		fVar.SetTarget(fVar.Default) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import ( | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"slices" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @@ -517,6 +518,10 @@ func parseVault(result *Config, list *ast.ObjectList) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if v.Address != "" { | ||||
| 		v.Address = configutil.NormalizeAddr(v.Address) | ||||
| 	} | ||||
|  | ||||
| 	if v.TLSSkipVerifyRaw != nil { | ||||
| 		v.TLSSkipVerify, err = parseutil.ParseBool(v.TLSSkipVerifyRaw) | ||||
| 		if err != nil { | ||||
| @@ -793,10 +798,63 @@ func parseMethod(result *Config, list *ast.ObjectList) error { | ||||
| 	// Canonicalize namespace path if provided | ||||
| 	m.Namespace = namespace.Canonicalize(m.Namespace) | ||||
|  | ||||
| 	// Normalize any configuration addresses | ||||
| 	if len(m.Config) > 0 { | ||||
| 		var err error | ||||
| 		for k, v := range m.Config { | ||||
| 			vStr, ok := v.(string) | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			m.Config[k], err = normalizeAutoAuthMethod(m.Type, k, vStr) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	result.AutoAuth.Method = &m | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // autoAuthMethodKeys maps an auto-auth method type to its associated | ||||
| // configuration whose values are URLs, IP addresses, or host:port style | ||||
| // addresses. All auto-auth types must have an entry in this map, otherwise our | ||||
| // normalization check will fail when parsing the storage entry config. | ||||
| // Auto-auth method types which don't contain such keys should include an empty | ||||
| // array. | ||||
| var autoAuthMethodKeys = map[string][]string{ | ||||
| 	"alicloud":   {""}, | ||||
| 	"approle":    {""}, | ||||
| 	"aws":        {""}, | ||||
| 	"azure":      {"resource"}, | ||||
| 	"cert":       {""}, | ||||
| 	"cf":         {""}, | ||||
| 	"gcp":        {"service_account"}, | ||||
| 	"jwt":        {""}, | ||||
| 	"ldap":       {""}, | ||||
| 	"kerberos":   {""}, | ||||
| 	"kubernetes": {""}, | ||||
| 	"oci":        {""}, | ||||
| 	"token_file": {""}, | ||||
| } | ||||
|  | ||||
| // normalizeAutoAuthMethod takes a storage name, a configuration key | ||||
| // and it's associated value and will normalize any URLs, IP addresses, or | ||||
| // host:port style addresses. | ||||
| func normalizeAutoAuthMethod(method string, key string, value string) (string, error) { | ||||
| 	keys, ok := autoAuthMethodKeys[method] | ||||
| 	if !ok { | ||||
| 		return "", fmt.Errorf("unknown auto-auth method type %s", method) | ||||
| 	} | ||||
|  | ||||
| 	if slices.Contains(keys, key) { | ||||
| 		return configutil.NormalizeAddr(value), nil | ||||
| 	} | ||||
|  | ||||
| 	return value, nil | ||||
| } | ||||
|  | ||||
| func parseSinks(result *Config, list *ast.ObjectList) error { | ||||
| 	name := "sink" | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import ( | ||||
| 	"github.com/go-test/deep" | ||||
| 	"github.com/hashicorp/vault/command/agentproxyshared" | ||||
| 	"github.com/hashicorp/vault/internalshared/configutil" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| // TestLoadConfigFile_ProxyCache tests loading a config file containing a cache | ||||
| @@ -203,3 +204,126 @@ func TestLoadConfigFile_ProxyCacheStaticSecrets(t *testing.T) { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test_LoadConfigFile_AutoAuth_AddrConformance verifies basic config file | ||||
| // loading in addition to RFC-5942 §4 normalization of auto-auth methods. | ||||
| // See: https://rfc-editor.org/rfc/rfc5952.html | ||||
| func Test_LoadConfigFile_AutoAuth_AddrConformance(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	for name, method := range map[string]*Method{ | ||||
| 		"aws": { | ||||
| 			Type:      "aws", | ||||
| 			MountPath: "auth/aws", | ||||
| 			Namespace: "aws-namespace/", | ||||
| 			Config: map[string]any{ | ||||
| 				"role": "foobar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"azure": { | ||||
| 			Type:      "azure", | ||||
| 			MountPath: "auth/azure", | ||||
| 			Namespace: "azure-namespace/", | ||||
| 			Config: map[string]any{ | ||||
| 				"authenticate_from_environment": true, | ||||
| 				"role":                          "dev-role", | ||||
| 				"resource":                      "https://[2001:0:0:1::1]", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"gcp": { | ||||
| 			Type:      "gcp", | ||||
| 			MountPath: "auth/gcp", | ||||
| 			Namespace: "gcp-namespace/", | ||||
| 			Config: map[string]any{ | ||||
| 				"role":            "dev-role", | ||||
| 				"service_account": "https://[2001:db8:ac3:fe4::1]", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
|  | ||||
| 			config, err := LoadConfigFile("./test-fixtures/config-auto-auth-" + name + ".hcl") | ||||
| 			require.NoError(t, err) | ||||
|  | ||||
| 			expected := &Config{ | ||||
| 				SharedConfig: &configutil.SharedConfig{ | ||||
| 					PidFile: "./pidfile", | ||||
| 					Listeners: []*configutil.Listener{ | ||||
| 						{ | ||||
| 							Type:        "unix", | ||||
| 							Address:     "/path/to/socket", | ||||
| 							TLSDisable:  true, | ||||
| 							SocketMode:  "configmode", | ||||
| 							SocketUser:  "configuser", | ||||
| 							SocketGroup: "configgroup", | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:       "tcp", | ||||
| 							Address:    "2001:db8::1:8200", // Normalized | ||||
| 							TLSDisable: true, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:       "tcp", | ||||
| 							Address:    "[2001:0:0:1::1]:3000", // Normalized | ||||
| 							Role:       "metrics_only", | ||||
| 							TLSDisable: true, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Type:        "tcp", | ||||
| 							Role:        "default", | ||||
| 							Address:     "2001:db8:0:1:1:1:1:1:8400", // Normalized | ||||
| 							TLSKeyFile:  "/path/to/cakey.pem", | ||||
| 							TLSCertFile: "/path/to/cacert.pem", | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				Vault: &Vault{ | ||||
| 					Address:          "https://[2001:db8::1]:8200", // Normalized | ||||
| 					CACert:           "config_ca_cert", | ||||
| 					CAPath:           "config_ca_path", | ||||
| 					TLSSkipVerifyRaw: interface{}("true"), | ||||
| 					TLSSkipVerify:    true, | ||||
| 					ClientCert:       "config_client_cert", | ||||
| 					ClientKey:        "config_client_key", | ||||
| 					Retry: &Retry{ | ||||
| 						NumRetries: 12, | ||||
| 					}, | ||||
| 				}, | ||||
| 				AutoAuth: &AutoAuth{ | ||||
| 					Method: method, // Method properties are normalized correctly | ||||
| 					Sinks: []*Sink{ | ||||
| 						{ | ||||
| 							Type:   "file", | ||||
| 							DHType: "curve25519", | ||||
| 							DHPath: "/tmp/file-foo-dhpath", | ||||
| 							AAD:    "foobar", | ||||
| 							Config: map[string]interface{}{ | ||||
| 								"path": "/tmp/file-foo", | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				APIProxy: &APIProxy{ | ||||
| 					EnforceConsistency:  "always", | ||||
| 					WhenInconsistent:    "retry", | ||||
| 					UseAutoAuthTokenRaw: true, | ||||
| 					UseAutoAuthToken:    true, | ||||
| 					ForceAutoAuthToken:  false, | ||||
| 				}, | ||||
| 				Cache: &Cache{ | ||||
| 					Persist: &agentproxyshared.PersistConfig{ | ||||
| 						Type:                    "kubernetes", | ||||
| 						Path:                    "/vault/agent-cache/", | ||||
| 						KeepAfterImport:         true, | ||||
| 						ExitOnErr:               true, | ||||
| 						ServiceAccountTokenFile: "/tmp/serviceaccount/token", | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			config.Prune() | ||||
| 			require.EqualValues(t, expected, config) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										76
									
								
								command/proxy/config/test-fixtures/config-auto-auth-aws.hcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								command/proxy/config/test-fixtures/config-auto-auth-aws.hcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
|  | ||||
| auto_auth { | ||||
|   method { | ||||
|     type      = "aws" | ||||
|     namespace = "/aws-namespace" | ||||
|     config = { | ||||
|       role = "foobar" | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type = "file" | ||||
|     config = { | ||||
|       path = "/tmp/file-foo" | ||||
|     } | ||||
|     aad     = "foobar" | ||||
|     dh_type = "curve25519" | ||||
|     dh_path = "/tmp/file-foo-dhpath" | ||||
|   } | ||||
| } | ||||
|  | ||||
| api_proxy { | ||||
|   use_auto_auth_token = true | ||||
|   enforce_consistency = "always" | ||||
|   when_inconsistent   = "retry" | ||||
| } | ||||
|  | ||||
| cache { | ||||
|   persist = { | ||||
|     type                       = "kubernetes" | ||||
|     path                       = "/vault/agent-cache/" | ||||
|     keep_after_import          = true | ||||
|     exit_on_err                = true | ||||
|     service_account_token_file = "/tmp/serviceaccount/token" | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "unix" { | ||||
|   address      = "/path/to/socket" | ||||
|   tls_disable  = true | ||||
|   socket_mode  = "configmode" | ||||
|   socket_user  = "configuser" | ||||
|   socket_group = "configgroup" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   address     = "2001:0db8::0001:8200" | ||||
|   tls_disable = true | ||||
| } | ||||
|  | ||||
| listener { | ||||
|   type        = "tcp" | ||||
|   address     = "[2001:0:0:1:0:0:0:1]:3000" | ||||
|   tls_disable = true | ||||
|   role        = "metrics_only" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   role          = "default" | ||||
|   address       = "2001:db8:0:1:1:1:1:1:8400" | ||||
|   tls_key_file  = "/path/to/cakey.pem" | ||||
|   tls_cert_file = "/path/to/cacert.pem" | ||||
| } | ||||
|  | ||||
| vault { | ||||
|   address         = "https://[2001:0db8::0001]:8200" | ||||
|   ca_cert         = "config_ca_cert" | ||||
|   ca_path         = "config_ca_path" | ||||
|   tls_skip_verify = "true" | ||||
|   client_cert     = "config_client_cert" | ||||
|   client_key      = "config_client_key" | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
|  | ||||
| auto_auth { | ||||
|   method { | ||||
|     type      = "azure" | ||||
|     namespace = "/azure-namespace" | ||||
|     config = { | ||||
|       authenticate_from_environment = true | ||||
|       role                          = "dev-role" | ||||
|       resource                      = "https://[2001:0:0:1:0:0:0:1]", | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type = "file" | ||||
|     config = { | ||||
|       path = "/tmp/file-foo" | ||||
|     } | ||||
|     aad     = "foobar" | ||||
|     dh_type = "curve25519" | ||||
|     dh_path = "/tmp/file-foo-dhpath" | ||||
|   } | ||||
| } | ||||
|  | ||||
| api_proxy { | ||||
|   use_auto_auth_token = true | ||||
|   enforce_consistency = "always" | ||||
|   when_inconsistent   = "retry" | ||||
| } | ||||
|  | ||||
| cache { | ||||
|   persist = { | ||||
|     type                       = "kubernetes" | ||||
|     path                       = "/vault/agent-cache/" | ||||
|     keep_after_import          = true | ||||
|     exit_on_err                = true | ||||
|     service_account_token_file = "/tmp/serviceaccount/token" | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "unix" { | ||||
|   address      = "/path/to/socket" | ||||
|   tls_disable  = true | ||||
|   socket_mode  = "configmode" | ||||
|   socket_user  = "configuser" | ||||
|   socket_group = "configgroup" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   address     = "2001:0db8::0001:8200" | ||||
|   tls_disable = true | ||||
| } | ||||
|  | ||||
| listener { | ||||
|   type        = "tcp" | ||||
|   address     = "[2001:0:0:1:0:0:0:1]:3000" | ||||
|   tls_disable = true | ||||
|   role        = "metrics_only" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   role          = "default" | ||||
|   address       = "2001:db8:0:1:1:1:1:1:8400" | ||||
|   tls_key_file  = "/path/to/cakey.pem" | ||||
|   tls_cert_file = "/path/to/cacert.pem" | ||||
| } | ||||
|  | ||||
| vault { | ||||
|   address         = "https://[2001:0db8::0001]:8200" | ||||
|   ca_cert         = "config_ca_cert" | ||||
|   ca_path         = "config_ca_path" | ||||
|   tls_skip_verify = "true" | ||||
|   client_cert     = "config_client_cert" | ||||
|   client_key      = "config_client_key" | ||||
| } | ||||
							
								
								
									
										77
									
								
								command/proxy/config/test-fixtures/config-auto-auth-gcp.hcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								command/proxy/config/test-fixtures/config-auto-auth-gcp.hcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| # Copyright (c) HashiCorp, Inc. | ||||
| # SPDX-License-Identifier: BUSL-1.1 | ||||
|  | ||||
| pid_file = "./pidfile" | ||||
|  | ||||
| auto_auth { | ||||
|   method { | ||||
|     type      = "gcp" | ||||
|     namespace = "/gcp-namespace" | ||||
|     config = { | ||||
|       role            = "dev-role" | ||||
|       service_account = "https://[2001:DB8:AC3:FE4::1]" | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sink { | ||||
|     type = "file" | ||||
|     config = { | ||||
|       path = "/tmp/file-foo" | ||||
|     } | ||||
|     aad     = "foobar" | ||||
|     dh_type = "curve25519" | ||||
|     dh_path = "/tmp/file-foo-dhpath" | ||||
|   } | ||||
| } | ||||
|  | ||||
| api_proxy { | ||||
|   use_auto_auth_token = true | ||||
|   enforce_consistency = "always" | ||||
|   when_inconsistent   = "retry" | ||||
| } | ||||
|  | ||||
| cache { | ||||
|   persist = { | ||||
|     type                       = "kubernetes" | ||||
|     path                       = "/vault/agent-cache/" | ||||
|     keep_after_import          = true | ||||
|     exit_on_err                = true | ||||
|     service_account_token_file = "/tmp/serviceaccount/token" | ||||
|   } | ||||
| } | ||||
|  | ||||
| listener "unix" { | ||||
|   address      = "/path/to/socket" | ||||
|   tls_disable  = true | ||||
|   socket_mode  = "configmode" | ||||
|   socket_user  = "configuser" | ||||
|   socket_group = "configgroup" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   address     = "2001:0db8::0001:8200" | ||||
|   tls_disable = true | ||||
| } | ||||
|  | ||||
| listener { | ||||
|   type        = "tcp" | ||||
|   address     = "[2001:0:0:1:0:0:0:1]:3000" | ||||
|   tls_disable = true | ||||
|   role        = "metrics_only" | ||||
| } | ||||
|  | ||||
| listener "tcp" { | ||||
|   role          = "default" | ||||
|   address       = "2001:db8:0:1:1:1:1:1:8400" | ||||
|   tls_key_file  = "/path/to/cakey.pem" | ||||
|   tls_cert_file = "/path/to/cacert.pem" | ||||
| } | ||||
|  | ||||
| vault { | ||||
|   address         = "https://[2001:0db8::0001]:8200" | ||||
|   ca_cert         = "config_ca_cert" | ||||
|   ca_path         = "config_ca_path" | ||||
|   tls_skip_verify = "true" | ||||
|   client_cert     = "config_client_cert" | ||||
|   client_key      = "config_client_key" | ||||
| } | ||||
| @@ -2196,3 +2196,115 @@ func TestProxy_Config_ReloadTls(t *testing.T) { | ||||
| 		t.Fatalf("got a non-zero exit status: %d, stdout/stderr: %s", code, output) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestProxy_Config_AddrConformance verifies that the vault address is correctly | ||||
| // normalized to conform to RFC-5942 §4 when configured by a config file, | ||||
| // environment variables, or CLI flags. | ||||
| // See: https://rfc-editor.org/rfc/rfc5952.html | ||||
| func TestProxy_Config_AddrConformance(t *testing.T) { | ||||
| 	for name, test := range map[string]struct { | ||||
| 		args     []string | ||||
| 		envVars  map[string]string | ||||
| 		cfg      string | ||||
| 		expected *proxyConfig.Config | ||||
| 	}{ | ||||
| 		"ipv4 config": { | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://127.0.0.1:8200" | ||||
| } | ||||
| `, | ||||
| 			expected: &proxyConfig.Config{ | ||||
| 				Vault: &proxyConfig.Vault{ | ||||
| 					Address: "https://127.0.0.1:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 config": { | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
| `, | ||||
| 			expected: &proxyConfig.Config{ | ||||
| 				Vault: &proxyConfig.Vault{ | ||||
| 					// Use the normalized version in the config | ||||
| 					Address: "https://[2001:db8::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 cli arg overrides": { | ||||
| 			args: []string{"-address=https://[2001:0:0:1:0:0:0:1]:8200"}, | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
| `, | ||||
| 			expected: &proxyConfig.Config{ | ||||
| 				Vault: &proxyConfig.Vault{ | ||||
| 					// Use a normalized version of the args address | ||||
| 					Address: "https://[2001:0:0:1::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 env var overrides": { | ||||
| 			envVars: map[string]string{ | ||||
| 				"VAULT_ADDR": "https://[2001:DB8:AC3:FE4::1]:8200", | ||||
| 			}, | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
| `, | ||||
| 			expected: &proxyConfig.Config{ | ||||
| 				Vault: &proxyConfig.Vault{ | ||||
| 					// Use a normalized version of the env var address | ||||
| 					Address: "https://[2001:db8:ac3:fe4::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"ipv6 all uses cli overrides": { | ||||
| 			args: []string{"-address=https://[2001:0:0:1:0:0:0:1]:8200"}, | ||||
| 			envVars: map[string]string{ | ||||
| 				"VAULT_ADDR": "https://[2001:DB8:AC3:FE4::1]:8200", | ||||
| 			}, | ||||
| 			cfg: ` | ||||
| vault { | ||||
|   address = "https://[2001:0db8::0001]:8200" | ||||
| } | ||||
| `, | ||||
| 			expected: &proxyConfig.Config{ | ||||
| 				Vault: &proxyConfig.Vault{ | ||||
| 					// Use a normalized version of the args address | ||||
| 					Address: "https://[2001:0:0:1::1]:8200", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			// In CI our tests are run with VAULT_ADDR=, which will break our tests | ||||
| 			// because it'll default to an unset address. Ensure that's cleared out | ||||
| 			// of the environment. | ||||
| 			t.Cleanup(func() { | ||||
| 				os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) | ||||
| 			}) | ||||
| 			os.Unsetenv(api.EnvVaultAddress) | ||||
| 			for k, v := range test.envVars { | ||||
| 				t.Setenv(k, v) | ||||
| 			} | ||||
|  | ||||
| 			configFile := populateTempFile(t, "proxy-"+strings.ReplaceAll(name, " ", "-"), test.cfg) | ||||
| 			cfg, err := proxyConfig.LoadConfigFile(configFile.Name()) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotEmptyf(t, cfg.Vault.Address, "proxy config is missing address: %+v", cfg.Vault) | ||||
|  | ||||
| 			cmd := &ProxyCommand{BaseCommand: &BaseCommand{}} | ||||
| 			f := cmd.Flags() | ||||
| 			args := append([]string{}, test.args...) | ||||
| 			require.NoError(t, f.Parse(args)) | ||||
|  | ||||
| 			cmd.applyConfigOverrides(f, cfg) | ||||
| 			require.Equalf(t, test.expected.Vault.Address, cfg.Vault.Address, "proxy config is missing address: config: %+v, flags: %+v", cfg.Vault, f) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -278,11 +278,12 @@ func (c *ServerCommand) Flags() *FlagSets { | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:    "dev-listen-address", | ||||
| 		Target:  &c.flagDevListenAddr, | ||||
| 		Default: "127.0.0.1:8200", | ||||
| 		EnvVar:  "VAULT_DEV_LISTEN_ADDRESS", | ||||
| 		Usage:   "Address to bind to in \"dev\" mode.", | ||||
| 		Name:        "dev-listen-address", | ||||
| 		Target:      &c.flagDevListenAddr, | ||||
| 		Default:     "127.0.0.1:8200", | ||||
| 		EnvVar:      "VAULT_DEV_LISTEN_ADDRESS", | ||||
| 		Usage:       "Address to bind to in \"dev\" mode.", | ||||
| 		Normalizers: []func(string) string{configutil.NormalizeAddr}, | ||||
| 	}) | ||||
| 	f.BoolVar(&BoolVar{ | ||||
| 		Name:    "dev-no-store-token", | ||||
| @@ -798,7 +799,7 @@ func (c *ServerCommand) setupStorage(config *server.Config) (physical.Backend, e | ||||
| 		} | ||||
| 	case storageTypeRaft: | ||||
| 		if envCA := os.Getenv("VAULT_CLUSTER_ADDR"); envCA != "" { | ||||
| 			config.ClusterAddr = envCA | ||||
| 			config.ClusterAddr = configutil.NormalizeAddr(envCA) | ||||
| 		} | ||||
| 		if len(config.ClusterAddr) == 0 { | ||||
| 			return nil, errors.New("Cluster address must be set when using raft storage") | ||||
|   | ||||
| @@ -993,11 +993,11 @@ func ParseStorage(result *Config, list *ast.ObjectList, name string) error { | ||||
|  | ||||
| 	// Override with top-level values if they are set | ||||
| 	if result.APIAddr != "" { | ||||
| 		redirectAddr = result.APIAddr | ||||
| 		redirectAddr = configutil.NormalizeAddr(result.APIAddr) | ||||
| 	} | ||||
|  | ||||
| 	if result.ClusterAddr != "" { | ||||
| 		clusterAddr = result.ClusterAddr | ||||
| 		clusterAddr = configutil.NormalizeAddr(result.ClusterAddr) | ||||
| 	} | ||||
|  | ||||
| 	if result.DisableClusteringRaw != nil { | ||||
| @@ -1205,11 +1205,11 @@ func parseHAStorage(result *Config, list *ast.ObjectList, name string) error { | ||||
|  | ||||
| 	// Override with top-level values if they are set | ||||
| 	if result.APIAddr != "" { | ||||
| 		redirectAddr = result.APIAddr | ||||
| 		redirectAddr = configutil.NormalizeAddr(result.APIAddr) | ||||
| 	} | ||||
|  | ||||
| 	if result.ClusterAddr != "" { | ||||
| 		clusterAddr = result.ClusterAddr | ||||
| 		clusterAddr = configutil.NormalizeAddr(result.ClusterAddr) | ||||
| 	} | ||||
|  | ||||
| 	if result.DisableClusteringRaw != nil { | ||||
|   | ||||
| @@ -1247,13 +1247,13 @@ storage "cockroachdb" { | ||||
| 		"consul": { | ||||
| 			config: ` | ||||
| storage "consul" { | ||||
|   address = "2001:db8:0:0:0:0:2:1:8500" | ||||
|   address = "[2001:db8:0:0:0:0:2:1]:8500" | ||||
|   path    = "vault/" | ||||
| }`, | ||||
| 			expected: &Storage{ | ||||
| 				Type: "consul", | ||||
| 				Config: map[string]string{ | ||||
| 					"address": "2001:db8::2:1:8500", | ||||
| 					"address": "[2001:db8::2:1]:8500", | ||||
| 					"path":    "vault/", | ||||
| 				}, | ||||
| 			}, | ||||
| @@ -1359,7 +1359,7 @@ storage "mssql" { | ||||
| 		"mysql": { | ||||
| 			config: ` | ||||
| storage "mysql" { | ||||
| 	address  = "2001:db8:0:0:0:0:2:1:3306" | ||||
| 	address  = "[2001:db8:0:0:0:0:2:1]:3306" | ||||
|   username = "user1234" | ||||
|   password = "secret123!" | ||||
|   database = "vault" | ||||
| @@ -1367,7 +1367,7 @@ storage "mysql" { | ||||
| 			expected: &Storage{ | ||||
| 				Type: "mysql", | ||||
| 				Config: map[string]string{ | ||||
| 					"address":  "2001:db8::2:1:3306", | ||||
| 					"address":  "[2001:db8::2:1]:3306", | ||||
| 					"username": "user1234", | ||||
| 					"password": "secret123!", | ||||
| 					"database": "vault", | ||||
| @@ -1429,13 +1429,13 @@ storage "swift" { | ||||
| 		"zookeeper": { | ||||
| 			config: ` | ||||
| storage "zookeeper" { | ||||
| 	address = "2001:db8:0:0:0:0:2:1:2181" | ||||
| 	address = "[2001:db8:0:0:0:0:2:1]:2181" | ||||
|   path    = "vault/" | ||||
| }`, | ||||
| 			expected: &Storage{ | ||||
| 				Type: "zookeeper", | ||||
| 				Config: map[string]string{ | ||||
| 					"address": "2001:db8::2:1:2181", | ||||
| 					"address": "[2001:db8::2:1]:2181", | ||||
| 					"path":    "vault/", | ||||
| 				}, | ||||
| 			}, | ||||
|   | ||||
| @@ -19,7 +19,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| func tcpListenerFactory(l *configutil.Listener, _ io.Writer, ui cli.Ui) (net.Listener, map[string]string, reloadutil.ReloadFunc, error) { | ||||
| 	addr := l.Address | ||||
| 	addr := configutil.NormalizeAddr(l.Address) | ||||
| 	if addr == "" { | ||||
| 		addr = "127.0.0.1:8200" | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Ryan Cragun
					Ryan Cragun