Skip to content

Commit aad80b8

Browse files
authored
set user agent for outbound requests (#98)
1 parent 313bd37 commit aad80b8

8 files changed

Lines changed: 107 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## Unreleased
22

3+
IMPROVEMENTS:
4+
* Requests from the extension to Vault now set the User-Agent field accordingly.
5+
36
## 0.9.0 (February 23, 2023)
47

58
IMPROVEMENTS:

internal/config/info.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package config
2+
3+
const (
4+
ExtensionName = "vault-lambda-extension"
5+
ExtensionVersion = "0.9.0"
6+
VaultLogLevel = "VAULT_LOG_LEVEL" // Optional, one of TRACE, DEBUG, INFO, WARN, ERROR, OFF
7+
)

internal/config/useragent.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
)
7+
8+
// GetUserAgentBase returns a base user agent string with the given user agent name and version in the form:
9+
// vault-client-go/0.0.1 (Darwin arm64; Go go1.19.2)
10+
func GetUserAgentBase(clientName string, clientVersion string) string {
11+
return fmt.Sprintf("%s/%s (%s %s; Go %s)", clientName, clientVersion, runtime.GOOS, runtime.GOARCH, runtime.Version())
12+
}

internal/proxy/proxy.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ func proxyHandler(logger hclog.Logger, client *vault.Client, cache *Cache) func(
8383
}
8484
}
8585

