From 24ed178f4447a8bf2504d07dc92b011c69ca3d4e Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 28 May 2015 14:28:50 -0700 Subject: [PATCH] http: adding rekey handlers --- http/handler.go | 2 + http/sys_rekey.go | 171 +++++++++++++++++++++++++++++++++++++++++ http/sys_rekey_test.go | 149 +++++++++++++++++++++++++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 http/sys_rekey.go create mode 100644 http/sys_rekey_test.go diff --git a/http/handler.go b/http/handler.go index 5843bbb411..56ba8e0aab 100644 --- a/http/handler.go +++ b/http/handler.go @@ -42,6 +42,8 @@ func Handler(core *vault.Core) http.Handler { mux.Handle("/v1/sys/health", handleSysHealth(core)) mux.Handle("/v1/sys/rotate", handleSysRotate(core)) mux.Handle("/v1/sys/key-status", handleSysKeyStatus(core)) + mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core)) + mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core)) mux.Handle("/v1/", handleLogical(core)) // Wrap the handler in another handler to trigger all help paths. diff --git a/http/sys_rekey.go b/http/sys_rekey.go new file mode 100644 index 0000000000..5099878252 --- /dev/null +++ b/http/sys_rekey.go @@ -0,0 +1,171 @@ +package http + +import ( + "encoding/hex" + "errors" + "fmt" + "net/http" + + "github.com/hashicorp/vault/vault" +) + +func handleSysRekeyInit(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + handleSysRekeyInitGet(core, w, r) + case "POST", "PUT": + handleSysRekeyInitPut(core, w, r) + case "DELETE": + handleSysRekeyInitDelete(core, w, r) + default: + respondError(w, http.StatusMethodNotAllowed, nil) + } + }) +} + +func handleSysRekeyInitGet(core *vault.Core, w http.ResponseWriter, r *http.Request) { + // Get the current configuration + sealConfig, err := core.SealConfig() + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + if sealConfig == nil { + respondError(w, http.StatusBadRequest, fmt.Errorf( + "server is not yet initialized")) + return + } + + // Get the rekey configuration + rekeyConf, err := core.RekeyConfig() + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + + // Get the progress + progress, err := core.RekeyProgress() + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + + // Format the status + status := &RekeyStatusResponse{ + Started: false, + T: 0, + N: 0, + Progress: progress, + Required: sealConfig.SecretThreshold, + } + if rekeyConf != nil { + status.Started = true + status.T = rekeyConf.SecretThreshold + status.N = rekeyConf.SecretShares + } + respondOk(w, status) +} + +func handleSysRekeyInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) { + // Parse the request + var req RekeyRequest + if err := parseRequest(r, &req); err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + + // Initialize the rekey + err := core.RekeyInit(&vault.SealConfig{ + SecretShares: req.SecretShares, + SecretThreshold: req.SecretThreshold, + }) + if err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + respondOk(w, nil) +} + +func handleSysRekeyInitDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) { + err := core.RekeyCancel() + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + respondOk(w, nil) +} + +func handleSysRekeyUpdate(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PUT" { + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + + // Parse the request + var req RekeyUpdateRequest + if err := parseRequest(r, &req); err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + if req.Key == "" { + respondError( + w, http.StatusBadRequest, + errors.New("'key' must specified in request body as JSON")) + return + } + + // Decode the key, which is hex encoded + key, err := hex.DecodeString(req.Key) + if err != nil { + respondError( + w, http.StatusBadRequest, + errors.New("'key' must be a valid hex-string")) + return + } + + // Use the key to make progress on rekey + result, err := core.RekeyUpdate(key) + if err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + + // Format the response + resp := &RekeyUpdateResponse{} + if result != nil { + resp.Complete = true + + // Encode the keys + keys := make([]string, 0, len(result.SecretShares)) + for _, k := range result.SecretShares { + keys = append(keys, hex.EncodeToString(k)) + } + resp.Keys = keys + } + respondOk(w, resp) + }) +} + +type RekeyRequest struct { + SecretShares int `json:"secret_shares"` + SecretThreshold int `json:"secret_threshold"` +} + +type RekeyStatusResponse struct { + Started bool `json:"started"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Required int `json:"required"` +} + +type RekeyUpdateRequest struct { + Key string +} + +type RekeyUpdateResponse struct { + Complete bool `json:"complete"` + Keys []string `json:"keys"` +} diff --git a/http/sys_rekey_test.go b/http/sys_rekey_test.go new file mode 100644 index 0000000000..9b1ed57de3 --- /dev/null +++ b/http/sys_rekey_test.go @@ -0,0 +1,149 @@ +package http + +import ( + "encoding/hex" + "net/http" + "reflect" + "testing" + + "github.com/hashicorp/vault/vault" +) + +func TestSysRekeyInit_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/rekey/init") + if err != nil { + t.Fatalf("err: %s", err) + } + + var actual map[string]interface{} + expected := map[string]interface{}{ + "started": false, + "t": float64(0), + "n": float64(0), + "progress": float64(0), + "required": float64(1), + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestSysRekeyInit_Setup(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPut(t, addr+"/v1/sys/rekey/init", map[string]interface{}{ + "secret_shares": 5, + "secret_threshold": 3, + }) + testResponseStatus(t, resp, 204) + + resp, err := http.Get(addr + "/v1/sys/rekey/init") + if err != nil { + t.Fatalf("err: %s", err) + } + + var actual map[string]interface{} + expected := map[string]interface{}{ + "started": true, + "t": float64(3), + "n": float64(5), + "progress": float64(0), + "required": float64(1), + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestSysRekeyInit_Cancel(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPut(t, addr+"/v1/sys/rekey/init", map[string]interface{}{ + "secret_shares": 5, + "secret_threshold": 3, + }) + testResponseStatus(t, resp, 204) + + resp = testHttpDelete(t, addr+"/v1/sys/rekey/init") + testResponseStatus(t, resp, 204) + + resp, err := http.Get(addr + "/v1/sys/rekey/init") + if err != nil { + t.Fatalf("err: %s", err) + } + + var actual map[string]interface{} + expected := map[string]interface{}{ + "started": false, + "t": float64(0), + "n": float64(0), + "progress": float64(0), + "required": float64(1), + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestSysRekey_badKey(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPut(t, addr+"/v1/sys/rekey/update", map[string]interface{}{ + "key": "0123", + }) + testResponseStatus(t, resp, 400) +} + +func TestSysRekey_Update(t *testing.T) { + core, master, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPut(t, addr+"/v1/sys/rekey/init", map[string]interface{}{ + "secret_shares": 5, + "secret_threshold": 3, + }) + testResponseStatus(t, resp, 204) + + resp = testHttpPut(t, addr+"/v1/sys/rekey/update", map[string]interface{}{ + "key": hex.EncodeToString(master), + }) + + var actual map[string]interface{} + expected := map[string]interface{}{ + "complete": true, + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + + keys := actual["keys"].([]interface{}) + if len(keys) != 5 { + t.Fatalf("bad: %#v", keys) + } + + delete(actual, "keys") + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +}