diff --git a/api/secret.go b/api/secret.go index 473e8f112..6dfacc003 100644 --- a/api/secret.go +++ b/api/secret.go @@ -14,7 +14,7 @@ type Secret struct { Renewable bool LeaseDuration int `mapstructure:"lease_duration"` LeaseDurationMax int `mapstructure:"lease_duration_max"` - Data map[string]interface{} `mapstructure:"-"` + Data map[string]interface{} `mapstructure:"data"` } // ParseSecret is used to parse a secret value from JSON from an io.Reader. diff --git a/http/handler.go b/http/handler.go index 924cfaaf0..a2bcd5488 100644 --- a/http/handler.go +++ b/http/handler.go @@ -15,6 +15,7 @@ func Handler(core *vault.Core) http.Handler { mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core)) mux.Handle("/v1/sys/seal", handleSysSeal(core)) mux.Handle("/v1/sys/unseal", handleSysUnseal(core)) + mux.Handle("/v1/", handleLogical(core)) return mux } diff --git a/http/logical.go b/http/logical.go new file mode 100644 index 000000000..c5faefc89 --- /dev/null +++ b/http/logical.go @@ -0,0 +1,80 @@ +package http + +import ( + "net/http" + "strings" + + "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/vault" +) + +func handleLogical(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Determine the path... + if !strings.HasPrefix(r.URL.Path, "/v1/") { + respondError(w, http.StatusNotFound, nil) + return + } + path := r.URL.Path[len("/v1/"):] + if path == "" { + respondError(w, http.StatusNotFound, nil) + return + } + + // Determine the operation + var op logical.Operation + switch r.Method { + case "GET": + op = logical.ReadOperation + case "PUT": + op = logical.WriteOperation + default: + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + + // Parse the request if we can + var req map[string]interface{} + if op == logical.WriteOperation { + if err := parseRequest(r, &req); err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + } + + // Make the internal request + resp, err := core.HandleRequest(&logical.Request{ + Operation: op, + Path: path, + Data: req, + }) + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + + var httpResp interface{} + if resp != nil { + logicalResp := &LogicalResponse{Data: resp.Data} + if resp.IsSecret && resp.Lease != nil { + logicalResp.VaultId = resp.Lease.VaultID + logicalResp.Renewable = resp.Lease.Renewable + logicalResp.LeaseDuration = int(resp.Lease.Duration.Seconds()) + logicalResp.LeaseDurationMax = int(resp.Lease.MaxDuration.Seconds()) + } + + httpResp = logicalResp + } + + // Respond + respondOk(w, httpResp) + }) +} + +type LogicalResponse struct { + VaultId string `json:"vault_id"` + Renewable bool `json:"renewable"` + LeaseDuration int `json:"lease_duration"` + LeaseDurationMax int `json:"lease_duration_max"` + Data map[string]interface{} `json:"data"` +} diff --git a/http/logical_test.go b/http/logical_test.go new file mode 100644 index 000000000..b8d91c1d6 --- /dev/null +++ b/http/logical_test.go @@ -0,0 +1,41 @@ +package http + +import ( + "net/http" + "reflect" + "testing" + + "github.com/hashicorp/vault/vault" +) + +func TestLogical(t *testing.T) { + core, _ := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + + resp := testHttpPut(t, addr+"/v1/secret/foo", map[string]interface{}{ + "data": "bar", + }) + testResponseStatus(t, resp, 204) + + resp, err := http.Get(addr + "/v1/secret/foo") + if err != nil { + t.Fatalf("err: %s", err) + } + + var actual map[string]interface{} + expected := map[string]interface{}{ + "vault_id": "", + "renewable": false, + "lease_duration": float64(0), + "lease_duration_max": float64(0), + "data": map[string]interface{}{ + "data": "bar", + }, + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +}