vault/http/handler_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

890 lines
24 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package http
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/textproto"
"net/url"
"reflect"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)
func TestHandler_parseMFAHandler(t *testing.T) {
var err error
var expectedMFACreds logical.MFACreds
req := &logical.Request{
Headers: make(map[string][]string),
}
headerName := textproto.CanonicalMIMEHeaderKey(MFAHeaderName)
// Set TOTP passcode in the MFA header
req.Headers[headerName] = []string{
"my_totp:123456",
"my_totp:111111",
"my_second_mfa:hi=hello",
"my_third_mfa",
}
err = parseMFAHeader(req)
if err != nil {
t.Fatal(err)
}
// Verify that it is being parsed properly
expectedMFACreds = logical.MFACreds{
"my_totp": []string{
"123456",
"111111",
},
"my_second_mfa": []string{
"hi=hello",
},
"my_third_mfa": []string{},
}
if !reflect.DeepEqual(expectedMFACreds, req.MFACreds) {
t.Fatalf("bad: parsed MFACreds; expected: %#v\n actual: %#v\n", expectedMFACreds, req.MFACreds)
}
// Split the creds of a method type in different headers and check if they
// all get merged together
req.Headers[headerName] = []string{
"my_mfa:passcode=123456",
"my_mfa:month=july",
"my_mfa:day=tuesday",
}
err = parseMFAHeader(req)
if err != nil {
t.Fatal(err)
}
expectedMFACreds = logical.MFACreds{
"my_mfa": []string{
"passcode=123456",
"month=july",
"day=tuesday",
},
}
if !reflect.DeepEqual(expectedMFACreds, req.MFACreds) {
t.Fatalf("bad: parsed MFACreds; expected: %#v\n actual: %#v\n", expectedMFACreds, req.MFACreds)
}
// Header without method name should error out
req.Headers[headerName] = []string{
":passcode=123456",
}
err = parseMFAHeader(req)
if err == nil {
t.Fatalf("expected an error; actual: %#v\n", req.MFACreds)
}
// Header without method name and method value should error out
req.Headers[headerName] = []string{
":",
}
err = parseMFAHeader(req)
if err == nil {
t.Fatalf("expected an error; actual: %#v\n", req.MFACreds)
}
// Header without method name and method value should error out
req.Headers[headerName] = []string{
"my_totp:",
}
err = parseMFAHeader(req)
if err == nil {
t.Fatalf("expected an error; actual: %#v\n", req.MFACreds)
}
}
func TestHandler_cors(t *testing.T) {
core, _, _ := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
// Enable CORS and allow from any origin for testing.
corsConfig := core.CORSConfig()
err := corsConfig.Enable(context.Background(), []string{addr}, nil)
if err != nil {
t.Fatalf("Error enabling CORS: %s", err)
}
req, err := http.NewRequest(http.MethodOptions, addr+"/v1/sys/seal-status", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set("Origin", "BAD ORIGIN")
// Requests from unacceptable origins will be rejected with a 403.
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("Bad status:\nexpected: 403 Forbidden\nactual: %s", resp.Status)
}
//
// Test preflight requests
//
// Set a valid origin
req.Header.Set("Origin", addr)
// Server should NOT accept arbitrary methods.
req.Header.Set("Access-Control-Request-Method", "FOO")
client = cleanhttp.DefaultClient()
resp, err = client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
// Fail if an arbitrary method is accepted.
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatalf("Bad status:\nexpected: 405 Method Not Allowed\nactual: %s", resp.Status)
}
// Server SHOULD accept acceptable methods.
req.Header.Set("Access-Control-Request-Method", http.MethodPost)
client = cleanhttp.DefaultClient()
resp, err = client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
//
// Test that the CORS headers are applied correctly.
//
expHeaders := map[string]string{
"Access-Control-Allow-Origin": addr,
"Access-Control-Allow-Headers": strings.Join(vault.StdAllowedHeaders, ","),
"Access-Control-Max-Age": "300",
"Vary": "Origin",
}
for expHeader, expected := range expHeaders {
actual := resp.Header.Get(expHeader)
if actual == "" {
t.Fatalf("bad:\nHeader: %#v was not on response.", expHeader)
}
if actual != expected {
t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n", expected, actual)
}
}
}
func TestHandler_HostnameHeader(t *testing.T) {
t.Parallel()
testCases := []struct {
description string
config *vault.CoreConfig
headerPresent bool
}{
{
description: "with no header configured",
config: nil,
headerPresent: false,
},
{
description: "with header configured",
config: &vault.CoreConfig{
EnableResponseHeaderHostname: true,
},
headerPresent: true,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
var core *vault.Core
if tc.config == nil {
core, _, _ = vault.TestCoreUnsealed(t)
} else {
core, _, _ = vault.TestCoreUnsealedWithConfig(t, tc.config)
}
ln, addr := TestServer(t, core)
defer ln.Close()
req, err := http.NewRequest("GET", addr+"/v1/sys/seal-status", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp == nil {
t.Fatal("nil response")
}
hnHeader := resp.Header.Get("X-Vault-Hostname")
if tc.headerPresent && hnHeader == "" {
t.Logf("header configured = %t", core.HostnameHeaderEnabled())
t.Fatal("missing 'X-Vault-Hostname' header entry in response")
}
if !tc.headerPresent && hnHeader != "" {
t.Fatal("didn't expect 'X-Vault-Hostname' header but it was present anyway")
}
rniHeader := resp.Header.Get("X-Vault-Raft-Node-ID")
if rniHeader != "" {
t.Fatalf("no raft node ID header was expected, since we're not running a raft cluster. instead, got %s", rniHeader)
}
})
}
}
func TestHandler_CacheControlNoStore(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
req, err := http.NewRequest("GET", addr+"/v1/sys/mounts", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(consts.AuthHeaderName, token)
req.Header.Set(WrapTTLHeaderName, "60s")
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp == nil {
t.Fatalf("nil response")
}
actual := resp.Header.Get("Cache-Control")
if actual == "" {
t.Fatalf("missing 'Cache-Control' header entry in response writer")
}
if actual != "no-store" {
t.Fatalf("bad: Cache-Control. Expected: 'no-store', Actual: %q", actual)
}
}
func TestHandler_InFlightRequest(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)
req, err := http.NewRequest("GET", addr+"/v1/sys/in-flight-req", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(consts.AuthHeaderName, token)
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp == nil {
t.Fatalf("nil response")
}
var actual map[string]interface{}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if actual == nil || len(actual) == 0 {
t.Fatal("expected to get at least one in-flight request, got nil or zero length map")
}
for _, v := range actual {
reqInfo, ok := v.(map[string]interface{})
if !ok {
t.Fatal("failed to read in-flight request")
}
if reqInfo["request_path"] != "/v1/sys/in-flight-req" {
t.Fatalf("expected /v1/sys/in-flight-req in-flight request path, got %s", actual["request_path"])
}
}
}
// TestHandler_MissingToken tests the response / error code if a request comes
// in with a missing client token. See
// https://github.com/hashicorp/vault/issues/8377
func TestHandler_MissingToken(t *testing.T) {
// core, _, token := vault.TestCoreUnsealed(t)
core, _, _ := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
req, err := http.NewRequest("GET", addr+"/v1/sys/internal/ui/mounts/cubbyhole", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(WrapTTLHeaderName, "60s")
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 403 {
t.Fatalf("expected code 403, got: %d", resp.StatusCode)
}
}
func TestHandler_Accepted(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
req, err := http.NewRequest("POST", addr+"/v1/auth/token/tidy", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(consts.AuthHeaderName, token)
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
testResponseStatus(t, resp, 202)
}
// We use this test to verify header auth
func TestSysMounts_headerAuth(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
req, err := http.NewRequest("GET", addr+"/v1/sys/mounts", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(consts.AuthHeaderName, token)
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
var actual map[string]interface{}
expected := map[string]interface{}{
"lease_id": "",
"renewable": false,
"lease_duration": json.Number("0"),
"wrap_info": nil,
"warnings": nil,
"auth": nil,
"data": map[string]interface{}{
"secret/": map[string]interface{}{
"description": "key/value secret storage",
"type": "kv",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]interface{}{"version": "1"},
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
"type": "system",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"},
},
"local": false,
"seal_wrap": true,
"options": interface{}(nil),
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.DefaultBuiltinVersion,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
"type": "cubbyhole",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
},
"local": true,
"seal_wrap": false,
"options": interface{}(nil),
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
},
"identity/": map[string]interface{}{
"description": "identity store",
"type": "identity",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"},
},
"local": false,
"seal_wrap": false,
"options": interface{}(nil),
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
},
},
"secret/": map[string]interface{}{
"description": "key/value secret storage",
"type": "kv",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]interface{}{"version": "1"},
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
"type": "system",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"},
},
"local": false,
"seal_wrap": true,
"options": interface{}(nil),
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.DefaultBuiltinVersion,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
"type": "cubbyhole",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
},
"local": true,
"seal_wrap": false,
"options": interface{}(nil),
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
},
"identity/": map[string]interface{}{
"description": "identity store",
"type": "identity",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"},
},
"local": false,
"seal_wrap": false,
"options": interface{}(nil),
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
},
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
expected["request_id"] = actual["request_id"]
for k, v := range actual["data"].(map[string]interface{}) {
if v.(map[string]interface{})["accessor"] == "" {
t.Fatalf("no accessor from %s", k)
}
if v.(map[string]interface{})["uuid"] == "" {
t.Fatalf("no uuid from %s", k)
}
expected[k].(map[string]interface{})["accessor"] = v.(map[string]interface{})["accessor"]
expected[k].(map[string]interface{})["uuid"] = v.(map[string]interface{})["uuid"]
expected["data"].(map[string]interface{})[k].(map[string]interface{})["accessor"] = v.(map[string]interface{})["accessor"]
expected["data"].(map[string]interface{})[k].(map[string]interface{})["uuid"] = v.(map[string]interface{})["uuid"]
}
if diff := deep.Equal(actual, expected); len(diff) > 0 {
t.Fatalf("bad, diff: %#v", diff)
}
}
// We use this test to verify header auth wrapping
func TestSysMounts_headerAuth_Wrapped(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
req, err := http.NewRequest("GET", addr+"/v1/sys/mounts", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(consts.AuthHeaderName, token)
req.Header.Set(WrapTTLHeaderName, "60s")
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
var actual map[string]interface{}
expected := map[string]interface{}{
"request_id": "",
"lease_id": "",
"renewable": false,
"lease_duration": json.Number("0"),
"data": nil,
"wrap_info": map[string]interface{}{
"ttl": json.Number("60"),
},
"warnings": nil,
"auth": nil,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
actualToken, ok := actual["wrap_info"].(map[string]interface{})["token"]
if !ok || actualToken == "" {
t.Fatal("token missing in wrap info")
}
expected["wrap_info"].(map[string]interface{})["token"] = actualToken
actualCreationTime, ok := actual["wrap_info"].(map[string]interface{})["creation_time"]
if !ok || actualCreationTime == "" {
t.Fatal("creation_time missing in wrap info")
}
expected["wrap_info"].(map[string]interface{})["creation_time"] = actualCreationTime
actualCreationPath, ok := actual["wrap_info"].(map[string]interface{})["creation_path"]
if !ok || actualCreationPath == "" {
t.Fatal("creation_path missing in wrap info")
}
expected["wrap_info"].(map[string]interface{})["creation_path"] = actualCreationPath
actualAccessor, ok := actual["wrap_info"].(map[string]interface{})["accessor"]
if !ok || actualAccessor == "" {
t.Fatal("accessor missing in wrap info")
}
expected["wrap_info"].(map[string]interface{})["accessor"] = actualAccessor
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n%T %T", expected, actual, actual["warnings"], actual["data"])
}
}
func TestHandler_sealed(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
core.Seal(token)
resp, err := http.Get(addr + "/v1/secret/foo")
if err != nil {
t.Fatalf("err: %s", err)
}
testResponseStatus(t, resp, 503)
}
func TestHandler_ui_default(t *testing.T) {
core := vault.TestCoreUI(t, false)
ln, addr := TestServer(t, core)
defer ln.Close()
resp, err := http.Get(addr + "/ui/")
if err != nil {
t.Fatalf("err: %s", err)
}
testResponseStatus(t, resp, 404)
}
func TestHandler_ui_enabled(t *testing.T) {
core := vault.TestCoreUI(t, true)
ln, addr := TestServer(t, core)
defer ln.Close()
resp, err := http.Get(addr + "/ui/")
if err != nil {
t.Fatalf("err: %s", err)
}
testResponseStatus(t, resp, 200)
}
func TestHandler_error(t *testing.T) {
w := httptest.NewRecorder()
respondError(w, 500, errors.New("test Error"))
if w.Code != 500 {
t.Fatalf("expected 500, got %d", w.Code)
}
// The code inside of the error should override
// the argument to respondError
w2 := httptest.NewRecorder()
e := logical.CodedError(403, "error text")
respondError(w2, 500, e)
if w2.Code != 403 {
t.Fatalf("expected 403, got %d", w2.Code)
}
// vault.ErrSealed is a special case
w3 := httptest.NewRecorder()
respondError(w3, 400, consts.ErrSealed)
if w3.Code != 503 {
t.Fatalf("expected 503, got %d", w3.Code)
}
}
func TestHandler_requestAuth(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
rootCtx := namespace.RootContext(nil)
te, err := core.LookupToken(rootCtx, token)
if err != nil {
t.Fatalf("err: %s", err)
}
rWithAuthorization, err := http.NewRequest("GET", "v1/test/path", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
rWithAuthorization.Header.Set("Authorization", "Bearer "+token)
rWithVault, err := http.NewRequest("GET", "v1/test/path", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
rWithVault.Header.Set(consts.AuthHeaderName, token)
for _, r := range []*http.Request{rWithVault, rWithAuthorization} {
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
r = r.WithContext(rootCtx)
requestAuth(r, req)
err = core.PopulateTokenEntry(rootCtx, req)
if err != nil {
t.Fatalf("err: %s", err)
}
if req.ClientToken != token {
t.Fatalf("client token should be filled with %s, got %s", token, req.ClientToken)
}
if req.TokenEntry() == nil {
t.Fatal("token entry should not be nil")
}
if !reflect.DeepEqual(req.TokenEntry(), te) {
t.Fatalf("token entry should be the same as the core")
}
if req.ClientTokenAccessor == "" {
t.Fatal("token accessor should not be empty")
}
}
rNothing, err := http.NewRequest("GET", "v1/test/path", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
requestAuth(rNothing, req)
err = core.PopulateTokenEntry(rootCtx, req)
if err != nil {
t.Fatalf("expected no error, got %s", err)
}
if req.ClientToken != "" {
t.Fatalf("client token should not be filled, got %s", req.ClientToken)
}
}
func TestHandler_getTokenFromReq(t *testing.T) {
r := http.Request{Header: http.Header{}}
tok, _ := getTokenFromReq(&r)
if tok != "" {
t.Fatalf("expected '' as result, got '%s'", tok)
}
r.Header.Set("Authorization", "Bearer TOKEN NOT_GOOD_TOKEN")
token, fromHeader := getTokenFromReq(&r)
if !fromHeader {
t.Fatal("expected from header")
} else if token != "TOKEN NOT_GOOD_TOKEN" {
t.Fatal("did not get expected token value")
} else if r.Header.Get("Authorization") == "" {
t.Fatal("expected value to be passed through")
}
r.Header.Set(consts.AuthHeaderName, "NEWTOKEN")
tok, _ = getTokenFromReq(&r)
if tok == "TOKEN" {
t.Fatalf("%s header should be prioritized", consts.AuthHeaderName)
} else if tok != "NEWTOKEN" {
t.Fatalf("expected 'NEWTOKEN' as result, got '%s'", tok)
}
r.Header = http.Header{}
r.Header.Set("Authorization", "Basic TOKEN")
tok, fromHeader = getTokenFromReq(&r)
if tok != "" {
t.Fatalf("expected '' as result, got '%s'", tok)
} else if fromHeader {
t.Fatal("expected not from header")
}
}
func TestHandler_nonPrintableChars(t *testing.T) {
testNonPrintable(t, false)
testNonPrintable(t, true)
}
func testNonPrintable(t *testing.T, disable bool) {
core, _, token := vault.TestCoreUnsealedWithConfig(t, &vault.CoreConfig{
DisableKeyEncodingChecks: disable,
})
ln, addr := TestListener(t)
props := &vault.HandlerProperties{
Core: core,
DisablePrintableCheck: disable,
}
TestServerWithListenerAndProperties(t, ln, addr, core, props)
defer ln.Close()
req, err := http.NewRequest("PUT", addr+"/v1/cubbyhole/foo\u2028bar", strings.NewReader(`{"zip": "zap"}`))
if err != nil {
t.Fatalf("err: %s", err)
}
req.Header.Set(consts.AuthHeaderName, token)
client := cleanhttp.DefaultClient()
resp, err := client.Do(req)
if err != nil {
t.Fatalf("err: %s", err)
}
if disable {
testResponseStatus(t, resp, 204)
} else {
testResponseStatus(t, resp, 400)
}
}
func TestHandler_Parse_Form(t *testing.T) {
cluster := vault.NewTestCluster(t, &vault.CoreConfig{}, &vault.TestClusterOptions{
HandlerFunc: Handler,
})
cluster.Start()
defer cluster.Cleanup()
cores := cluster.Cores
core := cores[0].Core
vault.TestWaitActive(t, core)
c := cleanhttp.DefaultClient()
c.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: cluster.RootCAs,
},
}
values := url.Values{
"zip": []string{"zap"},
"abc": []string{"xyz"},
"multi": []string{"first", "second"},
"empty": []string{},
}
req, err := http.NewRequest("POST", cores[0].Client.Address()+"/v1/secret/foo", nil)
if err != nil {
t.Fatal(err)
}
req.Body = ioutil.NopCloser(strings.NewReader(values.Encode()))
req.Header.Set("x-vault-token", cluster.RootToken)
req.Header.Set("content-type", "application/x-www-form-urlencoded")
resp, err := c.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 204 {
t.Fatalf("bad response: %#v\nrequest was: %#v\nurl was: %#v", *resp, *req, req.URL)
}
client := cores[0].Client
client.SetToken(cluster.RootToken)
apiResp, err := client.Logical().Read("secret/foo")
if err != nil {
t.Fatal(err)
}
if apiResp == nil {
t.Fatal("api resp is nil")
}
expected := map[string]interface{}{
"zip": "zap",
"abc": "xyz",
"multi": "first,second",
}
if diff := deep.Equal(expected, apiResp.Data); diff != nil {
t.Fatal(diff)
}
}