package http import ( "context" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "net" "net/http" "reflect" "testing" "github.com/go-test/deep" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/helper/testhelpers/corehelpers" "github.com/hashicorp/vault/sdk/helper/xor" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault" ) var tokenLength string = fmt.Sprintf("%d", vault.TokenLength+vault.TokenPrefixLength) func TestSysGenerateRootAttempt_Status(t *testing.T) { core, _, token := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) defer ln.Close() TestServerAuth(t, addr, token) resp, err := http.Get(addr + "/v1/sys/generate-root/attempt") if err != nil { t.Fatalf("err: %s", err) } var actual map[string]interface{} expected := map[string]interface{}{ "started": false, "progress": json.Number("0"), "required": json.Number("3"), "complete": false, "encoded_token": "", "encoded_root_token": "", "pgp_fingerprint": "", "nonce": "", "otp_length": json.Number(tokenLength), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) expected["otp"] = actual["otp"] if diff := deep.Equal(actual, expected); diff != nil { t.Fatal(diff) } } func TestSysGenerateRootAttempt_Setup_OTP(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/generate-root/attempt", nil) testResponseStatus(t, resp, 200) var actual map[string]interface{} expected := map[string]interface{}{ "started": true, "progress": json.Number("0"), "required": json.Number("3"), "complete": false, "encoded_token": "", "encoded_root_token": "", "pgp_fingerprint": "", "otp_length": json.Number(tokenLength), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) if actual["nonce"].(string) == "" { t.Fatalf("nonce was empty") } expected["nonce"] = actual["nonce"] expected["otp"] = actual["otp"] if diff := deep.Equal(actual, expected); diff != nil { t.Fatal(diff) } resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt") actual = map[string]interface{}{} expected = map[string]interface{}{ "started": true, "progress": json.Number("0"), "required": json.Number("3"), "complete": false, "encoded_token": "", "encoded_root_token": "", "pgp_fingerprint": "", "otp": "", "otp_length": json.Number(tokenLength), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) if actual["nonce"].(string) == "" { t.Fatalf("nonce was empty") } expected["nonce"] = actual["nonce"] if !reflect.DeepEqual(actual, expected) { t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } } func TestSysGenerateRootAttempt_Setup_PGP(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/generate-root/attempt", map[string]interface{}{ "pgp_key": pgpkeys.TestPubKey1, }) testResponseStatus(t, resp, 200) resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt") var actual map[string]interface{} expected := map[string]interface{}{ "started": true, "progress": json.Number("0"), "required": json.Number("3"), "complete": false, "encoded_token": "", "encoded_root_token": "", "pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793", "otp": "", "otp_length": json.Number(tokenLength), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) if actual["nonce"].(string) == "" { t.Fatalf("nonce was empty") } expected["nonce"] = actual["nonce"] if diff := deep.Equal(actual, expected); diff != nil { t.Fatal(diff) } } func TestSysGenerateRootAttempt_Cancel(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/generate-root/attempt", nil) var actual map[string]interface{} expected := map[string]interface{}{ "started": true, "progress": json.Number("0"), "required": json.Number("3"), "complete": false, "encoded_token": "", "encoded_root_token": "", "pgp_fingerprint": "", "otp_length": json.Number(tokenLength), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) if actual["nonce"].(string) == "" { t.Fatalf("nonce was empty") } expected["nonce"] = actual["nonce"] expected["otp"] = actual["otp"] if diff := deep.Equal(actual, expected); diff != nil { t.Fatal(diff) } resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt") testResponseStatus(t, resp, 204) resp, err := http.Get(addr + "/v1/sys/generate-root/attempt") if err != nil { t.Fatalf("err: %s", err) } actual = map[string]interface{}{} expected = map[string]interface{}{ "started": false, "progress": json.Number("0"), "required": json.Number("3"), "complete": false, "encoded_token": "", "encoded_root_token": "", "pgp_fingerprint": "", "nonce": "", "otp": "", "otp_length": json.Number(tokenLength), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) if !reflect.DeepEqual(actual, expected) { t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } } func enableNoopAudit(t *testing.T, token string, core *vault.Core) { t.Helper() auditReq := &logical.Request{ Operation: logical.UpdateOperation, ClientToken: token, Path: "sys/audit/noop", Data: map[string]interface{}{ "type": "noop", }, } resp, err := core.HandleRequest(namespace.RootContext(context.Background()), auditReq) if err != nil { t.Fatal(err) } if resp.IsError() { t.Fatal(err) } } func testCoreUnsealedWithAudit(t *testing.T, records **[][]byte) (*vault.Core, [][]byte, string) { conf := &vault.CoreConfig{ BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(), AuditBackends: map[string]audit.Factory{ "noop": corehelpers.NoopAuditFactory(records), }, } core, keys, token := vault.TestCoreUnsealedWithConfig(t, conf) return core, keys, token } func testServerWithAudit(t *testing.T, records **[][]byte) (net.Listener, string, string, [][]byte) { core, keys, token := testCoreUnsealedWithAudit(t, records) ln, addr := TestServer(t, core) TestServerAuth(t, addr, token) enableNoopAudit(t, token, core) return ln, addr, token, keys } func TestSysGenerateRoot_badKey(t *testing.T) { var records *[][]byte ln, addr, token, _ := testServerWithAudit(t, &records) defer ln.Close() resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{ "key": "0123", }) testResponseStatus(t, resp, 400) if len(*records) < 3 { // One record for enabling the noop audit device, two for generate root attempt t.Fatalf("expected at least 3 audit records, got %d", len(*records)) } t.Log(string((*records)[2])) } func TestSysGenerateRoot_ReAttemptUpdate(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/generate-root/attempt", nil) testResponseStatus(t, resp, 200) resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt") testResponseStatus(t, resp, 204) resp = testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{ "pgp_key": pgpkeys.TestPubKey1, }) testResponseStatus(t, resp, 200) } func TestSysGenerateRoot_Update_OTP(t *testing.T) { var records *[][]byte ln, addr, token, keys := testServerWithAudit(t, &records) defer ln.Close() resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{}) var rootGenerationStatus map[string]interface{} testResponseStatus(t, resp, 200) testResponseBody(t, resp, &rootGenerationStatus) otp := rootGenerationStatus["otp"].(string) var actual map[string]interface{} var expected map[string]interface{} for i, key := range keys { resp = testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{ "nonce": rootGenerationStatus["nonce"].(string), "key": hex.EncodeToString(key), }) actual = map[string]interface{}{} expected = map[string]interface{}{ "complete": false, "nonce": rootGenerationStatus["nonce"].(string), "progress": json.Number(fmt.Sprintf("%d", i+1)), "required": json.Number(fmt.Sprintf("%d", len(keys))), "started": true, "pgp_fingerprint": "", "otp": "", "otp_length": json.Number("0"), } if i+1 == len(keys) { expected["complete"] = true } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) } if actual["encoded_token"] == nil || actual["encoded_token"] == "" { t.Fatalf("no encoded token found in response") } if actual["encoded_root_token"] == nil || actual["encoded_root-token"] == "" { t.Fatalf("no encoded root token found in response") } expected["encoded_token"] = actual["encoded_token"] expected["encoded_root_token"] = actual["encoded_root_token"] expected["encoded_token"] = actual["encoded_token"] if !reflect.DeepEqual(actual, expected) { t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } tokenBytes, err := base64.RawStdEncoding.DecodeString(expected["encoded_token"].(string)) if err != nil { t.Fatal(err) } tokenBytes, err = xor.XORBytes(tokenBytes, []byte(otp)) if err != nil { t.Fatal(err) } newRootToken := string(tokenBytes) actual = map[string]interface{}{} expected = map[string]interface{}{ "id": newRootToken, "display_name": "root", "meta": interface{}(nil), "num_uses": json.Number("0"), "policies": []interface{}{"root"}, "orphan": true, "creation_ttl": json.Number("0"), "ttl": json.Number("0"), "path": "auth/token/root", "explicit_max_ttl": json.Number("0"), "expire_time": nil, "entity_id": "", "type": "service", } resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self") testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"] expected["accessor"] = actual["data"].(map[string]interface{})["accessor"] if !reflect.DeepEqual(actual["data"], expected) { t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"]) } for _, r := range *records { t.Log(string(r)) } } func TestSysGenerateRoot_Update_PGP(t *testing.T) { core, keys, token := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) defer ln.Close() TestServerAuth(t, addr, token) resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{ "pgp_key": pgpkeys.TestPubKey1, }) testResponseStatus(t, resp, 200) // We need to get the nonce first before we update resp, err := http.Get(addr + "/v1/sys/generate-root/attempt") if err != nil { t.Fatalf("err: %s", err) } var rootGenerationStatus map[string]interface{} testResponseStatus(t, resp, 200) testResponseBody(t, resp, &rootGenerationStatus) var actual map[string]interface{} var expected map[string]interface{} for i, key := range keys { resp = testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{ "nonce": rootGenerationStatus["nonce"].(string), "key": hex.EncodeToString(key), }) actual = map[string]interface{}{} expected = map[string]interface{}{ "complete": false, "nonce": rootGenerationStatus["nonce"].(string), "progress": json.Number(fmt.Sprintf("%d", i+1)), "required": json.Number(fmt.Sprintf("%d", len(keys))), "started": true, "pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793", "otp": "", "otp_length": json.Number("0"), } if i+1 == len(keys) { expected["complete"] = true } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) } if actual["encoded_token"] == nil || actual["encoded_token"] == "" { t.Fatalf("no encoded token found in response") } if actual["encoded_root_token"] == nil || actual["encoded_root-token"] == "" { t.Fatalf("no encoded root token found in response") } expected["encoded_token"] = actual["encoded_token"] expected["encoded_root_token"] = actual["encoded_root_token"] expected["encoded_token"] = actual["encoded_token"] if !reflect.DeepEqual(actual, expected) { t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } decodedTokenBuf, err := pgpkeys.DecryptBytes(actual["encoded_token"].(string), pgpkeys.TestPrivKey1) if err != nil { t.Fatal(err) } if decodedTokenBuf == nil { t.Fatal("decoded root token buffer is nil") } newRootToken := decodedTokenBuf.String() actual = map[string]interface{}{} expected = map[string]interface{}{ "id": newRootToken, "display_name": "root", "meta": interface{}(nil), "num_uses": json.Number("0"), "policies": []interface{}{"root"}, "orphan": true, "creation_ttl": json.Number("0"), "ttl": json.Number("0"), "path": "auth/token/root", "explicit_max_ttl": json.Number("0"), "expire_time": nil, "entity_id": "", "type": "service", } resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self") testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"] expected["accessor"] = actual["data"].(map[string]interface{})["accessor"] if diff := deep.Equal(actual["data"], expected); diff != nil { t.Fatal(diff) } }