Skip to content

Commit 2b73823

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 9e6b5ce commit 2b73823

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
@@ -743,6 +743,10 @@ func parseVault(result *Config, list *ast.ObjectList) error {
743743
return err
744744
}
745745

746+
if v.Address != "" {
747+
v.Address = configutil.NormalizeAddr(v.Address)
748+
}
749+
746750
if v.TLSSkipVerifyRaw != nil {
747751
v.TLSSkipVerify, err = parseutil.ParseBool(v.TLSSkipVerifyRaw)
748752
if err != nil {
@@ -1030,10 +1034,63 @@ func parseMethod(result *Config, list *ast.ObjectList) error {
10301034
// Canonicalize namespace path if provided
10311035
m.Namespace = namespace.Canonicalize(m.Namespace)
10321036

1037+
// Normalize any configuration addresses
1038+
if len(m.Config) > 0 {
1039+
var err error
1040+
for k, v := range m.Config {
1041+
vStr, ok := v.(string)
1042+
if !ok {
1043+
continue
1044+
}
1045+
m.Config[k], err = normalizeAutoAuthMethod(m.Type, k, vStr)
1046+
if err != nil {
1047+
return err
1048+
}
1049+
}
1050+
}
1051+
10331052
result.AutoAuth.Method = &m
10341053
return nil
10351054
}
10361055

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

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)