86+
// add user agent header
87+
ua := config.GetUserAgentBase(client.Name, client.Version)
88+
fwReq.Header.Set("User-Agent", ua+"; requesting from proxy")
89+
8690
resp, err := client.VaultConfig.HttpClient.Do(fwReq)
8791
if err != nil {
8892
http.Error(w, fmt.Sprintf("failed to proxy request: %s", err), http.StatusBadGateway)

internal/proxy/proxy_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type vaultResponse struct {
3030
}
3131

3232
var (
33+
vaultRequests []*http.Request
3334
fakeVaultResponse vaultResponse
3435
vaultResponseFooBar = vaultResponse{
3536
secret: &api.Secret{
@@ -69,9 +70,14 @@ func TestProxy(t *testing.T) {
6970
defer close()
7071

7172
t.Run("happy path bare http client", func(t *testing.T) {
73+
// reset request array
74+
vaultRequests = []*http.Request{}
7275
fakeVaultResponse = vaultResponseFooBar
7376
resp, err := http.Get(fmt.Sprintf("http://%s/v1/secret/data/foo", proxyAddr))
7477

78+
// the stored request should be the one _from the proxy_ since it's stored by
79+
// the (fake) vault.
80+
require.NotEmpty(t, vaultRequests[0].Header.Get("User-Agent"))
7581
require.NoError(t, err)
7682
require.Equal(t, http.StatusOK, resp.StatusCode)
7783
defer resp.Body.Close()
@@ -83,12 +89,15 @@ func TestProxy(t *testing.T) {
8389
})
8490

8591
t.Run("happy path with vault client", func(t *testing.T) {
92+
vaultRequests = []*http.Request{}
8693
fakeVaultResponse = vaultResponseFooBar
8794
proxyVaultClient, err := api.NewClient(&api.Config{
8895
Address: "http://" + proxyAddr,
8996
})
9097
require.NoError(t, err)
9198
resp, err := proxyVaultClient.Logical().Read("secret/data/foo")
99+
100+
require.NotEmpty(t, vaultRequests[0].Header.Get("User-Agent"))
92101
require.NoError(t, err)
93102
require.Equal(t, "bar", resp.Data["foo"])
94103
})
@@ -103,6 +112,7 @@ func TestProxy(t *testing.T) {
103112
})
104113

105114
t.Run("failed upstream request should give 502", func(t *testing.T) {
115+
106116
fakeVaultResponse = vaultResponseFooBar
107117
resp, err := http.Get(fmt.Sprintf("http://%s/FailedTransport", proxyAddr))
108118
require.NoError(t, err)
@@ -114,7 +124,7 @@ func startProxy(t *testing.T, vaultAddress string, ses *session.Session) (string
114124
vaultConfig := api.DefaultConfig()
115125
require.NoError(t, vaultConfig.Error)
116126
vaultConfig.Address = vaultAddress
117-
client, err := vault.NewClient(hclog.NewNullLogger(), vaultConfig, config.AuthConfig{}, ses)
127+
client, err := vault.NewClient("", "", hclog.NewNullLogger(), vaultConfig, config.AuthConfig{}, ses)
118128
require.NoError(t, err)
119129
client.VaultConfig.Address = vaultAddress
120130
ln, err := net.Listen("tcp", "127.0.0.1:0")
@@ -130,6 +140,10 @@ func startProxy(t *testing.T, vaultAddress string, ses *session.Session) (string
130140
func fakeVault() *httptest.Server {
131141
vaultResponsePtr := &fakeVaultResponse
132142
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
143+
// after handling, save the request so we can inspect it in test cases
144+
defer func() {
145+
vaultRequests = append(vaultRequests, r)
146+
}()
133147
switch {
134148
case strings.Contains(r.URL.Path, "login"):
135149
b, err := json.Marshal(vaultLoginResponse)

internal/vault/client.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ const (
3232

3333
// Client holds api.Client and handles state required to renew tokens and re-auth as required.
3434
type Client struct {
35+
Name string
36+
Version string
37+
3538
mtx sync.Mutex
3639

3740
VaultClient *api.Client
@@ -50,7 +53,7 @@ type Client struct {
5053

5154
// NewClient uses the AWS IAM auth method configured in a Vault cluster to
5255
// authenticate the execution role and create a Vault API client.
53-
func NewClient(logger hclog.Logger, vaultConfig *api.Config, authConfig config.AuthConfig, awsSes *session.Session) (*Client, error) {
56+
func NewClient(name, version string, logger hclog.Logger, vaultConfig *api.Config, authConfig config.AuthConfig, awsSes *session.Session) (*Client, error) {
5457
vaultClient, err := api.NewClient(vaultConfig)
5558
if err != nil {
5659
return nil, fmt.Errorf("error making extension: %w", err)
@@ -64,6 +67,8 @@ func NewClient(logger hclog.Logger, vaultConfig *api.Config, authConfig config.A
6467
client := &Client{
6568
VaultClient: vaultClient,
6669
VaultConfig: vaultConfig,
70+
Name: name,
71+
Version: version,
6772

6873
logger: logger,
6974
stsSvc: sts.New(awsSes),
@@ -238,3 +243,11 @@ func parseTokenExpiryGracePeriod() (time.Duration, error) {
238243

239244
return expiryGracePeriod, nil
240245
}
246+
247+
// UserAgentRequestCallback takes a function that returns a user agent string and will invoke that function to set
248+
// the user agent string on the request.
249+
func UserAgentRequestCallback(agentFunc func(request *api.Request) string) api.RequestCallback {
250+
return func(req *api.Request) {
251+
req.Headers.Set("User-Agent", agentFunc(req))
252+
}
253+
}

internal/vault/client_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,49 @@ func TestParseTokenExpiryGracePeriod(t *testing.T) {
296296
require.Error(t, err)
297297
}
298298

299+
const userAgent = "abcd"
300+
301+
func TestUserAgentHeaderAddition(t *testing.T) {
302+
vault := fakeVault()
303+
generateVaultClient := func() *api.Client {
304+
vaultClient, err := api.NewClient(&api.Config{
305+
Address: vault.URL,
306+
})
307+
require.NoError(t, err)
308+
return vaultClient
309+
}
310+
311+
t.Run("Ensure request contains header if decorator set", func(t *testing.T) {
312+
vaultRequests = []*http.Request{}
313+
vaultClient := generateVaultClient()
314+
vaultClient.SetToken(t.Name())
315+
c := Client{
316+
VaultClient: vaultClient,
317+
logger: hclog.Default(),
318+
//stsSvc: stsSvc,
319+
320+
tokenRenewable: true,
321+
tokenExpiry: time.Now().Add(time.Hour),
322+
tokenTTL: 10 * time.Hour,
323+
}
324+
secretFunc = generateSecretFunc(t, []*api.Secret{
325+
with10hLease,
326+
})
327+
c.VaultClient = c.VaultClient.WithRequestCallbacks(UserAgentRequestCallback(fakeUserAgent))
328+
329+
_, err := c.Token(context.Background())
330+
require.NoError(t, err)
331+
332+
// validate request was set and the user agent is what we expect
333+
require.Equal(t, 1, len(vaultRequests))
334+
require.Equal(t, userAgent, vaultRequests[0].Header.Get("User-Agent"))
335+
})
336+
}
337+
338+
func fakeUserAgent(_ *api.Request) string {
339+
return userAgent
340+
}
341+
299342
func fakeVault() *httptest.Server {
300343
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
301344
defer func() {

main.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,12 @@ import (
2828
"github.com/hashicorp/vault/api"
2929
)
3030

31-
const (
32-
extensionName = "vault-lambda-extension"
33-
vaultLogLevel = "VAULT_LOG_LEVEL" // Optional, one of TRACE, DEBUG, INFO, WARN, ERROR, OFF
34-
)
35-
3631
func main() {
3732
logger := hclog.New(&hclog.LoggerOptions{
38-
Level: hclog.LevelFromString(os.Getenv(vaultLogLevel)),
33+
Level: hclog.LevelFromString(os.Getenv(config.VaultLogLevel)),
3934
})
4035

41-
err := realMain(logger.Named(extensionName))
36+
err := realMain(logger.Named(config.VaultLogLevel))
4237
if err != nil {
4338
logger.Error("Fatal error, exiting", "error", err)
4439
os.Exit(1)
@@ -76,7 +71,7 @@ func realMain(logger hclog.Logger) error {
7671
}()
7772

7873
extensionClient := extension.NewClient(os.Getenv("AWS_LAMBDA_RUNTIME_API"))
79-
_, err = extensionClient.Register(ctx, extensionName)
74+
_, err = extensionClient.Register(ctx, config.ExtensionName)
8075
if err != nil {
8176
return err
8277
}
@@ -119,7 +114,7 @@ func runExtension(ctx context.Context, logger hclog.Logger, wg *sync.WaitGroup)
119114
} else {
120115
ses = session.Must(session.NewSession())
121116
}
122-
client, err := vault.NewClient(logger.Named("vault-client"), vaultConfig, authConfig, ses)
117+
client, err := vault.NewClient(config.ExtensionName, config.ExtensionVersion, logger.Named("vault-client"), vaultConfig, authConfig, ses)
123118
if err != nil {
124119
return nil, fmt.Errorf("error getting client: %w", err)
125120
} else if client == nil {
@@ -134,7 +129,11 @@ func runExtension(ctx context.Context, logger hclog.Logger, wg *sync.WaitGroup)
134129
return nil, fmt.Errorf("error logging in to Vault: %w", err)
135130
}
136131

137-
client.VaultClient = client.VaultClient.WithRequestCallbacks(api.RequireState(newState)).WithResponseCallbacks()
132+
uaFunc := func(request *api.Request) string {
133+
return config.GetUserAgentBase(config.ExtensionName, config.ExtensionVersion) + "; writing to temp file"
134+
}
135+
136+
client.VaultClient = client.VaultClient.WithRequestCallbacks(api.RequireState(newState), vault.UserAgentRequestCallback(uaFunc)).WithResponseCallbacks()
138137

139138
err = writePreconfiguredSecrets(client.VaultClient)
140139
if err != nil {

0 commit comments

Comments
 (0)