vault/http/sys_seal_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

559 lines
14 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package http
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
"github.com/hashicorp/vault/vault/seal"
"github.com/hashicorp/vault/version"
)
func TestSysSealStatus(t *testing.T) {
core := vault.TestCore(t)
vault.TestCoreInit(t, core)
ln, addr := TestServer(t, core)
defer ln.Close()
resp, err := http.Get(addr + "/v1/sys/seal-status")
if err != nil {
t.Fatalf("err: %s", err)
}
var actual map[string]interface{}
expected := map[string]interface{}{
"sealed": true,
"t": json.Number("3"),
"n": json.Number("3"),
"progress": json.Number("0"),
"nonce": "",
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
"build_date": version.BuildDate,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if actual["version"] == nil {
t.Fatalf("expected version information")
}
expected["version"] = actual["version"]
if actual["cluster_name"] == nil {
delete(expected, "cluster_name")
} else {
expected["cluster_name"] = actual["cluster_name"]
}
if actual["cluster_id"] == nil {
delete(expected, "cluster_id")
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
func TestSysSealStatus_uninit(t *testing.T) {
core := vault.TestCore(t)
ln, addr := TestServer(t, core)
defer ln.Close()
resp, err := http.Get(addr + "/v1/sys/seal-status")
if err != nil {
t.Fatalf("err: %s", err)
}
testResponseStatus(t, resp, 200)
}
func TestSysSeal(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)
resp := testHttpPut(t, token, addr+"/v1/sys/seal", nil)
testResponseStatus(t, resp, 204)
if !core.Sealed() {
t.Fatal("should be sealed")
}
}
func TestSysSeal_unsealed(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)
resp := testHttpPut(t, token, addr+"/v1/sys/seal", nil)
testResponseStatus(t, resp, 204)
if !core.Sealed() {
t.Fatal("should be sealed")
}
}
func TestSysUnseal(t *testing.T) {
core := vault.TestCore(t)
keys, _ := vault.TestCoreInit(t, core)
ln, addr := TestServer(t, core)
defer ln.Close()
for i, key := range keys {
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"key": hex.EncodeToString(key),
})
var actual map[string]interface{}
expected := map[string]interface{}{
"sealed": true,
"t": json.Number("3"),
"n": json.Number("3"),
"progress": json.Number(fmt.Sprintf("%d", i+1)),
"nonce": "",
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
"build_date": version.BuildDate,
}
if i == len(keys)-1 {
expected["sealed"] = false
expected["progress"] = json.Number("0")
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if i < len(keys)-1 && (actual["nonce"] == nil || actual["nonce"].(string) == "") {
t.Fatalf("got nil nonce, actual is %#v", actual)
} else {
expected["nonce"] = actual["nonce"]
}
if actual["version"] == nil {
t.Fatalf("expected version information")
}
expected["version"] = actual["version"]
if actual["cluster_name"] == nil {
delete(expected, "cluster_name")
} else {
expected["cluster_name"] = actual["cluster_name"]
}
if actual["cluster_id"] == nil {
delete(expected, "cluster_id")
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
}
func subtestBadSingleKey(t *testing.T, seal vault.Seal) {
core := vault.TestCoreWithSeal(t, seal, false)
_, err := core.Initialize(context.Background(), &vault.InitParams{
BarrierConfig: &vault.SealConfig{
SecretShares: 1,
SecretThreshold: 1,
},
RecoveryConfig: &vault.SealConfig{
SecretShares: 1,
SecretThreshold: 1,
},
})
if err != nil {
t.Fatalf("err: %s", err)
}
ln, addr := TestServer(t, core)
defer ln.Close()
testCases := []struct {
description string
key string
}{
// hex key tests
// hexadecimal strings have 2 symbols per byte; size(0xAA) == 1 byte
{
"short hex key",
strings.Repeat("AA", 8),
},
{
"long hex key",
strings.Repeat("AA", 34),
},
{
"uneven hex key byte length",
strings.Repeat("AA", 33),
},
{
"valid hex key but wrong cluster",
"4482691dd3a710723c4f77c4920ee21b96c226bf4829fa6eb8e8262c180ae933",
},
// base64 key tests
// base64 strings have min. 1 character per byte; size("m") == 1 byte
{
"short b64 key",
base64.StdEncoding.EncodeToString([]byte(strings.Repeat("m", 8))),
},
{
"long b64 key",
base64.StdEncoding.EncodeToString([]byte(strings.Repeat("m", 34))),
},
{
"uneven b64 key byte length",
base64.StdEncoding.EncodeToString([]byte(strings.Repeat("m", 33))),
},
{
"valid b64 key but wrong cluster",
"RIJpHdOnEHI8T3fEkg7iG5bCJr9IKfpuuOgmLBgK6TM=",
},
// other key tests
{
"empty key",
"",
},
{
"key with bad format",
"ThisKeyIsNeitherB64NorHex",
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"key": tc.key,
})
testResponseStatus(t, resp, 400)
})
}
}
func subtestBadMultiKey(t *testing.T, seal vault.Seal) {
numKeys := 3
core := vault.TestCoreWithSeal(t, seal, false)
_, err := core.Initialize(context.Background(), &vault.InitParams{
BarrierConfig: &vault.SealConfig{
SecretShares: numKeys,
SecretThreshold: numKeys,
},
RecoveryConfig: &vault.SealConfig{
SecretShares: numKeys,
SecretThreshold: numKeys,
},
})
if err != nil {
t.Fatalf("err: %s", err)
}
ln, addr := TestServer(t, core)
defer ln.Close()
testCases := []struct {
description string
keys []string
}{
{
"all unseal keys from another cluster",
[]string{
"b189d98fdec3a15bed9b1cce5088f82b92896696b788c07bdf03c73da08279a5e8",
"0fa98232f034177d8d9c2824899a2ac1e55dc6799348533e10510b856aef99f61a",
"5344f5caa852f9ba1967d9623ed286a45ea7c4a529522d25f05d29ff44f17930ac",
},
},
{
"mixing unseal keys from different cluster, different share config",
[]string{
"b189d98fdec3a15bed9b1cce5088f82b92896696b788c07bdf03c73da08279a5e8",
"0fa98232f034177d8d9c2824899a2ac1e55dc6799348533e10510b856aef99f61a",
"e04ea3020838c2050c4a169d7ba4d30e034eec8e83e8bed9461bf2646ee412c0",
},
},
{
"mixing unseal keys from different clusters, similar share config",
[]string{
"b189d98fdec3a15bed9b1cce5088f82b92896696b788c07bdf03c73da08279a5e8",
"0fa98232f034177d8d9c2824899a2ac1e55dc6799348533e10510b856aef99f61a",
"413f80521b393aa6c4e42e9a3a3ab7f00c2002b2c3bf1e273fc6f363f35f2a378b",
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
for i, key := range tc.keys {
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"key": key,
})
if i == numKeys-1 {
// last key
testResponseStatus(t, resp, 400)
} else {
// unseal in progress
testResponseStatus(t, resp, 200)
}
}
})
}
}
func TestSysUnseal_BadKeyNewShamir(t *testing.T) {
seal := vault.NewTestSeal(t,
&seal.TestSealOpts{StoredKeys: seal.StoredKeysSupportedShamirRoot})
subtestBadSingleKey(t, seal)
subtestBadMultiKey(t, seal)
}
func TestSysUnseal_BadKeyAutoUnseal(t *testing.T) {
seal := vault.NewTestSeal(t,
&seal.TestSealOpts{StoredKeys: seal.StoredKeysSupportedGeneric})
subtestBadSingleKey(t, seal)
subtestBadMultiKey(t, seal)
}
func TestSysUnseal_Reset(t *testing.T) {
core := vault.TestCore(t)
ln, addr := TestServer(t, core)
defer ln.Close()
thresh := 3
resp := testHttpPut(t, "", addr+"/v1/sys/init", map[string]interface{}{
"secret_shares": 5,
"secret_threshold": thresh,
})
var actual map[string]interface{}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
keysRaw, ok := actual["keys"]
if !ok {
t.Fatalf("no keys: %#v", actual)
}
for i, key := range keysRaw.([]interface{}) {
if i > thresh-2 {
break
}
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"key": key.(string),
})
var actual map[string]interface{}
expected := map[string]interface{}{
"sealed": true,
"t": json.Number("3"),
"n": json.Number("5"),
"progress": json.Number(strconv.Itoa(i + 1)),
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
"build_date": version.BuildDate,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if actual["version"] == nil {
t.Fatalf("expected version information")
}
expected["version"] = actual["version"]
if actual["nonce"] == "" && expected["sealed"].(bool) {
t.Fatalf("expected a nonce")
}
expected["nonce"] = actual["nonce"]
if actual["cluster_name"] == nil {
delete(expected, "cluster_name")
} else {
expected["cluster_name"] = actual["cluster_name"]
}
if actual["cluster_id"] == nil {
delete(expected, "cluster_id")
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
resp = testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"reset": true,
})
actual = map[string]interface{}{}
expected := map[string]interface{}{
"sealed": true,
"t": json.Number("3"),
"n": json.Number("5"),
"progress": json.Number("0"),
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"build_date": version.BuildDate,
"migration": false,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if actual["version"] == nil {
t.Fatalf("expected version information")
}
expected["version"] = actual["version"]
expected["nonce"] = actual["nonce"]
if actual["cluster_name"] == nil {
delete(expected, "cluster_name")
} else {
expected["cluster_name"] = actual["cluster_name"]
}
if actual["cluster_id"] == nil {
delete(expected, "cluster_id")
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
// Test Seal's permissions logic, which is slightly different than normal code
// paths in that it queries the ACL rather than having checkToken do it. This
// is because it was abusing RootPaths in logical_system, but that caused some
// haywire with code paths that expected there to be an actual corresponding
// logical.Path for it. This way is less hacky, but this test ensures that we
// have not opened up a permissions hole.
func TestSysSeal_Permissions(t *testing.T) {
core, _, root := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, root)
// Set the 'test' policy object to permit write access to sys/seal
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "sys/policy/test",
Data: map[string]interface{}{
"rules": `path "sys/seal" { capabilities = ["read"] }`,
},
ClientToken: root,
}
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.IsError() {
t.Fatalf("bad: %#v", resp)
}
// Create a non-root token with access to that policy
req.Path = "auth/token/create"
req.Data = map[string]interface{}{
"id": "child",
"policies": []string{"test"},
}
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v %v", err, resp)
}
if resp.Auth.ClientToken != "child" {
t.Fatalf("bad: %#v", resp)
}
// We must go through the HTTP interface since seal doesn't go through HandleRequest
// We expect this to fail since it needs update and sudo
httpResp := testHttpPut(t, "child", addr+"/v1/sys/seal", nil)
testResponseStatus(t, httpResp, 403)
// Now modify to add update capability
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "sys/policy/test",
Data: map[string]interface{}{
"rules": `path "sys/seal" { capabilities = ["update"] }`,
},
ClientToken: root,
}
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.IsError() {
t.Fatalf("bad: %#v", resp)
}
// We expect this to fail since it needs sudo
httpResp = testHttpPut(t, "child", addr+"/v1/sys/seal", nil)
testResponseStatus(t, httpResp, 403)
// Now modify to just sudo capability
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "sys/policy/test",
Data: map[string]interface{}{
"rules": `path "sys/seal" { capabilities = ["sudo"] }`,
},
ClientToken: root,
}
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.IsError() {
t.Fatalf("bad: %#v", resp)
}
// We expect this to fail since it needs update
httpResp = testHttpPut(t, "child", addr+"/v1/sys/seal", nil)
testResponseStatus(t, httpResp, 403)
// Now modify to add all needed capabilities
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "sys/policy/test",
Data: map[string]interface{}{
"rules": `path "sys/seal" { capabilities = ["update", "sudo"] }`,
},
ClientToken: root,
}
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.IsError() {
t.Fatalf("bad: %#v", resp)
}
// We expect this to work
httpResp = testHttpPut(t, "child", addr+"/v1/sys/seal", nil)
testResponseStatus(t, httpResp, 204)
}
func TestSysStepDown(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)
resp := testHttpPut(t, token, addr+"/v1/sys/step-down", nil)
testResponseStatus(t, resp, 204)
}