From a2f457e10c0059e65b1bc4bbde5180646a56f7f1 Mon Sep 17 00:00:00 2001 From: Violet Hynes Date: Mon, 3 Apr 2023 14:14:47 -0400 Subject: [PATCH] VAULT-12940 Vault Agent uses Vault Agent specific User-Agent header when issuing requests (#19776) * VAULT-12940 test for templating user agent * VAULT-12940 User agent work so far * VAULT-12940 Vault Agent uses Vault Agent specific User-Agent header when issuing requests * VAULT-12940 Clean-up and godocs * VAULT-12940 changelog * VAULT-12940 Fix test checking headers * VAULT-12940 Fix test checking headers * VAULT-12940 Fix test checking headers * VAULT-12940 Fix test checking headers * VAULT-12940 copy/paste typos * VAULT-12940 improve comments, use make(http.Header) * VAULT-12940 small typos and clean-up --- changelog/19776.txt | 3 + command/agent.go | 2 +- command/agent/auth/auth.go | 10 + command/agent/cache/api_proxy.go | 16 + command/agent/cache/lease_cache.go | 14 +- command/agent/cache/lease_cache_test.go | 21 +- command/agent/cache/testing.go | 6 +- command/agent/template/template.go | 5 +- command/agent_test.go | 613 ++++++++++++++++++++++++ go.mod | 18 +- go.sum | 43 +- helper/useragent/useragent.go | 42 ++ helper/useragent/useragent_test.go | 72 ++- 13 files changed, 832 insertions(+), 33 deletions(-) create mode 100644 changelog/19776.txt diff --git a/changelog/19776.txt b/changelog/19776.txt new file mode 100644 index 000000000..786cfd321 --- /dev/null +++ b/changelog/19776.txt @@ -0,0 +1,3 @@ +```release-note:improvement +agent: Vault Agent now reports its name and version as part of the User-Agent header in all requests issued. +``` diff --git a/command/agent.go b/command/agent.go index 9772ff33f..f4f9fb503 100644 --- a/command/agent.go +++ b/command/agent.go @@ -290,7 +290,7 @@ func (c *AgentCommand) Run(args []string) int { Ui: c.UI, ServiceName: "vault", DisplayName: "Vault", - UserAgent: useragent.String(), + UserAgent: useragent.AgentString(), ClusterName: config.ClusterName, }) if err != nil { diff --git a/command/agent/auth/auth.go b/command/agent/auth/auth.go index 80bc5bbbb..536d1be8b 100644 --- a/command/agent/auth/auth.go +++ b/command/agent/auth/auth.go @@ -14,6 +14,7 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/go-hclog" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/helper/useragent" "github.com/hashicorp/vault/sdk/helper/jsonutil" ) @@ -156,6 +157,15 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) error { credCh = make(chan struct{}) } + if ah.client != nil { + headers := ah.client.Headers() + if headers == nil { + headers = make(http.Header) + } + headers.Set("User-Agent", useragent.AgentAutoAuthString()) + ah.client.SetHeaders(headers) + } + var watcher *api.LifetimeWatcher first := true diff --git a/command/agent/cache/api_proxy.go b/command/agent/cache/api_proxy.go index 4581117a0..bebcb7a0d 100644 --- a/command/agent/cache/api_proxy.go +++ b/command/agent/cache/api_proxy.go @@ -6,11 +6,13 @@ package cache import ( "context" "fmt" + gohttp "net/http" "sync" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/helper/useragent" "github.com/hashicorp/vault/http" ) @@ -76,6 +78,20 @@ func (ap *APIProxy) Send(ctx context.Context, req *SendRequest) (*SendResponse, // the client doesn't manually set the header. Removing any Accept-Encoding header allows the // transparent compression to occur. req.Request.Header.Del("Accept-Encoding") + + if req.Request.Header == nil { + req.Request.Header = make(gohttp.Header) + } + + // Set our User-Agent to be one indicating we are Vault Agent's API proxy. + // If the sending client had one, preserve it. + if req.Request.Header.Get("User-Agent") != "" { + initialUserAgent := req.Request.Header.Get("User-Agent") + req.Request.Header.Set("User-Agent", useragent.AgentProxyStringWithProxiedUserAgent(initialUserAgent)) + } else { + req.Request.Header.Set("User-Agent", useragent.AgentProxyString()) + } + client.SetHeaders(req.Request.Header) fwReq := client.NewRequest(req.Request.Method, req.Request.URL.Path) diff --git a/command/agent/cache/lease_cache.go b/command/agent/cache/lease_cache.go index 286f498e0..feb526af5 100644 --- a/command/agent/cache/lease_cache.go +++ b/command/agent/cache/lease_cache.go @@ -27,6 +27,7 @@ import ( "github.com/hashicorp/vault/command/agent/cache/cachememdb" "github.com/hashicorp/vault/helper/namespace" nshelper "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/helper/useragent" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/cryptoutil" @@ -477,7 +478,18 @@ func (c *LeaseCache) startRenewing(ctx context.Context, index *cachememdb.Index, return } client.SetToken(req.Token) - client.SetHeaders(req.Request.Header) + + headers := client.Headers() + if headers == nil { + headers = make(http.Header) + } + + // We do not preserve the initial User-Agent here (i.e. use + // AgentProxyStringWithProxiedUserAgent) since these requests are from + // the proxy subsystem, but are made by Agent's lifetime watcher, + // not triggered by a specific request. + headers.Set("User-Agent", useragent.AgentProxyString()) + client.SetHeaders(headers) watcher, err := client.NewLifetimeWatcher(&api.LifetimeWatcherInput{ Secret: secret, diff --git a/command/agent/cache/lease_cache_test.go b/command/agent/cache/lease_cache_test.go index 6ce618312..a0de2660e 100644 --- a/command/agent/cache/lease_cache_test.go +++ b/command/agent/cache/lease_cache_test.go @@ -17,6 +17,8 @@ import ( "testing" "time" + "github.com/hashicorp/vault/helper/useragent" + "github.com/go-test/deep" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" @@ -737,7 +739,7 @@ func compareBeforeAndAfter(t *testing.T, before, after *LeaseCache, beforeLen, a assert.Equal(t, cachedItem.Lease, restoredItem.Lease) assert.Equal(t, cachedItem.LeaseToken, restoredItem.LeaseToken) assert.Equal(t, cachedItem.Namespace, restoredItem.Namespace) - assert.Equal(t, cachedItem.RequestHeader, restoredItem.RequestHeader) + assert.EqualValues(t, cachedItem.RequestHeader, restoredItem.RequestHeader) assert.Equal(t, cachedItem.RequestMethod, restoredItem.RequestMethod) assert.Equal(t, cachedItem.RequestPath, restoredItem.RequestPath) assert.Equal(t, cachedItem.RequestToken, restoredItem.RequestToken) @@ -842,16 +844,21 @@ func TestLeaseCache_PersistAndRestore(t *testing.T) { var deleteIDs []string for i, ct := range cacheTests { // Send once to cache + req := httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)) + req.Header.Set("User-Agent", useragent.AgentProxyString()) + sendReq := &SendRequest{ Token: ct.token, - Request: httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)), + Request: req, } if ct.deleteFromPersistentStore { deleteID, err := computeIndexID(sendReq) require.NoError(t, err) deleteIDs = append(deleteIDs, deleteID) // Now reset the body after calculating the index - sendReq.Request = httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)) + req = httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)) + req.Header.Set("User-Agent", useragent.AgentProxyString()) + sendReq.Request = req } resp, err := lc.Send(context.Background(), sendReq) require.NoError(t, err) @@ -860,9 +867,11 @@ func TestLeaseCache_PersistAndRestore(t *testing.T) { // Send again to test cache. If this isn't cached, the response returned // will be the next in the list and the status code will not match. + req = httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)) + req.Header.Set("User-Agent", useragent.AgentProxyString()) sendCacheReq := &SendRequest{ Token: ct.token, - Request: httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)), + Request: req, } respCached, err := lc.Send(context.Background(), sendCacheReq) require.NoError(t, err, "failed to send request %+v", ct) @@ -894,9 +903,11 @@ func TestLeaseCache_PersistAndRestore(t *testing.T) { // And finally send the cache requests once to make sure they're all being // served from the restoredCache unless they were intended to be missing after restore. for i, ct := range cacheTests { + req := httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)) + req.Header.Set("User-Agent", useragent.AgentProxyString()) sendCacheReq := &SendRequest{ Token: ct.token, - Request: httptest.NewRequest(ct.method, ct.urlPath, strings.NewReader(ct.body)), + Request: req, } respCached, err := restoredCache.Send(context.Background(), sendCacheReq) require.NoError(t, err, "failed to send request %+v", ct) diff --git a/command/agent/cache/testing.go b/command/agent/cache/testing.go index f68ae6e49..46e15bc36 100644 --- a/command/agent/cache/testing.go +++ b/command/agent/cache/testing.go @@ -13,6 +13,8 @@ import ( "strings" "time" + "github.com/hashicorp/vault/helper/useragent" + "github.com/hashicorp/vault/api" ) @@ -47,11 +49,13 @@ func (p *mockProxier) ResponseIndex() int { } func newTestSendResponse(status int, body string) *SendResponse { + headers := make(http.Header) + headers.Add("User-Agent", useragent.AgentProxyString()) resp := &SendResponse{ Response: &api.Response{ Response: &http.Response{ StatusCode: status, - Header: http.Header{}, + Header: headers, }, }, } diff --git a/command/agent/template/template.go b/command/agent/template/template.go index 6de083d0a..15d064616 100644 --- a/command/agent/template/template.go +++ b/command/agent/template/template.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/consul-template/manager" "github.com/hashicorp/go-hclog" "github.com/hashicorp/vault/command/agent/config" + "github.com/hashicorp/vault/helper/useragent" "github.com/hashicorp/vault/sdk/helper/pointerutil" ) @@ -160,7 +161,8 @@ func (ts *Server) Run(ctx context.Context, incoming chan string, templates []*ct *latestToken = token ctv := ctconfig.Config{ Vault: &ctconfig.VaultConfig{ - Token: latestToken, + Token: latestToken, + ClientUserAgent: pointerutil.StringPtr(useragent.AgentTemplatingString()), }, } @@ -239,6 +241,7 @@ func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) (*ctc conf.Vault.RenewToken = pointerutil.BoolPtr(false) conf.Vault.Token = pointerutil.StringPtr("") conf.Vault.Address = &sc.AgentConfig.Vault.Address + conf.Vault.ClientUserAgent = pointerutil.StringPtr(useragent.AgentTemplatingString()) if sc.Namespace != "" { conf.Vault.Namespace = &sc.Namespace diff --git a/command/agent_test.go b/command/agent_test.go index 2be2d163b..f9606a772 100644 --- a/command/agent_test.go +++ b/command/agent_test.go @@ -26,6 +26,7 @@ import ( credAppRole "github.com/hashicorp/vault/builtin/credential/approle" "github.com/hashicorp/vault/command/agent" agentConfig "github.com/hashicorp/vault/command/agent/config" + "github.com/hashicorp/vault/helper/useragent" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/logging" @@ -514,6 +515,258 @@ listener "tcp" { } } +// TestAgent_Template_UserAgent Validates that the User-Agent sent to Vault +// as part of Templating requests is correct. Uses the custom handler +// userAgentHandler struct defined in this test package, so that Vault validates the +// User-Agent on requests sent by Agent. +func TestAgent_Template_UserAgent(t *testing.T) { + //---------------------------------------------------- + // Start the server and agent + //---------------------------------------------------- + logger := logging.NewVaultLogger(hclog.Trace) + var h userAgentHandler + cluster := vault.NewTestCluster(t, + &vault.CoreConfig{ + Logger: logger, + CredentialBackends: map[string]logical.Factory{ + "approle": credAppRole.Factory, + }, + LogicalBackends: map[string]logical.Factory{ + "kv": logicalKv.Factory, + }, + }, + &vault.TestClusterOptions{ + NumCores: 1, + HandlerFunc: vaulthttp.HandlerFunc( + func(properties *vault.HandlerProperties) http.Handler { + h.props = properties + h.userAgentToCheckFor = useragent.AgentTemplatingString() + h.pathToCheck = "/v1/secret/data" + h.requestMethodToCheck = "GET" + h.t = t + return &h + }), + }) + cluster.Start() + defer cluster.Cleanup() + + vault.TestWaitActive(t, cluster.Cores[0].Core) + serverClient := cluster.Cores[0].Client + + // Unset the environment variable so that agent picks up the right test + // cluster address + defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) + os.Setenv(api.EnvVaultAddress, serverClient.Address()) + + // Enable the approle auth method + req := serverClient.NewRequest("POST", "/v1/sys/auth/approle") + req.BodyBytes = []byte(`{ + "type": "approle" + }`) + request(t, serverClient, req, 204) + + // give test-role permissions to read the kv secret + req = serverClient.NewRequest("PUT", "/v1/sys/policy/myapp-read") + req.BodyBytes = []byte(`{ + "policy": "path \"secret/*\" { capabilities = [\"read\", \"list\"] }" + }`) + request(t, serverClient, req, 204) + + // Create a named role + req = serverClient.NewRequest("PUT", "/v1/auth/approle/role/test-role") + req.BodyBytes = []byte(`{ + "token_ttl": "5m", + "token_policies":"default,myapp-read", + "policies":"default,myapp-read" + }`) + request(t, serverClient, req, 204) + + // Fetch the RoleID of the named role + req = serverClient.NewRequest("GET", "/v1/auth/approle/role/test-role/role-id") + body := request(t, serverClient, req, 200) + data := body["data"].(map[string]interface{}) + roleID := data["role_id"].(string) + + // Get a SecretID issued against the named role + req = serverClient.NewRequest("PUT", "/v1/auth/approle/role/test-role/secret-id") + body = request(t, serverClient, req, 200) + data = body["data"].(map[string]interface{}) + secretID := data["secret_id"].(string) + + // Write the RoleID and SecretID to temp files + roleIDPath := makeTempFile(t, "role_id.txt", roleID+"\n") + secretIDPath := makeTempFile(t, "secret_id.txt", secretID+"\n") + defer os.Remove(roleIDPath) + defer os.Remove(secretIDPath) + + // setup the kv secrets + req = serverClient.NewRequest("POST", "/v1/sys/mounts/secret/tune") + req.BodyBytes = []byte(`{ + "options": {"version": "2"} + }`) + request(t, serverClient, req, 200) + + // populate a secret + req = serverClient.NewRequest("POST", "/v1/secret/data/myapp") + req.BodyBytes = []byte(`{ + "data": { + "username": "bar", + "password": "zap" + } + }`) + request(t, serverClient, req, 200) + + // populate another secret + req = serverClient.NewRequest("POST", "/v1/secret/data/otherapp") + req.BodyBytes = []byte(`{ + "data": { + "username": "barstuff", + "password": "zap", + "cert": "something" + } + }`) + request(t, serverClient, req, 200) + + // make a temp directory to hold renders. Each test will create a temp dir + // inside this one + tmpDirRoot, err := os.MkdirTemp("", "agent-test-renders") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDirRoot) + // create temp dir for this test run + tmpDir, err := os.MkdirTemp(tmpDirRoot, "TestAgent_Template_UserAgent") + if err != nil { + t.Fatal(err) + } + + // make some template files + var templatePaths []string + fileName := filepath.Join(tmpDir, "render_0.tmpl") + if err := os.WriteFile(fileName, []byte(templateContents(0)), 0o600); err != nil { + t.Fatal(err) + } + templatePaths = append(templatePaths, fileName) + + // build up the template config to be added to the Agent config.hcl file + var templateConfigStrings []string + for i, t := range templatePaths { + index := fmt.Sprintf("render_%d.json", i) + s := fmt.Sprintf(templateConfigString, t, tmpDir, index) + templateConfigStrings = append(templateConfigStrings, s) + } + + // Create a config file + config := ` +vault { + address = "%s" + tls_skip_verify = true +} + +auto_auth { + method "approle" { + mount_path = "auth/approle" + config = { + role_id_file_path = "%s" + secret_id_file_path = "%s" + remove_secret_id_file_after_reading = false + } + } +} + +%s +` + + // flatten the template configs + templateConfig := strings.Join(templateConfigStrings, " ") + + config = fmt.Sprintf(config, serverClient.Address(), roleIDPath, secretIDPath, templateConfig) + configPath := makeTempFile(t, "config.hcl", config) + defer os.Remove(configPath) + + // Start the agent + ui, cmd := testAgentCommand(t, logger) + cmd.client = serverClient + cmd.startedCh = make(chan struct{}) + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + code := cmd.Run([]string{"-config", configPath}) + if code != 0 { + t.Errorf("non-zero return code when running agent: %d", code) + t.Logf("STDOUT from agent:\n%s", ui.OutputWriter.String()) + t.Logf("STDERR from agent:\n%s", ui.ErrorWriter.String()) + } + wg.Done() + }() + + select { + case <-cmd.startedCh: + case <-time.After(5 * time.Second): + t.Errorf("timeout") + } + + // We need to shut down the Agent command + defer func() { + cmd.ShutdownCh <- struct{}{} + wg.Wait() + }() + + verify := func(suffix string) { + t.Helper() + // We need to poll for a bit to give Agent time to render the + // templates. Without this, the test will attempt to read + // the temp dir before Agent has had time to render and will + // likely fail the test + tick := time.Tick(1 * time.Second) + timeout := time.After(10 * time.Second) + var err error + for { + select { + case <-timeout: + t.Fatalf("timed out waiting for templates to render, last error: %v", err) + case <-tick: + } + // Check for files rendered in the directory and break + // early for shutdown if we do have all the files + // rendered + + //---------------------------------------------------- + // Perform the tests + //---------------------------------------------------- + + if numFiles := testListFiles(t, tmpDir, ".json"); numFiles != len(templatePaths) { + err = fmt.Errorf("expected (%d) templates, got (%d)", len(templatePaths), numFiles) + continue + } + + for i := range templatePaths { + fileName := filepath.Join(tmpDir, fmt.Sprintf("render_%d.json", i)) + var c []byte + c, err = os.ReadFile(fileName) + if err != nil { + continue + } + if string(c) != templateRendered(i)+suffix { + err = fmt.Errorf("expected=%q, got=%q", templateRendered(i)+suffix, string(c)) + continue + } + } + return + } + } + + verify("") + + fileName = filepath.Join(tmpDir, "render_0.tmpl") + if err := os.WriteFile(fileName, []byte(templateContents(0)+"{}"), 0o600); err != nil { + t.Fatal(err) + } + + verify("{}") +} + // TestAgent_Template tests rendering templates func TestAgent_Template_Basic(t *testing.T) { //---------------------------------------------------- @@ -1151,6 +1404,27 @@ func (h *handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { vaulthttp.Handler.Handler(h.props).ServeHTTP(resp, req) } +// userAgentHandler makes it easy to test the User-Agent header received +// by Vault +type userAgentHandler struct { + props *vault.HandlerProperties + failCount int + userAgentToCheckFor string + pathToCheck string + requestMethodToCheck string + t *testing.T +} + +func (h *userAgentHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.Method == h.requestMethodToCheck && strings.Contains(req.RequestURI, h.pathToCheck) { + userAgent := req.UserAgent() + if !(userAgent == h.userAgentToCheckFor) { + h.t.Fatalf("User-Agent string not as expected. Expected to find %s, got %s", h.userAgentToCheckFor, userAgent) + } + } + vaulthttp.Handler.Handler(h.props).ServeHTTP(w, req) +} + // TestAgent_Template_Retry verifies that the template server retries requests // based on retry configuration. func TestAgent_Template_Retry(t *testing.T) { @@ -1441,6 +1715,345 @@ auto_auth { return config, cleanup } +// TestAgent_AutoAuth_UserAgent tests that the User-Agent sent +// to Vault by Vault Agent is correct when performing Auto-Auth. +// Uses the custom handler userAgentHandler (defined above) so +// that Vault validates the User-Agent on requests sent by Agent. +func TestAgent_AutoAuth_UserAgent(t *testing.T) { + logger := logging.NewVaultLogger(hclog.Trace) + var h userAgentHandler + cluster := vault.NewTestCluster(t, &vault.CoreConfig{ + Logger: logger, + CredentialBackends: map[string]logical.Factory{ + "approle": credAppRole.Factory, + }, + }, &vault.TestClusterOptions{ + NumCores: 1, + HandlerFunc: vaulthttp.HandlerFunc( + func(properties *vault.HandlerProperties) http.Handler { + h.props = properties + h.userAgentToCheckFor = useragent.AgentAutoAuthString() + h.requestMethodToCheck = "PUT" + h.pathToCheck = "auth/approle/login" + h.t = t + return &h + }), + }) + cluster.Start() + defer cluster.Cleanup() + + serverClient := cluster.Cores[0].Client + + // Enable the approle auth method + req := serverClient.NewRequest("POST", "/v1/sys/auth/approle") + req.BodyBytes = []byte(`{ + "type": "approle" + }`) + request(t, serverClient, req, 204) + + // Create a named role + req = serverClient.NewRequest("PUT", "/v1/auth/approle/role/test-role") + req.BodyBytes = []byte(`{ + "secret_id_num_uses": "10", + "secret_id_ttl": "1m", + "token_max_ttl": "1m", + "token_num_uses": "10", + "token_ttl": "1m", + "policies": "default" + }`) + request(t, serverClient, req, 204) + + // Fetch the RoleID of the named role + req = serverClient.NewRequest("GET", "/v1/auth/approle/role/test-role/role-id") + body := request(t, serverClient, req, 200) + data := body["data"].(map[string]interface{}) + roleID := data["role_id"].(string) + + // Get a SecretID issued against the named role + req = serverClient.NewRequest("PUT", "/v1/auth/approle/role/test-role/secret-id") + body = request(t, serverClient, req, 200) + data = body["data"].(map[string]interface{}) + secretID := data["secret_id"].(string) + + // Write the RoleID and SecretID to temp files + roleIDPath := makeTempFile(t, "role_id.txt", roleID+"\n") + secretIDPath := makeTempFile(t, "secret_id.txt", secretID+"\n") + defer os.Remove(roleIDPath) + defer os.Remove(secretIDPath) + + sinkf, err := os.CreateTemp("", "sink.test.") + if err != nil { + t.Fatal(err) + } + sink := sinkf.Name() + sinkf.Close() + os.Remove(sink) + + autoAuthConfig := fmt.Sprintf(` +auto_auth { + method "approle" { + mount_path = "auth/approle" + config = { + role_id_file_path = "%s" + secret_id_file_path = "%s" + } + } + + sink "file" { + config = { + path = "%s" + } + } +}`, roleIDPath, secretIDPath, sink) + + listenAddr := generateListenerAddress(t) + listenConfig := fmt.Sprintf(` +listener "tcp" { + address = "%s" + tls_disable = true +} +`, listenAddr) + + config := fmt.Sprintf(` +vault { + address = "%s" + tls_skip_verify = true +} +api_proxy { + use_auto_auth_token = true +} +%s +%s +`, serverClient.Address(), listenConfig, autoAuthConfig) + configPath := makeTempFile(t, "config.hcl", config) + defer os.Remove(configPath) + + // Unset the environment variable so that agent picks up the right test + // cluster address + defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) + os.Unsetenv(api.EnvVaultAddress) + + // Start the agent + _, cmd := testAgentCommand(t, logger) + cmd.startedCh = make(chan struct{}) + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + cmd.Run([]string{"-config", configPath}) + wg.Done() + }() + + select { + case <-cmd.startedCh: + case <-time.After(5 * time.Second): + t.Errorf("timeout") + } + + // Validate that the auto-auth token has been correctly attained + // and works for LookupSelf + conf := api.DefaultConfig() + conf.Address = "http://" + listenAddr + agentClient, err := api.NewClient(conf) + if err != nil { + t.Fatalf("err: %s", err) + } + + agentClient.SetToken("") + err = agentClient.SetAddress("http://" + listenAddr) + if err != nil { + t.Fatal(err) + } + + // Wait for the token to be sent to syncs and be available to be used + time.Sleep(5 * time.Second) + + req = agentClient.NewRequest("GET", "/v1/auth/token/lookup-self") + body = request(t, agentClient, req, 200) + + close(cmd.ShutdownCh) + wg.Wait() +} + +// TestAgent_APIProxyWithoutCache_UserAgent tests that the User-Agent sent +// to Vault by Vault Agent is correct using the API proxy without +// the cache configured. Uses the custom handler +// userAgentHandler struct defined in this test package, so that Vault validates the +// User-Agent on requests sent by Agent. +func TestAgent_APIProxyWithoutCache_UserAgent(t *testing.T) { + logger := logging.NewVaultLogger(hclog.Trace) + userAgentForProxiedClient := "proxied-client" + var h userAgentHandler + cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ + NumCores: 1, + HandlerFunc: vaulthttp.HandlerFunc( + func(properties *vault.HandlerProperties) http.Handler { + h.props = properties + h.userAgentToCheckFor = useragent.AgentProxyStringWithProxiedUserAgent(userAgentForProxiedClient) + h.pathToCheck = "/v1/auth/token/lookup-self" + h.requestMethodToCheck = "GET" + h.t = t + return &h + }), + }) + cluster.Start() + defer cluster.Cleanup() + + serverClient := cluster.Cores[0].Client + + // Unset the environment variable so that agent picks up the right test + // cluster address + defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) + os.Unsetenv(api.EnvVaultAddress) + + listenAddr := generateListenerAddress(t) + listenConfig := fmt.Sprintf(` +listener "tcp" { + address = "%s" + tls_disable = true +} +`, listenAddr) + + config := fmt.Sprintf(` +vault { + address = "%s" + tls_skip_verify = true +} +%s +`, serverClient.Address(), listenConfig) + configPath := makeTempFile(t, "config.hcl", config) + defer os.Remove(configPath) + + // Start the agent + _, cmd := testAgentCommand(t, logger) + cmd.startedCh = make(chan struct{}) + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + cmd.Run([]string{"-config", configPath}) + wg.Done() + }() + + select { + case <-cmd.startedCh: + case <-time.After(5 * time.Second): + t.Errorf("timeout") + } + + agentClient, err := api.NewClient(api.DefaultConfig()) + if err != nil { + t.Fatal(err) + } + agentClient.AddHeader("User-Agent", userAgentForProxiedClient) + agentClient.SetToken(serverClient.Token()) + agentClient.SetMaxRetries(0) + err = agentClient.SetAddress("http://" + listenAddr) + if err != nil { + t.Fatal(err) + } + + _, err = agentClient.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + close(cmd.ShutdownCh) + wg.Wait() +} + +// TestAgent_APIProxyWithCache_UserAgent tests that the User-Agent sent +// to Vault by Vault Agent is correct using the API proxy with +// the cache configured. Uses the custom handler +// userAgentHandler struct defined in this test package, so that Vault validates the +// User-Agent on requests sent by Agent. +func TestAgent_APIProxyWithCache_UserAgent(t *testing.T) { + logger := logging.NewVaultLogger(hclog.Trace) + userAgentForProxiedClient := "proxied-client" + var h userAgentHandler + cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ + NumCores: 1, + HandlerFunc: vaulthttp.HandlerFunc( + func(properties *vault.HandlerProperties) http.Handler { + h.props = properties + h.userAgentToCheckFor = useragent.AgentProxyStringWithProxiedUserAgent(userAgentForProxiedClient) + h.pathToCheck = "/v1/auth/token/lookup-self" + h.requestMethodToCheck = "GET" + h.t = t + return &h + }), + }) + cluster.Start() + defer cluster.Cleanup() + + serverClient := cluster.Cores[0].Client + + // Unset the environment variable so that agent picks up the right test + // cluster address + defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) + os.Unsetenv(api.EnvVaultAddress) + + listenAddr := generateListenerAddress(t) + listenConfig := fmt.Sprintf(` +listener "tcp" { + address = "%s" + tls_disable = true +} +`, listenAddr) + + cacheConfig := ` +cache { +}` + + config := fmt.Sprintf(` +vault { + address = "%s" + tls_skip_verify = true +} +%s +%s +`, serverClient.Address(), listenConfig, cacheConfig) + configPath := makeTempFile(t, "config.hcl", config) + defer os.Remove(configPath) + + // Start the agent + _, cmd := testAgentCommand(t, logger) + cmd.startedCh = make(chan struct{}) + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + cmd.Run([]string{"-config", configPath}) + wg.Done() + }() + + select { + case <-cmd.startedCh: + case <-time.After(5 * time.Second): + t.Errorf("timeout") + } + + agentClient, err := api.NewClient(api.DefaultConfig()) + if err != nil { + t.Fatal(err) + } + agentClient.AddHeader("User-Agent", userAgentForProxiedClient) + agentClient.SetToken(serverClient.Token()) + agentClient.SetMaxRetries(0) + err = agentClient.SetAddress("http://" + listenAddr) + if err != nil { + t.Fatal(err) + } + + _, err = agentClient.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + close(cmd.ShutdownCh) + wg.Wait() +} + func TestAgent_Cache_DynamicSecret(t *testing.T) { logger := logging.NewVaultLogger(hclog.Trace) cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ diff --git a/go.mod b/go.mod index d38c311ff..e30590e52 100644 --- a/go.mod +++ b/go.mod @@ -63,8 +63,8 @@ require ( github.com/google/go-metrics-stackdriver v0.2.0 github.com/google/tink/go v1.6.1 github.com/hashicorp/cap v0.2.1-0.20220727210936-60cd1534e220 - github.com/hashicorp/consul-template v0.29.5 - github.com/hashicorp/consul/api v1.17.0 + github.com/hashicorp/consul-template v0.30.1-0.20230322153229-821bc07137f5 + github.com/hashicorp/consul/api v1.18.0 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/eventlogger v0.1.1 github.com/hashicorp/go-cleanhttp v0.5.2 @@ -105,7 +105,7 @@ require ( github.com/hashicorp/hcp-link v0.1.0 github.com/hashicorp/hcp-scada-provider v0.2.1 github.com/hashicorp/hcp-sdk-go v0.23.0 - github.com/hashicorp/nomad/api v0.0.0-20220707195938-75f4c2237b28 + github.com/hashicorp/nomad/api v0.0.0-20230103221135-ce00d683f9be github.com/hashicorp/raft v1.3.10 github.com/hashicorp/raft-autopilot v0.2.0 github.com/hashicorp/raft-boltdb/v2 v2.0.0-20210421194847-a7e34179d62c @@ -192,7 +192,7 @@ require ( go.opentelemetry.io/otel v1.11.2 go.opentelemetry.io/otel/sdk v1.11.2 go.opentelemetry.io/otel/trace v1.11.2 - go.uber.org/atomic v1.9.0 + go.uber.org/atomic v1.10.0 go.uber.org/goleak v1.1.12 golang.org/x/crypto v0.6.0 golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb @@ -247,7 +247,9 @@ require ( github.com/Jeffail/gabs v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect @@ -356,8 +358,8 @@ require ( github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect - github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect - github.com/huandu/xstrings v1.3.2 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.11.0 // indirect @@ -417,11 +419,13 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/snowflakedb/gosnowflake v1.6.3 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 // indirect @@ -448,7 +452,7 @@ require ( golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/text v0.8.0 // indirect - golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect diff --git a/go.sum b/go.sum index 15a6eda2e..76e6b31e0 100644 --- a/go.sum +++ b/go.sum @@ -141,10 +141,13 @@ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= @@ -784,10 +787,10 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/cap v0.2.1-0.20220727210936-60cd1534e220 h1:Vgv3jG0kicczshK+lOHWJ9OososZjnjSu1YslqofFYY= github.com/hashicorp/cap v0.2.1-0.20220727210936-60cd1534e220/go.mod h1:zb3VvIFA0lM2lbmO69NjowV9dJzJnZS89TaM9blXPJA= -github.com/hashicorp/consul-template v0.29.5 h1:tzEo93RqODAX2cgOe/ke8xcpdPdxg5rxl6d22wE3f6c= -github.com/hashicorp/consul-template v0.29.5/go.mod h1:SZGBPz/t0JaBwMOqM6q/mG66cBRA8IeDUjOwjO0Pa5M= -github.com/hashicorp/consul/api v1.17.0 h1:aqytbw31uCPNn37ST+717IyGod+P1eTgSGu3yjRo4bs= -github.com/hashicorp/consul/api v1.17.0/go.mod h1:ZNwemOPAdgtV4cCx9fqxNmw+PI3vliW6gYin2WD+F2g= +github.com/hashicorp/consul-template v0.30.1-0.20230322153229-821bc07137f5 h1:Z5Pzj0hUZzjWtaKATeWzTHfsr+onJEwBVe7JBiJiemM= +github.com/hashicorp/consul-template v0.30.1-0.20230322153229-821bc07137f5/go.mod h1:KfaZbjtDruE5wsV9fBimP30C6xnF3cHJUX7/AQOFgM4= +github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= +github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE= @@ -931,8 +934,8 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0 h1:kBpVVl1sl3MaSrs97e0+pDQhSrqJv9gVbSUrPpVfl1w= github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0/go.mod h1:6pdNz0vo0mF0GvhwDG56O3N18qBrAz/XRIcfINfTbwo= -github.com/hashicorp/nomad/api v0.0.0-20220707195938-75f4c2237b28 h1:fo8EbQ6tc9hYqxik9CAdFMqy48TW8hh2I3znysPqf+0= -github.com/hashicorp/nomad/api v0.0.0-20220707195938-75f4c2237b28/go.mod h1:FslB+3eLbZgkuPWffqO1GeNzBFw1SuVqN2PXsMNe0Fg= +github.com/hashicorp/nomad/api v0.0.0-20230103221135-ce00d683f9be h1:bJ/jBA5pt/5OT1oaApx8B5g/nRyohn61Q8TyUp4PoEI= +github.com/hashicorp/nomad/api v0.0.0-20230103221135-ce00d683f9be/go.mod h1:EM/2XaEwHziSB4NdWZ6MfE65TcvgWwVawOUBT8kVRqE= github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= @@ -1008,11 +1011,13 @@ github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyu github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= -github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -1467,9 +1472,11 @@ github.com/sethvargo/go-limiter v0.7.1 h1:wWNhTj0pxjyJ7wuJHpRJpYwJn+bUnjYfw2a85e github.com/sethvargo/go-limiter v0.7.1/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= github.com/shirou/gopsutil/v3 v3.22.6 h1:FnHOFOh+cYAM0C30P+zysPISzlknLC5Z1G4EAElznfQ= github.com/shirou/gopsutil/v3 v3.22.6/go.mod h1:EdIubSnZhbAvBS1yJ7Xi+AShB/hxwLHOMz4MCYz7yMs= +github.com/shoenig/test v0.5.2 h1:ELZ7qZ/6CPrT71PXrSe2TFzLs4/cGCqqU5lZ5RhZ+B8= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1499,6 +1506,9 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -1643,8 +1653,9 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= @@ -1695,6 +1706,7 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1805,6 +1817,7 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -1939,6 +1952,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1947,6 +1961,7 @@ golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1967,8 +1982,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= -golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/helper/useragent/useragent.go b/helper/useragent/useragent.go index 308527721..bc5565a76 100644 --- a/helper/useragent/useragent.go +++ b/helper/useragent/useragent.go @@ -32,3 +32,45 @@ func String() string { return fmt.Sprintf("Vault/%s (+%s; %s)", versionFunc(), projectURL, rt) } + +// AgentString returns the consistent user-agent string for Vault Agent. +// +// e.g. Vault Agent/0.10.4 (+https://www.vaultproject.io/; go1.10.1) +func AgentString() string { + return fmt.Sprintf("Vault Agent/%s (+%s; %s)", + versionFunc(), projectURL, rt) +} + +// AgentTemplatingString returns the consistent user-agent string for Vault Agent Templating. +// +// e.g. Vault Agent Templating/0.10.4 (+https://www.vaultproject.io/; go1.10.1) +func AgentTemplatingString() string { + return fmt.Sprintf("Vault Agent Templating/%s (+%s; %s)", + versionFunc(), projectURL, rt) +} + +// AgentProxyString returns the consistent user-agent string for Vault Agent API Proxying. +// +// e.g. Vault Agent API Proxy/0.10.4 (+https://www.vaultproject.io/; go1.10.1) +func AgentProxyString() string { + return fmt.Sprintf("Vault Agent API Proxy/%s (+%s; %s)", + versionFunc(), projectURL, rt) +} + +// AgentProxyStringWithProxiedUserAgent returns the consistent user-agent +// string for Vault Agent API Proxying, keeping the User-Agent of the proxied +// client as an extension to this UserAgent +// +// e.g. Vault Agent API Proxy/0.10.4 (+https://www.vaultproject.io/; go1.10.1); proxiedUserAgent +func AgentProxyStringWithProxiedUserAgent(proxiedUserAgent string) string { + return fmt.Sprintf("Vault Agent API Proxy/%s (+%s; %s); %s", + versionFunc(), projectURL, rt, proxiedUserAgent) +} + +// AgentAutoAuthString returns the consistent user-agent string for Vault Agent Auto-Auth. +// +// e.g. Vault Agent Auto-Auth/0.10.4 (+https://www.vaultproject.io/; go1.10.1) +func AgentAutoAuthString() string { + return fmt.Sprintf("Vault Agent Auto-Auth/%s (+%s; %s)", + versionFunc(), projectURL, rt) +} diff --git a/helper/useragent/useragent_test.go b/helper/useragent/useragent_test.go index 795a7ba60..0c95d9519 100644 --- a/helper/useragent/useragent_test.go +++ b/helper/useragent/useragent_test.go @@ -5,6 +5,8 @@ package useragent import ( "testing" + + "github.com/stretchr/testify/require" ) func TestUserAgent(t *testing.T) { @@ -15,7 +17,71 @@ func TestUserAgent(t *testing.T) { act := String() exp := "Vault/1.2.3 (+https://vault-test.com; go5.0)" - if exp != act { - t.Errorf("expected %q to be %q", act, exp) - } + require.Equal(t, exp, act) +} + +// TestUserAgent_VaultAgent tests the AgentString() function works +// as expected +func TestUserAgent_VaultAgent(t *testing.T) { + projectURL = "https://vault-test.com" + rt = "go5.0" + versionFunc = func() string { return "1.2.3" } + + act := AgentString() + + exp := "Vault Agent/1.2.3 (+https://vault-test.com; go5.0)" + require.Equal(t, exp, act) +} + +// TestUserAgent_VaultAgentTemplating tests the AgentTemplatingString() function works +// as expected +func TestUserAgent_VaultAgentTemplating(t *testing.T) { + projectURL = "https://vault-test.com" + rt = "go5.0" + versionFunc = func() string { return "1.2.3" } + + act := AgentTemplatingString() + + exp := "Vault Agent Templating/1.2.3 (+https://vault-test.com; go5.0)" + require.Equal(t, exp, act) +} + +// TestUserAgent_VaultAgentProxy tests the AgentProxyString() function works +// as expected +func TestUserAgent_VaultAgentProxy(t *testing.T) { + projectURL = "https://vault-test.com" + rt = "go5.0" + versionFunc = func() string { return "1.2.3" } + + act := AgentProxyString() + + exp := "Vault Agent API Proxy/1.2.3 (+https://vault-test.com; go5.0)" + require.Equal(t, exp, act) +} + +// TestUserAgent_VaultAgentProxyWithProxiedUserAgent tests the AgentProxyStringWithProxiedUserAgent() +// function works as expected +func TestUserAgent_VaultAgentProxyWithProxiedUserAgent(t *testing.T) { + projectURL = "https://vault-test.com" + rt = "go5.0" + versionFunc = func() string { return "1.2.3" } + userAgent := "my-user-agent" + + act := AgentProxyStringWithProxiedUserAgent(userAgent) + + exp := "Vault Agent API Proxy/1.2.3 (+https://vault-test.com; go5.0); my-user-agent" + require.Equal(t, exp, act) +} + +// TestUserAgent_VaultAgentAutoAuth tests the AgentAutoAuthString() function works +// as expected +func TestUserAgent_VaultAgentAutoAuth(t *testing.T) { + projectURL = "https://vault-test.com" + rt = "go5.0" + versionFunc = func() string { return "1.2.3" } + + act := AgentAutoAuthString() + + exp := "Vault Agent Auto-Auth/1.2.3 (+https://vault-test.com; go5.0)" + require.Equal(t, exp, act) }