VAULT-5935 agent: redact renew-self if using auto auth (#15380)

Vault agent redacts the token and accessor for `/auth/token/lookup-self` (and `lookup`)
if the token is the auto auth token to prevent it from leaking.

Similarly, we need to redact the token and accessor from `renew-self`
and `renew`, which also leak the token and accessor.

I tested this locally by starting up a Vault agent and querying the
agent endpoints, and ensuring that the accessor and token were set to
the empty string in the response.
This commit is contained in:
Christopher Swenson 2022-05-12 09:25:55 -07:00 committed by GitHub
parent 8497010acd
commit 0af0543bbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 19 deletions

3
changelog/15380.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:bug
agent: Redact auto auth token from renew endpoints
```

View file

@ -2,6 +2,7 @@ package cache
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
@ -13,7 +14,7 @@ import (
"time" "time"
"github.com/go-test/deep" "github.com/go-test/deep"
hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
kv "github.com/hashicorp/vault-plugin-secrets-kv" kv "github.com/hashicorp/vault-plugin-secrets-kv"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/credential/userpass" "github.com/hashicorp/vault/builtin/credential/userpass"
@ -214,9 +215,13 @@ func tokenRevocationValidation(t *testing.T, sampleSpace map[string]string, expe
func TestCache_AutoAuthTokenStripping(t *testing.T) { func TestCache_AutoAuthTokenStripping(t *testing.T) {
response1 := `{"data": {"id": "testid", "accessor": "testaccessor", "request": "lookup-self"}}` response1 := `{"data": {"id": "testid", "accessor": "testaccessor", "request": "lookup-self"}}`
response2 := `{"data": {"id": "testid", "accessor": "testaccessor", "request": "lookup"}}` response2 := `{"data": {"id": "testid", "accessor": "testaccessor", "request": "lookup"}}`
response3 := `{"auth": {"client_token": "testid", "accessor": "testaccessor"}}`
response4 := `{"auth": {"client_token": "testid", "accessor": "testaccessor"}}`
responses := []*SendResponse{ responses := []*SendResponse{
newTestSendResponse(http.StatusOK, response1), newTestSendResponse(http.StatusOK, response1),
newTestSendResponse(http.StatusOK, response2), newTestSendResponse(http.StatusOK, response2),
newTestSendResponse(http.StatusOK, response3),
newTestSendResponse(http.StatusOK, response4),
} }
leaseCache := testNewLeaseCache(t, responses) leaseCache := testNewLeaseCache(t, responses)
@ -279,6 +284,30 @@ func TestCache_AutoAuthTokenStripping(t *testing.T) {
if secret.Data["id"] != nil || secret.Data["accessor"] != nil || secret.Data["request"].(string) != "lookup" { if secret.Data["id"] != nil || secret.Data["accessor"] != nil || secret.Data["request"].(string) != "lookup" {
t.Fatalf("failed to strip off auto-auth token on lookup") t.Fatalf("failed to strip off auto-auth token on lookup")
} }
secret, err = testClient.Auth().Token().RenewSelf(1)
if err != nil {
t.Fatal(err)
}
if secret.Auth == nil {
secretJson, _ := json.Marshal(secret)
t.Fatalf("Expected secret to have Auth but was %s", secretJson)
}
if secret.Auth.ClientToken != "" || secret.Auth.Accessor != "" {
t.Fatalf("failed to strip off auto-auth token on renew-self")
}
secret, err = testClient.Auth().Token().Renew("testid", 1)
if err != nil {
t.Fatal(err)
}
if secret.Auth == nil {
secretJson, _ := json.Marshal(secret)
t.Fatalf("Expected secret to have Auth but was %s", secretJson)
}
if secret.Auth.ClientToken != "" || secret.Auth.Accessor != "" {
t.Fatalf("failed to strip off auto-auth token on renew")
}
} }
func TestCache_AutoAuthClientTokenProxyStripping(t *testing.T) { func TestCache_AutoAuthClientTokenProxyStripping(t *testing.T) {

View file

@ -13,7 +13,7 @@ import (
"time" "time"
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/agent/sink" "github.com/hashicorp/vault/command/agent/sink"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
@ -67,7 +67,7 @@ func Handler(ctx context.Context, logger hclog.Logger, proxier Proxier, inmemSin
return return
} }
err = processTokenLookupResponse(ctx, logger, inmemSink, req, resp) err = sanitizeAutoAuthTokenResponse(ctx, logger, inmemSink, req, resp)
if err != nil { if err != nil {
logical.RespondError(w, http.StatusInternalServerError, fmt.Errorf("failed to process token lookup response: %w", err)) logical.RespondError(w, http.StatusInternalServerError, fmt.Errorf("failed to process token lookup response: %w", err))
return return
@ -120,11 +120,10 @@ func setHeaders(w http.ResponseWriter, resp *SendResponse) {
w.WriteHeader(resp.Response.StatusCode) w.WriteHeader(resp.Response.StatusCode)
} }
// processTokenLookupResponse checks if the request was one of token // sanitizeAutoAuthTokenResponse checks if the request was a lookup or renew
// lookup-self. If the auto-auth token was used to perform lookup-self, the // and if the auto-auth token was used to perform lookup-self, the identifier
// identifier of the token and its accessor same will be stripped off of the // of the token and its accessor same will be stripped off of the response.
// response. func sanitizeAutoAuthTokenResponse(ctx context.Context, logger hclog.Logger, inmemSink sink.Sink, req *SendRequest, resp *SendResponse) error {
func processTokenLookupResponse(ctx context.Context, logger hclog.Logger, inmemSink sink.Sink, req *SendRequest, resp *SendResponse) error {
// If auto-auth token is not being used, there is nothing to do. // If auto-auth token is not being used, there is nothing to do.
if inmemSink == nil { if inmemSink == nil {
return nil return nil
@ -138,11 +137,11 @@ func processTokenLookupResponse(ctx context.Context, logger hclog.Logger, inmemS
_, path := deriveNamespaceAndRevocationPath(req) _, path := deriveNamespaceAndRevocationPath(req)
switch path { switch path {
case vaultPathTokenLookupSelf: case vaultPathTokenLookupSelf, vaultPathTokenRenewSelf:
if req.Token != autoAuthToken { if req.Token != autoAuthToken {
return nil return nil
} }
case vaultPathTokenLookup: case vaultPathTokenLookup, vaultPathTokenRenew:
jsonBody := map[string]interface{}{} jsonBody := map[string]interface{}{}
if err := json.Unmarshal(req.RequestBody, &jsonBody); err != nil { if err := json.Unmarshal(req.RequestBody, &jsonBody); err != nil {
return err return err
@ -170,15 +169,26 @@ func processTokenLookupResponse(ctx context.Context, logger hclog.Logger, inmemS
if err != nil { if err != nil {
return fmt.Errorf("failed to parse token lookup response: %v", err) return fmt.Errorf("failed to parse token lookup response: %v", err)
} }
if secret == nil || secret.Data == nil { if secret == nil {
return nil return nil
} } else if secret.Data != nil {
// lookup endpoints
if secret.Data["id"] == nil && secret.Data["accessor"] == nil { if secret.Data["id"] == nil && secret.Data["accessor"] == nil {
return nil return nil
} }
delete(secret.Data, "id") delete(secret.Data, "id")
delete(secret.Data, "accessor") delete(secret.Data, "accessor")
} else if secret.Auth != nil {
// renew endpoints
if secret.Auth.Accessor == "" && secret.Auth.ClientToken == "" {
return nil
}
secret.Auth.Accessor = ""
secret.Auth.ClientToken = ""
} else {
// nothing to redact
return nil
}
bodyBytes, err := json.Marshal(secret) bodyBytes, err := json.Marshal(secret)
if err != nil { if err != nil {

View file

@ -16,12 +16,12 @@ import (
"sync" "sync"
"time" "time"
hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/base62" "github.com/hashicorp/go-secure-stdlib/base62"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/agent/cache/cacheboltdb" "github.com/hashicorp/vault/command/agent/cache/cacheboltdb"
cachememdb "github.com/hashicorp/vault/command/agent/cache/cachememdb" "github.com/hashicorp/vault/command/agent/cache/cachememdb"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
nshelper "github.com/hashicorp/vault/helper/namespace" nshelper "github.com/hashicorp/vault/helper/namespace"
vaulthttp "github.com/hashicorp/vault/http" vaulthttp "github.com/hashicorp/vault/http"
@ -42,6 +42,8 @@ const (
vaultPathTokenRevokeOrphan = "/v1/auth/token/revoke-orphan" vaultPathTokenRevokeOrphan = "/v1/auth/token/revoke-orphan"
vaultPathTokenLookup = "/v1/auth/token/lookup" vaultPathTokenLookup = "/v1/auth/token/lookup"
vaultPathTokenLookupSelf = "/v1/auth/token/lookup-self" vaultPathTokenLookupSelf = "/v1/auth/token/lookup-self"
vaultPathTokenRenew = "/v1/auth/token/renew"
vaultPathTokenRenewSelf = "/v1/auth/token/renew-self"
vaultPathLeaseRevoke = "/v1/sys/leases/revoke" vaultPathLeaseRevoke = "/v1/sys/leases/revoke"
vaultPathLeaseRevokeForce = "/v1/sys/leases/revoke-force" vaultPathLeaseRevokeForce = "/v1/sys/leases/revoke-force"
vaultPathLeaseRevokePrefix = "/v1/sys/leases/revoke-prefix" vaultPathLeaseRevokePrefix = "/v1/sys/leases/revoke-prefix"