diff --git a/api/client.go b/api/client.go index b5f7e9bb82656..957ba5d824980 100644 --- a/api/client.go +++ b/api/client.go @@ -720,6 +720,24 @@ func (c *Client) SetMaxRetries(retries int) { c.config.MaxRetries = retries } +func (c *Client) SetMaxIdleConnections(idle int) { + c.modifyLock.RLock() + defer c.modifyLock.RUnlock() + c.config.modifyLock.Lock() + defer c.config.modifyLock.Unlock() + + c.config.HttpClient.Transport.(*http.Transport).MaxIdleConns = idle +} + +func (c *Client) MaxIdleConnections() int { + c.modifyLock.RLock() + defer c.modifyLock.RUnlock() + c.config.modifyLock.Lock() + defer c.config.modifyLock.Unlock() + + return c.config.HttpClient.Transport.(*http.Transport).MaxIdleConns +} + func (c *Client) MaxRetries() int { c.modifyLock.RLock() defer c.modifyLock.RUnlock() diff --git a/changelog/15986.txt b/changelog/15986.txt new file mode 100644 index 0000000000000..663ccc8c9b411 --- /dev/null +++ b/changelog/15986.txt @@ -0,0 +1,2 @@ +```release-note:improvement +agent: Added `disable_idle_connections` configuration to disable leaving idle connections open in auto-auth, caching and templating. \ No newline at end of file diff --git a/command/agent.go b/command/agent.go index 883944da756c3..16fa9fa38e18d 100644 --- a/command/agent.go +++ b/command/agent.go @@ -368,13 +368,24 @@ func (c *AgentCommand) Run(args []string) int { client.SetNamespace(config.AutoAuth.Method.Namespace) } templateNamespace = client.Headers().Get(consts.NamespaceHeaderName) + + sinkClient, err := client.CloneWithHeaders() + if err != nil { + c.UI.Error(fmt.Sprintf("Error cloning client for file sink: %v", err)) + return 1 + } + + if config.DisableIdleConnsAutoAuth { + sinkClient.SetMaxIdleConnections(-1) + } + for _, sc := range config.AutoAuth.Sinks { switch sc.Type { case "file": config := &sink.SinkConfig{ Logger: c.logger.Named("sink.file"), Config: sc.Config, - Client: client, + Client: sinkClient, WrapTTL: sc.WrapTTL, DHType: sc.DHType, DeriveKey: sc.DeriveKey, @@ -490,9 +501,19 @@ func (c *AgentCommand) Run(args []string) int { if config.Cache != nil { cacheLogger := c.logger.Named("cache") + proxyClient, err := client.CloneWithHeaders() + if err != nil { + c.UI.Error(fmt.Sprintf("Error cloning client for caching: %v", err)) + return 1 + } + + if config.DisableIdleConnsAutoAuth { + proxyClient.SetMaxIdleConnections(-1) + } + // Create the API proxier apiProxy, err := cache.NewAPIProxy(&cache.APIProxyConfig{ - Client: client, + Client: proxyClient, Logger: cacheLogger.Named("apiproxy"), EnforceConsistency: enforceConsistency, WhenInconsistentAction: whenInconsistent, @@ -505,7 +526,7 @@ func (c *AgentCommand) Run(args []string) int { // Create the lease cache proxier and set its underlying proxier to // the API proxier. leaseCache, err = cache.NewLeaseCache(&cache.LeaseCacheConfig{ - Client: client, + Client: proxyClient, BaseContext: ctx, Proxier: apiProxy, Logger: cacheLogger.Named("leasecache"), @@ -793,14 +814,19 @@ func (c *AgentCommand) Run(args []string) int { // Auth Handler is going to set its own retry values, so we want to // work on a copy of the client to not affect other subsystems. - clonedClient, err := c.client.CloneWithHeaders() + ahClient, err := c.client.CloneWithHeaders() if err != nil { c.UI.Error(fmt.Sprintf("Error cloning client for auth handler: %v", err)) return 1 } + + if config.DisableIdleConnsAutoAuth { + ahClient.SetMaxIdleConnections(-1) + } + ah := auth.NewAuthHandler(&auth.AuthHandlerConfig{ Logger: c.logger.Named("auth.handler"), - Client: clonedClient, + Client: ahClient, WrapTTL: config.AutoAuth.Method.WrapTTL, MinBackoff: config.AutoAuth.Method.MinBackoff, MaxBackoff: config.AutoAuth.Method.MaxBackoff, @@ -811,7 +837,7 @@ func (c *AgentCommand) Run(args []string) int { ss := sink.NewSinkServer(&sink.SinkServerConfig{ Logger: c.logger.Named("sink.server"), - Client: client, + Client: ahClient, ExitAfterAuth: exitAfterAuth, }) diff --git a/command/agent/config/config.go b/command/agent/config/config.go index e68af26f644fc..8a28dcf631528 100644 --- a/command/agent/config/config.go +++ b/command/agent/config/config.go @@ -24,14 +24,20 @@ import ( type Config struct { *configutil.SharedConfig `hcl:"-"` - AutoAuth *AutoAuth `hcl:"auto_auth"` - ExitAfterAuth bool `hcl:"exit_after_auth"` - Cache *Cache `hcl:"cache"` - Vault *Vault `hcl:"vault"` - TemplateConfig *TemplateConfig `hcl:"template_config"` - Templates []*ctconfig.TemplateConfig `hcl:"templates"` + AutoAuth *AutoAuth `hcl:"auto_auth"` + ExitAfterAuth bool `hcl:"exit_after_auth"` + Cache *Cache `hcl:"cache"` + Vault *Vault `hcl:"vault"` + TemplateConfig *TemplateConfig `hcl:"template_config"` + Templates []*ctconfig.TemplateConfig `hcl:"templates"` + DisableIdleConns []string `hcl:"disable_idle_connections"` + DisableIdleConnsCaching bool `hcl:"-"` + DisableIdleConnsTemplating bool `hcl:"-"` + DisableIdleConnsAutoAuth bool `hcl:"-"` } +const DisableIdleConnsEnv = "VAULT_AGENT_DISABLE_IDLE_CONNECTIONS" + func (c *Config) Prune() { for _, l := range c.Listeners { l.RawConfig = nil @@ -260,6 +266,28 @@ func LoadConfig(path string) (*Config, error) { result.Vault.Retry.NumRetries = 0 } + if disableIdleConnsEnv := os.Getenv(DisableIdleConnsEnv); disableIdleConnsEnv != "" { + result.DisableIdleConns, err = parseutil.ParseCommaStringSlice(strings.ToLower(disableIdleConnsEnv)) + if err != nil { + return nil, fmt.Errorf("error parsing environment variable %s: %v", DisableIdleConnsEnv, err) + } + } + + for _, subsystem := range result.DisableIdleConns { + switch subsystem { + case "auto-auth": + result.DisableIdleConnsAutoAuth = true + case "caching": + result.DisableIdleConnsCaching = true + case "templating": + result.DisableIdleConnsTemplating = true + case "": + continue + default: + return nil, fmt.Errorf("unknown disable_idle_connections value: %s", subsystem) + } + } + return result, nil } diff --git a/command/agent/config/config_test.go b/command/agent/config/config_test.go index 1a1aec2a14d1c..c9728543c3b4d 100644 --- a/command/agent/config/config_test.go +++ b/command/agent/config/config_test.go @@ -1033,3 +1033,310 @@ func TestLoadConfigFile_EnforceConsistency(t *testing.T) { t.Fatal(diff) } } + +func TestLoadConfigFile_Disable_Idle_Conns_All(t *testing.T) { + config, err := LoadConfig("./test-fixtures/config-disable-idle-connections-all.hcl") + if err != nil { + t.Fatal(err) + } + + expected := &Config{ + SharedConfig: &configutil.SharedConfig{ + PidFile: "./pidfile", + }, + DisableIdleConns: []string{"auto-auth", "caching", "templating"}, + DisableIdleConnsCaching: true, + DisableIdleConnsAutoAuth: true, + DisableIdleConnsTemplating: true, + AutoAuth: &AutoAuth{ + Method: &Method{ + Type: "aws", + MountPath: "auth/aws", + Namespace: "my-namespace/", + Config: map[string]interface{}{ + "role": "foobar", + }, + }, + Sinks: []*Sink{ + { + Type: "file", + DHType: "curve25519", + DHPath: "/tmp/file-foo-dhpath", + AAD: "foobar", + Config: map[string]interface{}{ + "path": "/tmp/file-foo", + }, + }, + }, + }, + Vault: &Vault{ + Address: "http://127.0.0.1:1111", + Retry: &Retry{ + ctconfig.DefaultRetryAttempts, + }, + }, + } + + config.Prune() + if diff := deep.Equal(config, expected); diff != nil { + t.Fatal(diff) + } +} + +func TestLoadConfigFile_Disable_Idle_Conns_Auto_Auth(t *testing.T) { + config, err := LoadConfig("./test-fixtures/config-disable-idle-connections-auto-auth.hcl") + if err != nil { + t.Fatal(err) + } + + expected := &Config{ + SharedConfig: &configutil.SharedConfig{ + PidFile: "./pidfile", + }, + DisableIdleConns: []string{"auto-auth"}, + DisableIdleConnsCaching: false, + DisableIdleConnsAutoAuth: true, + DisableIdleConnsTemplating: false, + AutoAuth: &AutoAuth{ + Method: &Method{ + Type: "aws", + MountPath: "auth/aws", + Namespace: "my-namespace/", + Config: map[string]interface{}{ + "role": "foobar", + }, + }, + Sinks: []*Sink{ + { + Type: "file", + DHType: "curve25519", + DHPath: "/tmp/file-foo-dhpath", + AAD: "foobar", + Config: map[string]interface{}{ + "path": "/tmp/file-foo", + }, + }, + }, + }, + Vault: &Vault{ + Address: "http://127.0.0.1:1111", + Retry: &Retry{ + ctconfig.DefaultRetryAttempts, + }, + }, + } + + config.Prune() + if diff := deep.Equal(config, expected); diff != nil { + t.Fatal(diff) + } +} + +func TestLoadConfigFile_Disable_Idle_Conns_Templating(t *testing.T) { + config, err := LoadConfig("./test-fixtures/config-disable-idle-connections-templating.hcl") + if err != nil { + t.Fatal(err) + } + + expected := &Config{ + SharedConfig: &configutil.SharedConfig{ + PidFile: "./pidfile", + }, + DisableIdleConns: []string{"templating"}, + DisableIdleConnsCaching: false, + DisableIdleConnsAutoAuth: false, + DisableIdleConnsTemplating: true, + AutoAuth: &AutoAuth{ + Method: &Method{ + Type: "aws", + MountPath: "auth/aws", + Namespace: "my-namespace/", + Config: map[string]interface{}{ + "role": "foobar", + }, + }, + Sinks: []*Sink{ + { + Type: "file", + DHType: "curve25519", + DHPath: "/tmp/file-foo-dhpath", + AAD: "foobar", + Config: map[string]interface{}{ + "path": "/tmp/file-foo", + }, + }, + }, + }, + Vault: &Vault{ + Address: "http://127.0.0.1:1111", + Retry: &Retry{ + ctconfig.DefaultRetryAttempts, + }, + }, + } + + config.Prune() + if diff := deep.Equal(config, expected); diff != nil { + t.Fatal(diff) + } +} + +func TestLoadConfigFile_Disable_Idle_Conns_Caching(t *testing.T) { + config, err := LoadConfig("./test-fixtures/config-disable-idle-connections-caching.hcl") + if err != nil { + t.Fatal(err) + } + + expected := &Config{ + SharedConfig: &configutil.SharedConfig{ + PidFile: "./pidfile", + }, + DisableIdleConns: []string{"caching"}, + DisableIdleConnsCaching: true, + DisableIdleConnsAutoAuth: false, + DisableIdleConnsTemplating: false, + AutoAuth: &AutoAuth{ + Method: &Method{ + Type: "aws", + MountPath: "auth/aws", + Namespace: "my-namespace/", + Config: map[string]interface{}{ + "role": "foobar", + }, + }, + Sinks: []*Sink{ + { + Type: "file", + DHType: "curve25519", + DHPath: "/tmp/file-foo-dhpath", + AAD: "foobar", + Config: map[string]interface{}{ + "path": "/tmp/file-foo", + }, + }, + }, + }, + Vault: &Vault{ + Address: "http://127.0.0.1:1111", + Retry: &Retry{ + ctconfig.DefaultRetryAttempts, + }, + }, + } + + config.Prune() + if diff := deep.Equal(config, expected); diff != nil { + t.Fatal(diff) + } +} + +func TestLoadConfigFile_Disable_Idle_Conns_Empty(t *testing.T) { + config, err := LoadConfig("./test-fixtures/config-disable-idle-connections-empty.hcl") + if err != nil { + t.Fatal(err) + } + + expected := &Config{ + SharedConfig: &configutil.SharedConfig{ + PidFile: "./pidfile", + }, + DisableIdleConns: []string{}, + DisableIdleConnsCaching: false, + DisableIdleConnsAutoAuth: false, + DisableIdleConnsTemplating: false, + AutoAuth: &AutoAuth{ + Method: &Method{ + Type: "aws", + MountPath: "auth/aws", + Namespace: "my-namespace/", + Config: map[string]interface{}{ + "role": "foobar", + }, + }, + Sinks: []*Sink{ + { + Type: "file", + DHType: "curve25519", + DHPath: "/tmp/file-foo-dhpath", + AAD: "foobar", + Config: map[string]interface{}{ + "path": "/tmp/file-foo", + }, + }, + }, + }, + Vault: &Vault{ + Address: "http://127.0.0.1:1111", + Retry: &Retry{ + ctconfig.DefaultRetryAttempts, + }, + }, + } + + config.Prune() + if diff := deep.Equal(config, expected); diff != nil { + t.Fatal(diff) + } +} + +func TestLoadConfigFile_Disable_Idle_Conns_Env(t *testing.T) { + err := os.Setenv(DisableIdleConnsEnv, "auto-auth,caching,templating") + defer os.Unsetenv(DisableIdleConnsEnv) + + if err != nil { + t.Fatal(err) + } + config, err := LoadConfig("./test-fixtures/config-disable-idle-connections-empty.hcl") + if err != nil { + t.Fatal(err) + } + + expected := &Config{ + SharedConfig: &configutil.SharedConfig{ + PidFile: "./pidfile", + }, + DisableIdleConns: []string{"auto-auth", "caching", "templating"}, + DisableIdleConnsCaching: true, + DisableIdleConnsAutoAuth: true, + DisableIdleConnsTemplating: true, + AutoAuth: &AutoAuth{ + Method: &Method{ + Type: "aws", + MountPath: "auth/aws", + Namespace: "my-namespace/", + Config: map[string]interface{}{ + "role": "foobar", + }, + }, + Sinks: []*Sink{ + { + Type: "file", + DHType: "curve25519", + DHPath: "/tmp/file-foo-dhpath", + AAD: "foobar", + Config: map[string]interface{}{ + "path": "/tmp/file-foo", + }, + }, + }, + }, + Vault: &Vault{ + Address: "http://127.0.0.1:1111", + Retry: &Retry{ + ctconfig.DefaultRetryAttempts, + }, + }, + } + + config.Prune() + if diff := deep.Equal(config, expected); diff != nil { + t.Fatal(diff) + } +} + +func TestLoadConfigFile_Bad_Value_Disable_Idle_Conns(t *testing.T) { + _, err := LoadConfig("./test-fixtures/bad-config-disable-idle-connections.hcl") + if err == nil { + t.Fatal("should have error, it didn't") + } +} diff --git a/command/agent/config/test-fixtures/bad-config-disable-idle-connections.hcl b/command/agent/config/test-fixtures/bad-config-disable-idle-connections.hcl new file mode 100644 index 0000000000000..c13c82520ee6b --- /dev/null +++ b/command/agent/config/test-fixtures/bad-config-disable-idle-connections.hcl @@ -0,0 +1,27 @@ +pid_file = "./pidfile" +disable_idle_connections = ["foo","caching","templating"] + +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" + } +} + +vault { + address = "http://127.0.0.1:1111" +} diff --git a/command/agent/config/test-fixtures/config-disable-idle-connections-all.hcl b/command/agent/config/test-fixtures/config-disable-idle-connections-all.hcl new file mode 100644 index 0000000000000..69ff548f55614 --- /dev/null +++ b/command/agent/config/test-fixtures/config-disable-idle-connections-all.hcl @@ -0,0 +1,27 @@ +pid_file = "./pidfile" +disable_idle_connections = ["auto-auth","caching","templating"] + +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" + } +} + +vault { + address = "http://127.0.0.1:1111" +} diff --git a/command/agent/config/test-fixtures/config-disable-idle-connections-auto-auth.hcl b/command/agent/config/test-fixtures/config-disable-idle-connections-auto-auth.hcl new file mode 100644 index 0000000000000..1a63b20480d4f --- /dev/null +++ b/command/agent/config/test-fixtures/config-disable-idle-connections-auto-auth.hcl @@ -0,0 +1,27 @@ +pid_file = "./pidfile" +disable_idle_connections = ["auto-auth"] + +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" + } +} + +vault { + address = "http://127.0.0.1:1111" +} diff --git a/command/agent/config/test-fixtures/config-disable-idle-connections-caching.hcl b/command/agent/config/test-fixtures/config-disable-idle-connections-caching.hcl new file mode 100644 index 0000000000000..30d0806c03371 --- /dev/null +++ b/command/agent/config/test-fixtures/config-disable-idle-connections-caching.hcl @@ -0,0 +1,27 @@ +pid_file = "./pidfile" +disable_idle_connections = ["caching"] + +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" + } +} + +vault { + address = "http://127.0.0.1:1111" +} diff --git a/command/agent/config/test-fixtures/config-disable-idle-connections-empty.hcl b/command/agent/config/test-fixtures/config-disable-idle-connections-empty.hcl new file mode 100644 index 0000000000000..eb95310cedfff --- /dev/null +++ b/command/agent/config/test-fixtures/config-disable-idle-connections-empty.hcl @@ -0,0 +1,27 @@ +pid_file = "./pidfile" +disable_idle_connections = [] + +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" + } +} + +vault { + address = "http://127.0.0.1:1111" +} diff --git a/command/agent/config/test-fixtures/config-disable-idle-connections-templating.hcl b/command/agent/config/test-fixtures/config-disable-idle-connections-templating.hcl new file mode 100644 index 0000000000000..922377fc82a96 --- /dev/null +++ b/command/agent/config/test-fixtures/config-disable-idle-connections-templating.hcl @@ -0,0 +1,27 @@ +pid_file = "./pidfile" +disable_idle_connections = ["templating"] + +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" + } +} + +vault { + address = "http://127.0.0.1:1111" +} diff --git a/command/agent/template/template.go b/command/agent/template/template.go index 9ff22fbd9b25c..0fa1e9a0d2730 100644 --- a/command/agent/template/template.go +++ b/command/agent/template/template.go @@ -107,6 +107,7 @@ func (ts *Server) Run(ctx context.Context, incoming chan string, templates []*ct // configuration var runnerConfig *ctconfig.Config var runnerConfigErr error + if runnerConfig, runnerConfigErr = newRunnerConfig(ts.config, templates); runnerConfigErr != nil { return fmt.Errorf("template server failed to runner generate config: %w", runnerConfigErr) } @@ -244,6 +245,11 @@ func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) (*ctc conf.Vault.DefaultLeaseDuration = &sc.AgentConfig.TemplateConfig.StaticSecretRenderInt } + if sc.AgentConfig.DisableIdleConnsTemplating { + idleConns := -1 + conf.Vault.Transport.MaxIdleConns = &idleConns + } + conf.Vault.SSL = &ctconfig.SSLConfig{ Enabled: pointerutil.BoolPtr(false), Verify: pointerutil.BoolPtr(false), diff --git a/website/content/docs/agent/index.mdx b/website/content/docs/agent/index.mdx index a7de23df14452..6f7875f2f3db0 100644 --- a/website/content/docs/agent/index.mdx +++ b/website/content/docs/agent/index.mdx @@ -144,6 +144,10 @@ These are the currently-available general configuration option: with code `0` after a single successful auth, where success means that a token was retrieved and all sinks successfully wrote it +- `disable_idle_connections` `(string array: [])` - A list of strings that disables idle connections for various features in Vault Agent. + Valid values include: `auto-auth`, `caching` and `templating`. Can also be configured by setting the `VAULT_AGENT_DISABLE_IDLE_CONNECTIONS` + environment variable as a comma separated string. This environment variable will override any values found in a configuration file. + - `template` ([template][template]: ) - Specifies options used for templating Vault secrets to files. - `template_config` ([template_config][template-config]: ) - Specifies templating engine behavior.