http: generic read/write endpoint for secrets

This commit is contained in:
Mitchell Hashimoto 2015-03-15 19:34:47 -07:00
parent ca358f64dd
commit 742923452b
4 changed files with 123 additions and 1 deletions

View file

@ -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.

View file

@ -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
}

80
http/logical.go Normal file
View file

@ -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"`
}

41
http/logical_test.go Normal file
View file

@ -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)
}
}