Skip to content

Commit 6d3b334

Browse files
committed
VAULT-33758: IPv6 address conformance for proxy and agent
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 Signed-off-by: Ryan Cragun <[email protected]>
1 parent 5d1a971 commit 6d3b334

22 files changed

+1295
-239
lines changed

command/agent.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -939,12 +939,13 @@ 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
})
947-
config.Vault.Address = c.flagAddress
948+
config.Vault.Address = configutil.NormalizeAddr(c.flagAddress)
948949
c.setStringFlag(f, config.Vault.CACert, &StringVar{
949950
Name: flagNameCACert,
950951
Target: &c.flagCACert,
@@ -1029,6 +1030,7 @@ func (c *AgentCommand) setStringFlag(f *FlagSets, configVal string, fVar *String
10291030
switch {
10301031
case isFlagSet:
10311032
// Don't do anything as the flag is already set from the command line
1033+
return
10321034
case flagEnvSet:
10331035
// Use value from env var
10341036
*fVar.Target = flagEnvValue

command/agent/config/config.go

Lines changed: 57 additions & 0 deletions
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 it's 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

Lines changed: 108 additions & 64 deletions
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

Lines changed: 69 additions & 0 deletions
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)