Skip to content

Commit 58a49e6

Browse files
authored
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]: #29228
1 parent 6964612 commit 58a49e6

32 files changed

+1462
-303
lines changed

changelog/29517.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
```release-note:change
2+
agent/config: Configuration values including IPv6 addresses will be automatically translated and displayed conformant to RFC-5952 §4.
3+
```
4+
```release-note:change
5+
proxy/config: Configuration values including IPv6 addresses will be automatically translated and displayed conformant to RFC-5952 §4.
6+
```

command/agent.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -939,10 +939,11 @@ func (c *AgentCommand) applyConfigOverrides(f *FlagSets, config *agentConfig.Con
939939
})
940940

941941
c.setStringFlag(f, config.Vault.Address, &StringVar{
942-
Name: flagNameAddress,
943-
Target: &c.flagAddress,
944-
Default: "https://127.0.0.1:8200",
945-
EnvVar: api.EnvVaultAddress,
942+
Name: flagNameAddress,
943+
Target: &c.flagAddress,
944+
Default: "https://127.0.0.1:8200",
945+
EnvVar: api.EnvVaultAddress,
946+
Normalizers: []func(string) string{configutil.NormalizeAddr},
946947
})
947948
config.Vault.Address = c.flagAddress
948949
c.setStringFlag(f, config.Vault.CACert, &StringVar{
@@ -1031,13 +1032,13 @@ func (c *AgentCommand) setStringFlag(f *FlagSets, configVal string, fVar *String
10311032
// Don't do anything as the flag is already set from the command line
10321033
case flagEnvSet:
10331034
// Use value from env var
1034-
*fVar.Target = flagEnvValue
1035+
fVar.SetTarget(flagEnvValue)
10351036
case configVal != "":
10361037
// Use value from config
1037-
*fVar.Target = configVal
1038+
fVar.SetTarget(configVal)
10381039
default:
10391040
// Use the default value
1040-
*fVar.Target = fVar.Default
1041+
fVar.SetTarget(fVar.Default)
10411042
}
10421043
}
10431044

command/agent/config/config.go

+57
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,10 @@ func parseVault(result *Config, list *ast.ObjectList) error {
751751
return err
752752
}
753753

754+
if v.Address != "" {
755+
v.Address = configutil.NormalizeAddr(v.Address)
756+
}
757+
754758
if v.TLSSkipVerifyRaw != nil {
755759
v.TLSSkipVerify, err = parseutil.ParseBool(v.TLSSkipVerifyRaw)
756760
if err != nil {
@@ -1038,10 +1042,63 @@ func parseMethod(result *Config, list *ast.ObjectList) error {
10381042
// Canonicalize namespace path if provided
10391043
m.Namespace = namespace.Canonicalize(m.Namespace)
10401044

1045+
// Normalize any configuration addresses
1046+
if len(m.Config) > 0 {
1047+
var err error
1048+
for k, v := range m.Config {
1049+
vStr, ok := v.(string)
1050+
if !ok {
1051+
continue
1052+
}
1053+
m.Config[k], err = normalizeAutoAuthMethod(m.Type, k, vStr)
1054+
if err != nil {
1055+
return err
1056+
}
1057+
}
1058+
}
1059+
10411060
result.AutoAuth.Method = &m
10421061
return nil
10431062
}
10441063

1064+
// autoAuthMethodKeys maps an auto-auth method type to its associated
1065+
// configuration whose values are URLs, IP addresses, or host:port style
1066+
// addresses. All auto-auth types must have an entry in this map, otherwise our
1067+
// normalization check will fail when parsing the storage entry config.
1068+
// Auto-auth method types which don't contain such keys should include an empty
1069+
// array.
1070+
var autoAuthMethodKeys = map[string][]string{
1071+
"alicloud": {""},
1072+
"approle": {""},
1073+
"aws": {""},
1074+
"azure": {"resource"},
1075+
"cert": {""},
1076+
"cf": {""},
1077+
"gcp": {"service_account"},
1078+
"jwt": {""},
1079+
"ldap": {""},
1080+
"kerberos": {""},
1081+
"kubernetes": {""},
1082+
"oci": {""},
1083+
"token_file": {""},
1084+
}
1085+
1086+
// normalizeAutoAuthMethod takes a storage name, a configuration key
1087+
// and its associated value and will normalize any URLs, IP addresses, or
1088+
// host:port style addresses.
1089+
func normalizeAutoAuthMethod(method string, key string, value string) (string, error) {
1090+
keys, ok := autoAuthMethodKeys[method]
1091+
if !ok {
1092+
return "", fmt.Errorf("unknown auto-auth method type %s", method)
1093+
}
1094+
1095+
if slices.Contains(keys, key) {
1096+
return configutil.NormalizeAddr(value), nil
1097+
}
1098+
1099+
return value, nil
1100+
}
1101+
10451102
func parseSinks(result *Config, list *ast.ObjectList) error {
10461103
name := "sink"
10471104

command/agent/config/config_test.go

+108-64
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/hashicorp/vault/command/agentproxyshared"
1515
"github.com/hashicorp/vault/internalshared/configutil"
1616
"github.com/hashicorp/vault/sdk/helper/pointerutil"
17+
"github.com/stretchr/testify/require"
1718
"golang.org/x/exp/slices"
1819
)
1920

@@ -230,6 +231,9 @@ func TestLoadConfigDir_AgentCache(t *testing.T) {
230231
t.Fatal(err)
231232
}
232233
config2, err := LoadConfigFile("./test-fixtures/config-dir-cache/config-cache2.hcl")
234+
if err != nil {
235+
t.Fatal(err)
236+
}
233237

234238
mergedConfig := config.Merge(config2)
235239

@@ -441,77 +445,117 @@ func TestLoadConfigFile_AgentCache_NoListeners(t *testing.T) {
441445
}
442446
}
443447

444-
func TestLoadConfigFile(t *testing.T) {
445-
if err := os.Setenv("TEST_AAD_ENV", "aad"); err != nil {
446-
t.Fatal(err)
447-
}
448-
defer func() {
449-
if err := os.Unsetenv("TEST_AAD_ENV"); err != nil {
450-
t.Fatal(err)
451-
}
452-
}()
453-
454-
config, err := LoadConfigFile("./test-fixtures/config.hcl")
455-
if err != nil {
456-
t.Fatalf("err: %s", err)
457-
}
448+
// Test_LoadConfigFile_AutoAuth_AddrConformance verifies basic config file
449+
// loading in addition to RFC-5942 §4 normalization of auto-auth methods.
450+
// See: https://rfc-editor.org/rfc/rfc5952.html
451+
func Test_LoadConfigFile_AutoAuth_AddrConformance(t *testing.T) {
452+
t.Setenv("TEST_AAD_ENV", "aad")
453+
454+
for name, method := range map[string]*Method{
455+
"aws": {
456+
Type: "aws",
457+
MountPath: "auth/aws",
458+
Namespace: "aws-namespace/",
459+
Config: map[string]any{
460+
"role": "foobar",
461+
},
462+
},
463+
"azure": {
464+
Type: "azure",
465+
MountPath: "auth/azure",
466+
Namespace: "azure-namespace/",
467+
Config: map[string]any{
468+
"authenticate_from_environment": true,
469+
"role": "dev-role",
470+
"resource": "https://[2001:0:0:1::1]",
471+
},
472+
},
473+
"gcp": {
474+
Type: "gcp",
475+
MountPath: "auth/gcp",
476+
Namespace: "gcp-namespace/",
477+
Config: map[string]any{
478+
"role": "dev-role",
479+
"service_account": "https://[2001:db8:ac3:fe4::1]",
480+
},
481+
},
482+
} {
483+
t.Run(name, func(t *testing.T) {
484+
config, err := LoadConfigFile("./test-fixtures/config-auto-auth-" + name + ".hcl")
485+
require.NoError(t, err)
458486

459-
expected := &Config{
460-
SharedConfig: &configutil.SharedConfig{
461-
PidFile: "./pidfile",
462-
LogFile: "/var/log/vault/vault-agent.log",
463-
},
464-
AutoAuth: &AutoAuth{
465-
Method: &Method{
466-
Type: "aws",
467-
MountPath: "auth/aws",
468-
Namespace: "my-namespace/",
469-
Config: map[string]interface{}{
470-
"role": "foobar",
487+
expected := &Config{
488+
SharedConfig: &configutil.SharedConfig{
489+
PidFile: "./pidfile",
490+
Listeners: []*configutil.Listener{
491+
{
492+
Type: "unix",
493+
Address: "/path/to/socket",
494+
TLSDisable: true,
495+
AgentAPI: &configutil.AgentAPI{
496+
EnableQuit: true,
497+
},
498+
},
499+
{
500+
Type: "tcp",
501+
Address: "2001:db8::1:8200", // Normalized
502+
TLSDisable: true,
503+
},
504+
{
505+
Type: "tcp",
506+
Address: "[2001:0:0:1::1]:3000", // Normalized
507+
Role: "metrics_only",
508+
TLSDisable: true,
509+
},
510+
{
511+
Type: "tcp",
512+
Role: "default",
513+
Address: "2001:db8:0:1:1:1:1:1:8400", // Normalized
514+
TLSKeyFile: "/path/to/cakey.pem",
515+
TLSCertFile: "/path/to/cacert.pem",
516+
},
517+
},
518+
LogFile: "/var/log/vault/vault-agent.log",
471519
},
472-
MaxBackoff: 0,
473-
},
474-
Sinks: []*Sink{
475-
{
476-
Type: "file",
477-
DHType: "curve25519",
478-
DHPath: "/tmp/file-foo-dhpath",
479-
AAD: "foobar",
480-
Config: map[string]interface{}{
481-
"path": "/tmp/file-foo",
520+
Vault: &Vault{
521+
Address: "https://[2001:db8::1]:8200", // Address is normalized
522+
Retry: &Retry{
523+
NumRetries: 12, // Default number of retries when a vault stanza is set
482524
},
483525
},
484-
{
485-
Type: "file",
486-
WrapTTL: 5 * time.Minute,
487-
DHType: "curve25519",
488-
DHPath: "/tmp/file-foo-dhpath2",
489-
AAD: "aad",
490-
DeriveKey: true,
491-
Config: map[string]interface{}{
492-
"path": "/tmp/file-bar",
526+
AutoAuth: &AutoAuth{
527+
Method: method, // Method properties are normalized correctly
528+
Sinks: []*Sink{
529+
{
530+
Type: "file",
531+
DHType: "curve25519",
532+
DHPath: "/tmp/file-foo-dhpath",
533+
AAD: "foobar",
534+
Config: map[string]interface{}{
535+
"path": "/tmp/file-foo",
536+
},
537+
},
538+
{
539+
Type: "file",
540+
WrapTTL: 5 * time.Minute,
541+
DHType: "curve25519",
542+
DHPath: "/tmp/file-foo-dhpath2",
543+
AAD: "aad",
544+
DeriveKey: true,
545+
Config: map[string]interface{}{
546+
"path": "/tmp/file-bar",
547+
},
548+
},
493549
},
494550
},
495-
},
496-
},
497-
TemplateConfig: &TemplateConfig{
498-
MaxConnectionsPerHost: DefaultTemplateConfigMaxConnsPerHost,
499-
},
500-
}
501-
502-
config.Prune()
503-
if diff := deep.Equal(config, expected); diff != nil {
504-
t.Fatal(diff)
505-
}
506-
507-
config, err = LoadConfigFile("./test-fixtures/config-embedded-type.hcl")
508-
if err != nil {
509-
t.Fatalf("err: %s", err)
510-
}
551+
TemplateConfig: &TemplateConfig{
552+
MaxConnectionsPerHost: DefaultTemplateConfigMaxConnsPerHost,
553+
},
554+
}
511555

512-
config.Prune()
513-
if diff := deep.Equal(config, expected); diff != nil {
514-
t.Fatal(diff)
556+
config.Prune()
557+
require.EqualValues(t, expected, config)
558+
})
515559
}
516560
}
517561

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: BUSL-1.1
3+
4+
pid_file = "./pidfile"
5+
log_file = "/var/log/vault/vault-agent.log"
6+
7+
vault {
8+
address = "https://[2001:0db8::0001]:8200"
9+
}
10+
11+
auto_auth {
12+
method {
13+
type = "aws"
14+
namespace = "/aws-namespace"
15+
config = {
16+
role = "foobar"
17+
}
18+
}
19+
20+
sink {
21+
type = "file"
22+
config = {
23+
path = "/tmp/file-foo"
24+
}
25+
aad = "foobar"
26+
dh_type = "curve25519"
27+
dh_path = "/tmp/file-foo-dhpath"
28+
}
29+
30+
sink {
31+
type = "file"
32+
wrap_ttl = "5m"
33+
aad_env_var = "TEST_AAD_ENV"
34+
dh_type = "curve25519"
35+
dh_path = "/tmp/file-foo-dhpath2"
36+
derive_key = true
37+
config = {
38+
path = "/tmp/file-bar"
39+
}
40+
}
41+
}
42+
43+
listener "unix" {
44+
address = "/path/to/socket"
45+
tls_disable = true
46+
47+
agent_api {
48+
enable_quit = true
49+
}
50+
}
51+
52+
listener "tcp" {
53+
address = "2001:0db8::0001:8200"
54+
tls_disable = true
55+
}
56+
57+
listener {
58+
type = "tcp"
59+
address = "[2001:0:0:1:0:0:0:1]:3000"
60+
tls_disable = true
61+
role = "metrics_only"
62+
}
63+
64+
listener "tcp" {
65+
role = "default"
66+
address = "2001:db8:0:1:1:1:1:1:8400"
67+
tls_key_file = "/path/to/cakey.pem"
68+
tls_cert_file = "/path/to/cacert.pem"
69+
}

0 commit comments

Comments
 (0)