4f4ddbf017
This commit splits ACL policies into more fine-grained capabilities. This both drastically simplifies the checking code and makes it possible to support needed workflows that are not possible with the previous method. It is backwards compatible; policies containing a "policy" string are simply converted to a set of capabilities matching previous behavior. Fixes #724 (and others).
307 lines
7.4 KiB
Go
307 lines
7.4 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"net/http"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
"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": float64(1),
|
|
"n": float64(1),
|
|
"progress": float64(0),
|
|
}
|
|
testResponseStatus(t, resp, 200)
|
|
testResponseBody(t, resp, &actual)
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
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, 400)
|
|
}
|
|
|
|
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)
|
|
|
|
check, err := core.Sealed()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !check {
|
|
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)
|
|
|
|
check, err := core.Sealed()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !check {
|
|
t.Fatal("should be sealed")
|
|
}
|
|
}
|
|
|
|
func TestSysUnseal(t *testing.T) {
|
|
core := vault.TestCore(t)
|
|
key, _ := vault.TestCoreInit(t, core)
|
|
ln, addr := TestServer(t, core)
|
|
defer ln.Close()
|
|
|
|
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
|
|
"key": hex.EncodeToString(key),
|
|
})
|
|
|
|
var actual map[string]interface{}
|
|
expected := map[string]interface{}{
|
|
"sealed": false,
|
|
"t": float64(1),
|
|
"n": float64(1),
|
|
"progress": float64(0),
|
|
}
|
|
testResponseStatus(t, resp, 200)
|
|
testResponseBody(t, resp, &actual)
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
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",
|
|
})
|
|
|
|
var actual map[string]interface{}
|
|
expected := map[string]interface{}{
|
|
"sealed": true,
|
|
"t": float64(1),
|
|
"n": float64(1),
|
|
"progress": float64(0),
|
|
}
|
|
testResponseStatus(t, resp, 200)
|
|
testResponseBody(t, resp, &actual)
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
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": float64(3),
|
|
"n": float64(5),
|
|
"progress": float64(i + 1),
|
|
}
|
|
testResponseStatus(t, resp, 200)
|
|
testResponseBody(t, resp, &actual)
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
|
|
}
|
|
}
|
|
|
|
resp = testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
|
|
"reset": true,
|
|
})
|
|
|
|
actual = map[string]interface{}{}
|
|
expected := map[string]interface{}{
|
|
"sealed": true,
|
|
"t": float64(3),
|
|
"n": float64(5),
|
|
"progress": float64(0),
|
|
}
|
|
testResponseStatus(t, resp, 200)
|
|
testResponseBody(t, resp, &actual)
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
|
|
}
|
|
|
|
}
|
|
|
|
// 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(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
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(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, 500)
|
|
|
|
// 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(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
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, 500)
|
|
|
|
// 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(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
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, 500)
|
|
|
|
// 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(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// We expect this to work
|
|
httpResp = testHttpPut(t, "child", addr+"/v1/sys/seal", nil)
|
|
testResponseStatus(t, httpResp, 204)
|
|
}
|