diff --git a/audit/entry_formatter.go b/audit/entry_formatter.go index 85c91b593b..65f48fe4f8 100644 --- a/audit/entry_formatter.go +++ b/audit/entry_formatter.go @@ -255,6 +255,10 @@ func (f *EntryFormatter) FormatRequest(ctx context.Context, in *logical.LogInput }, } + if req.HTTPRequest != nil && req.HTTPRequest.RequestURI != req.Path { + reqEntry.Request.RequestURI = req.HTTPRequest.RequestURI + } + if !auth.IssueTime.IsZero() { reqEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339) } @@ -472,6 +476,10 @@ func (f *EntryFormatter) FormatResponse(ctx context.Context, in *logical.LogInpu }, } + if req.HTTPRequest != nil && req.HTTPRequest.RequestURI != req.Path { + respEntry.Request.RequestURI = req.HTTPRequest.RequestURI + } + if auth.PolicyResults != nil { respEntry.Auth.PolicyResults = &PolicyResults{ Allowed: auth.PolicyResults.Allowed, diff --git a/audit/types.go b/audit/types.go index 956bf35c0a..b42296d0f4 100644 --- a/audit/types.go +++ b/audit/types.go @@ -195,6 +195,7 @@ type Request struct { WrapTTL int `json:"wrap_ttl,omitempty"` Headers map[string][]string `json:"headers,omitempty"` ClientCertificateSerialNumber string `json:"client_certificate_serial_number,omitempty"` + RequestURI string `json:"request_uri,omitempty"` } type Response struct { diff --git a/http/handler.go b/http/handler.go index 2e1db26aa0..d2c36e0d73 100644 --- a/http/handler.go +++ b/http/handler.go @@ -347,7 +347,8 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr // return an HTTP error here. This information is best effort. hostname, _ := os.Hostname() - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var hf func(w http.ResponseWriter, r *http.Request) + hf = func(w http.ResponseWriter, r *http.Request) { // This block needs to be here so that upon sending SIGHUP, custom response // headers are also reloaded into the handlers. var customHeaders map[string][]*logical.CustomHeader @@ -422,16 +423,9 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr core.Logger().Warn("error resolving potential API redirect", "error", err) } else { if redir != "" { - dest := url.URL{ - Path: redir, - RawQuery: r.URL.RawQuery, - } - w.Header().Set("Location", dest.String()) - if r.Method == http.MethodGet || r.Proto == "HTTP/1.0" { - w.WriteHeader(http.StatusFound) - } else { - w.WriteHeader(http.StatusTemporaryRedirect) - } + newReq := r.Clone(ctx) + newReq.URL.Path = redir + hf(w, newReq) cancelFunc() return } @@ -487,7 +481,8 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr h.ServeHTTP(nw, r) cancelFunc() - }) + } + return http.HandlerFunc(hf) } func WrapForwardedForHandler(h http.Handler, l *configutil.Listener) http.Handler { diff --git a/http/logical.go b/http/logical.go index 6ef1daa7a6..4958ce5c14 100644 --- a/http/logical.go +++ b/http/logical.go @@ -110,10 +110,8 @@ func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http. // add the HTTP request to the logical request object for later consumption. contentType := r.Header.Get("Content-Type") - if ra != nil && ra.IsBinaryPath(r.Context(), path) { - passHTTPReq = true - origBody = r.Body - } else if path == "sys/storage/raft/snapshot" || path == "sys/storage/raft/snapshot-force" { + if (ra != nil && ra.IsBinaryPath(r.Context(), path)) || + path == "sys/storage/raft/snapshot" || path == "sys/storage/raft/snapshot-force" { passHTTPReq = true origBody = r.Body } else { @@ -194,6 +192,11 @@ func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http. return nil, nil, http.StatusMethodNotAllowed, nil } + // RFC 5785 Redirect, keep the request for auditing purposes + if r.URL.Path != r.RequestURI { + passHTTPReq = true + } + requestId, err := uuid.GenerateUUID() if err != nil { return nil, nil, http.StatusInternalServerError, fmt.Errorf("failed to generate identifier for the request: %w", err) diff --git a/vault/external_tests/router/router_ext_test.go b/vault/external_tests/router/router_ext_test.go index b770382d7e..73d335784b 100644 --- a/vault/external_tests/router/router_ext_test.go +++ b/vault/external_tests/router/router_ext_test.go @@ -6,8 +6,12 @@ package router import ( "context" "net/http" + "strings" "testing" + "github.com/hashicorp/vault/audit" + "github.com/hashicorp/vault/helper/testhelpers/corehelpers" + "github.com/hashicorp/vault/helper/testhelpers" vaulthttp "github.com/hashicorp/vault/http" @@ -90,7 +94,11 @@ func TestRouter_UnmountRollbackIsntFatal(t *testing.T) { } func TestWellKnownRedirect_HA(t *testing.T) { + var records *[][]byte cluster := vault.NewTestCluster(t, &vault.CoreConfig{ + AuditBackends: map[string]audit.Factory{ + "noop": corehelpers.NoopAuditFactory(&records), + }, DisablePerformanceStandby: true, LogicalBackends: map[string]logical.Factory{ "noop": func(_ context.Context, _ *logical.BackendConfig) (logical.Backend, error) { @@ -114,6 +122,12 @@ func TestWellKnownRedirect_HA(t *testing.T) { standbys := testhelpers.DeriveStandbyCores(t, cluster) standby := standbys[0].Client + if err := active.Client.Sys().EnableAuditWithOptions("noop", &api.EnableAuditOptions{ + Type: "noop", + }); err != nil { + t.Fatalf("failed to enable audit: %v", err) + } + if err := active.Client.Sys().Mount("noop", &api.MountInput{ Type: "noop", }); err != nil { @@ -143,4 +157,15 @@ func TestWellKnownRedirect_HA(t *testing.T) { } else if resp2.StatusCode != http.StatusOK { t.Fatal("did not get expected response from noop backend after redirect") } + + if len(*records) < 2 { + t.Fatal("audit entries not populated") + } else { + rs := *records + // Make sure RequestURI is present in the redirect audit entries + if !strings.Contains(string(rs[len(rs)-1]), "request_uri\":\"/.well-known/foo/baz") || + !strings.Contains(string(rs[len(rs)-2]), "request_uri\":\"/.well-known/foo/baz") { + t.Fatal("did not find request_uri in audit entries") + } + } }