diff --git a/.changelog/15745.txt b/.changelog/15745.txt new file mode 100644 index 000000000..95203ef18 --- /dev/null +++ b/.changelog/15745.txt @@ -0,0 +1,3 @@ +```release-note:improvement +vault: configure Nomad User-Agent on vault clients +``` diff --git a/client/fingerprint/vault.go b/client/fingerprint/vault.go index c887ddcef..4639425d9 100644 --- a/client/fingerprint/vault.go +++ b/client/fingerprint/vault.go @@ -8,6 +8,7 @@ import ( log "github.com/hashicorp/go-hclog" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/helper/useragent" vapi "github.com/hashicorp/vault/api" ) @@ -35,18 +36,17 @@ func (f *VaultFingerprint) Fingerprint(req *FingerprintRequest, resp *Fingerprin return nil } - // Only create the client once to avoid creating too many connections to - // Vault. + // Only create the client once to avoid creating too many connections to Vault if f.client == nil { vaultConfig, err := config.VaultConfig.ApiConfig() if err != nil { return fmt.Errorf("Failed to initialize the Vault client config: %v", err) } - f.client, err = vapi.NewClient(vaultConfig) if err != nil { return fmt.Errorf("Failed to initialize Vault client: %s", err) } + useragent.SetHeaders(f.client) } // Connect to vault and parse its information diff --git a/client/vaultclient/vaultclient.go b/client/vaultclient/vaultclient.go index bd742e6e8..7d142db60 100644 --- a/client/vaultclient/vaultclient.go +++ b/client/vaultclient/vaultclient.go @@ -4,13 +4,13 @@ import ( "container/heap" "fmt" "math/rand" - "net/http" "strings" "sync" "time" metrics "github.com/armon/go-metrics" hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/nomad/helper/useragent" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" vaultapi "github.com/hashicorp/vault/api" @@ -21,7 +21,7 @@ import ( // wrapped tokens will be unwrapped using the vault API client. type TokenDeriverFunc func(*structs.Allocation, []string, *vaultapi.Client) (map[string]string, error) -// The interface which nomad client uses to interact with vault and +// VaultClient is the interface which nomad client uses to interact with vault and // periodically renews the tokens and secrets. type VaultClient interface { // Start initiates the renewal loop of tokens and secrets @@ -151,9 +151,8 @@ func NewVaultClient(config *config.VaultConfig, logger hclog.Logger, tokenDerive return nil, err } - client.SetHeaders(http.Header{ - "User-Agent": []string{"hashicorp/nomad"}, - }) + // Set our Nomad user agent + useragent.SetHeaders(client) // SetHeaders above will replace all headers, make this call second if config.Namespace != "" { @@ -193,7 +192,7 @@ func (c *vaultClient) isRunning() bool { return c.running } -// Starts the renewal loop of vault client +// Start starts the renewal loop of vault client func (c *vaultClient) Start() { c.lock.Lock() defer c.lock.Unlock() @@ -207,7 +206,7 @@ func (c *vaultClient) Start() { go c.run() } -// Stops the renewal loop of vault client +// Stop stops the renewal loop of vault client func (c *vaultClient) Stop() { c.lock.Lock() defer c.lock.Unlock() @@ -353,8 +352,7 @@ func (c *vaultClient) renew(req *vaultClientRenewalRequest) error { var renewalErr error leaseDuration := req.increment if req.isToken { - // Set the token in the API client to the one that needs - // renewal + // Set the token in the API client to the one that needs renewal c.client.SetToken(req.id) // Renew the token diff --git a/client/vaultclient/vaultclient_test.go b/client/vaultclient/vaultclient_test.go index 66f711761..ff9ca2877 100644 --- a/client/vaultclient/vaultclient_test.go +++ b/client/vaultclient/vaultclient_test.go @@ -7,10 +7,13 @@ import ( "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/helper/testlog" + "github.com/hashicorp/nomad/helper/useragent" "github.com/hashicorp/nomad/testutil" vaultapi "github.com/hashicorp/vault/api" vaultconsts "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/shoenig/test/must" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -354,3 +357,16 @@ func TestVaultClient_RenewalTime_Short(t *testing.T) { assert.Equal(t, 15*time.Second, renewalTime(dice, 30)) assert.Equal(t, 1*time.Second, renewalTime(dice, 2)) } + +func TestVaultClient_SetUserAgent(t *testing.T) { + ci.Parallel(t) + + conf := config.DefaultConfig() + conf.VaultConfig.Enabled = pointer.Of(true) + logger := testlog.HCLogger(t) + c, err := NewVaultClient(conf.VaultConfig, logger, nil) + must.NoError(t, err) + + ua := c.client.Headers().Get("User-Agent") + must.Eq(t, useragent.String(), ua) +} diff --git a/e2e/e2eutil/client.go b/e2e/e2eutil/client.go index 041e13070..8d63c82b0 100644 --- a/e2e/e2eutil/client.go +++ b/e2e/e2eutil/client.go @@ -5,16 +5,16 @@ import ( capi "github.com/hashicorp/consul/api" napi "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/helper/useragent" vapi "github.com/hashicorp/vault/api" - - "github.com/stretchr/testify/require" + "github.com/shoenig/test/must" ) // NomadClient creates a default Nomad client based on the env vars // from the test environment. Fails the test if it can't be created func NomadClient(t *testing.T) *napi.Client { client, err := napi.NewClient(napi.DefaultConfig()) - require.NoError(t, err, "could not create Nomad client") + must.NoError(t, err) return client } @@ -22,7 +22,7 @@ func NomadClient(t *testing.T) *napi.Client { // from the test environment. Fails the test if it can't be created func ConsulClient(t *testing.T) *capi.Client { client, err := capi.NewClient(capi.DefaultConfig()) - require.NoError(t, err, "could not create Consul client") + must.NoError(t, err) return client } @@ -30,6 +30,7 @@ func ConsulClient(t *testing.T) *capi.Client { // from the test environment. Fails the test if it can't be created func VaultClient(t *testing.T) *vapi.Client { client, err := vapi.NewClient(vapi.DefaultConfig()) - require.NoError(t, err, "could not create Vault client") + useragent.SetHeaders(client) + must.NoError(t, err) return client } diff --git a/e2e/framework/provisioner.go b/e2e/framework/provisioner.go index 524096ffc..5bc0bd1d4 100644 --- a/e2e/framework/provisioner.go +++ b/e2e/framework/provisioner.go @@ -7,6 +7,7 @@ import ( capi "github.com/hashicorp/consul/api" napi "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/helper/useragent" "github.com/hashicorp/nomad/helper/uuid" vapi "github.com/hashicorp/vault/api" ) @@ -115,6 +116,7 @@ func (p *singleClusterProvisioner) SetupTestCase(t *testing.T, opts SetupOptions if err != nil && opts.ExpectVault { return nil, err } + useragent.SetHeaders(vaultClient) info.VaultClient = vaultClient } else if opts.ExpectVault { return nil, fmt.Errorf("vault client expected but environment variable %s not set", diff --git a/helper/useragent/useragent.go b/helper/useragent/useragent.go index 149ae5366..1720e96ff 100644 --- a/helper/useragent/useragent.go +++ b/helper/useragent/useragent.go @@ -2,6 +2,7 @@ package useragent import ( "fmt" + "net/http" "runtime" "github.com/hashicorp/nomad/version" @@ -27,3 +28,15 @@ func String() string { return fmt.Sprintf("Nomad/%s (+%s; %s)", versionFunc(), projectURL, rt) } + +// HeaderSetter is anything that implements SetHeaders(http.Header). +type HeaderSetter interface { + SetHeaders(http.Header) +} + +// SetHeaders configures the User-Agent http.Header for the client. +func SetHeaders(client HeaderSetter) { + client.SetHeaders(http.Header{ + "User-Agent": []string{String()}, + }) +} diff --git a/nomad/vault.go b/nomad/vault.go index 41a954739..9f2307d00 100644 --- a/nomad/vault.go +++ b/nomad/vault.go @@ -12,6 +12,7 @@ import ( "time" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/helper/useragent" tomb "gopkg.in/tomb.v2" metrics "github.com/armon/go-metrics" @@ -452,6 +453,7 @@ func (v *vaultClient) buildClient() error { v.logger.Error("failed to create Vault client and not retrying", "error", err) return err } + useragent.SetHeaders(client) // Store the client, create/assign the /sys client v.client = client @@ -462,6 +464,7 @@ func (v *vaultClient) buildClient() error { v.logger.Error("failed to create Vault sys client and not retrying", "error", err) return err } + useragent.SetHeaders(v.clientSys) client.SetNamespace(v.config.Namespace) } else { v.clientSys = client diff --git a/testutil/vault.go b/testutil/vault.go index 566641cd3..ccc8b15cd 100644 --- a/testutil/vault.go +++ b/testutil/vault.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/testlog" + "github.com/hashicorp/nomad/helper/useragent" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/structs/config" vapi "github.com/hashicorp/vault/api" @@ -57,6 +58,7 @@ func NewTestVaultFromPath(t testing.T, binary string) *TestVault { t.Fatalf("failed to build Vault API client: %v", err) } client.SetToken(token) + useragent.SetHeaders(client) enable := true tv := &TestVault{ @@ -133,6 +135,7 @@ func NewTestVaultDelayed(t testing.T) *TestVault { t.Fatalf("failed to build Vault API client: %v", err) } client.SetToken(token) + useragent.SetHeaders(client) enable := true tv := &TestVault{