Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vault Agent Cache Auto-Auth SSRF Protection #7627

Merged
merged 29 commits into from
Oct 11, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
853a109
implement SSRF protection header
mjarmy Oct 3, 2019
84e3086
add test for SSRF protection header
mjarmy Oct 4, 2019
b499b32
cleanup
mjarmy Oct 4, 2019
6f07e18
refactor
mjarmy Oct 8, 2019
7044ac9
merge from master
mjarmy Oct 8, 2019
9adadc5
implement SSRF header on a per-listener basis
mjarmy Oct 9, 2019
d10eef8
cleanup
mjarmy Oct 9, 2019
0e5f902
cleanup
mjarmy Oct 9, 2019
4a03773
creat unit test for agent SSRF
mjarmy Oct 10, 2019
4ca14d8
improve unit test for agent SSRF
mjarmy Oct 10, 2019
0491fec
add VaultRequest SSRF header to CLI
mjarmy Oct 10, 2019
91e22a5
merge from master
mjarmy Oct 10, 2019
2125a20
fix unit test
mjarmy Oct 10, 2019
467698c
cleanup
mjarmy Oct 10, 2019
199ff09
merge from master
mjarmy Oct 11, 2019
a915b64
improve test suite
mjarmy Oct 11, 2019
34ff6dd
simplify check for Vault-Request header
mjarmy Oct 11, 2019
6d9c4a1
add constant for Vault-Request header
mjarmy Oct 11, 2019
22e199e
improve test suite
mjarmy Oct 11, 2019
14ee72d
change 'config' to 'agentConfig'
mjarmy Oct 11, 2019
5ea8878
Revert "change 'config' to 'agentConfig'"
mjarmy Oct 11, 2019
3e6acbc
do not remove header from request
mjarmy Oct 11, 2019
15ca067
change header name to X-Vault-Request
mjarmy Oct 11, 2019
44b75af
merge from master
mjarmy Oct 11, 2019
12c8c2b
simplify http.Handler logic
mjarmy Oct 11, 2019
335c5ec
cleanup
mjarmy Oct 11, 2019
ca26eba
simplify http.Handler logic
mjarmy Oct 11, 2019
14559e5
use stdlib errors package
mjarmy Oct 11, 2019
28e9dbc
merge from master
mjarmy Oct 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ func NewClient(c *Config) (*Client, error) {
}

// Add the VaultRequest SSRF protection header
client.headers["Vault-Request"] = []string{"true"}
client.headers[consts.VaultRequestHeader] = []string{"true"}

if token := os.Getenv(EnvVaultToken); token != "" {
client.token = token
Expand Down
6 changes: 2 additions & 4 deletions command/agent/cache/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"io"
"io/ioutil"
"net/http"
"reflect"
"time"

"github.com/hashicorp/errwrap"
Expand All @@ -27,14 +26,13 @@ func Handler(ctx context.Context, logger hclog.Logger, proxier Proxier, inmemSin

// Check for the required request header
if requireRequestHeader {
val, ok := r.Header["Vault-Request"]
if !ok || !reflect.DeepEqual(val, []string{"true"}) {
if val, ok := r.Header[consts.VaultRequestHeader]; !ok || len(val) != 1 || val[0] != "true" {
logical.RespondError(w, http.StatusPreconditionFailed, errors.New("missing 'Vault-Request' header"))
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
return
}

// Remove the required request header
delete(r.Header, "Vault-Request")
delete(r.Header, consts.VaultRequestHeader)
}

// Get token from the header
Expand Down
40 changes: 31 additions & 9 deletions command/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"reflect"
"sync"
"testing"
"time"
Expand All @@ -15,6 +17,7 @@ import (
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
"github.com/hashicorp/vault/command/agent"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
Expand Down Expand Up @@ -377,7 +380,7 @@ auto_auth {

func TestAgent_RequireRequestHeader(t *testing.T) {

// request is a helper function that issues HTTP requests.
// request issues HTTP requests.
request := func(client *api.Client, req *api.Request, expectedStatusCode int) map[string]interface{} {
resp, err := client.RawRequest(req)
if err != nil {
Expand All @@ -403,8 +406,7 @@ func TestAgent_RequireRequestHeader(t *testing.T) {
return body
}

// makeTempFile is a helper function that creates a temp file and
// populates it.
// makeTempFile creates a temp file and populates it.
makeTempFile := func(name, contents string) string {
f, err := ioutil.TempFile("", name)
if err != nil {
Expand All @@ -416,7 +418,7 @@ func TestAgent_RequireRequestHeader(t *testing.T) {
return path
}

// newApiClient is a helper function that creates an *api.Client.
// newApiClient creates an *api.Client.
newApiClient := func(addr string, includeVaultRequestHeader bool) *api.Client {
conf := api.DefaultConfig()
conf.Address = addr
Expand All @@ -425,11 +427,16 @@ func TestAgent_RequireRequestHeader(t *testing.T) {
t.Fatalf("err: %s", err)
}

h := cli.Headers()
val, ok := h[consts.VaultRequestHeader]
if !ok || !reflect.DeepEqual(val, []string{"true"}) {
t.Fatal("invalid Vault-Request header")
}
if !includeVaultRequestHeader {
h := cli.Headers()
delete(h, "Vault-Request")
delete(h, consts.VaultRequestHeader)
cli.SetHeaders(h)
}

return cli
}

Expand Down Expand Up @@ -588,12 +595,27 @@ listener "tcp" {
if err == nil {
t.Fatalf("expected error")
}
if resp.StatusCode != 412 {
t.Fatalf("expected status code %d, not %d", 412, resp.StatusCode)
if resp.StatusCode != http.StatusPreconditionFailed {
t.Fatalf("expected status code %d, not %d", http.StatusPreconditionFailed, resp.StatusCode)
}

// Test against a listener configuration that sets 'require_request_header'
// to 'true', with an invalid header present in the request.
agentClient = newApiClient("http://127.0.0.1:8103", false)
h := agentClient.Headers()
h[consts.VaultRequestHeader] = []string{"bogus"}
agentClient.SetHeaders(h)
req = agentClient.NewRequest("GET", "/v1/sys/health")
resp, err = agentClient.RawRequest(req)
if err == nil {
t.Fatalf("expected error")
}
if resp.StatusCode != http.StatusPreconditionFailed {
t.Fatalf("expected status code %d, not %d", http.StatusPreconditionFailed, resp.StatusCode)
}

// Test against a listener configuration that sets 'require_request_header'
// to 'true', with the header present in the request.
// to 'true', with the proper header present in the request.
agentClient = newApiClient("http://127.0.0.1:8103", true)
req = agentClient.NewRequest("GET", "/v1/sys/health")
request(agentClient, req, 200)
Expand Down
4 changes: 4 additions & 0 deletions sdk/helper/consts/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ package consts
// AgentPathCacheClear is the path that the agent will use as its cache-clear
// endpoint.
const AgentPathCacheClear = "/agent/v1/cache-clear"

// VaultRequestHeader is the name of the header entry used by the Agent for
// SSRF protection.
const VaultRequestHeader = "Vault-Request"
mjarmy marked this conversation as resolved.
Show resolved Hide resolved