open-vault/http/sys_seal_test.go

395 lines
10 KiB
Go

package http
import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/version"
"github.com/hashicorp/vault/vault"
)
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 TestSysUnseal_badKey(t *testing.T) {
core := vault.TestCore(t)
vault.TestCoreInit(t, core)
ln, addr := TestServer(t, core)
defer ln.Close()
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"key": "0123",
})
testResponseStatus(t, resp, 400)
}
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)
}