mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 11:08:10 +00:00
http: init endpoints
This commit is contained in:
38
api/SPEC.md
38
api/SPEC.md
@@ -89,6 +89,44 @@ The following HTTP status codes are used throughout the API.
|
|||||||
- `503` - Vault is down for maintenance or is currently sealed.
|
- `503` - Vault is down for maintenance or is currently sealed.
|
||||||
Try again later.
|
Try again later.
|
||||||
|
|
||||||
|
# Group Initialization
|
||||||
|
|
||||||
|
## Initialization [/sys/init]
|
||||||
|
### Initialization Status [GET]
|
||||||
|
Returns the status of whether the vault is initialized or not. The
|
||||||
|
vault doesn't have to be unsealed for this operation.
|
||||||
|
|
||||||
|
+ Response 200 (application/json)
|
||||||
|
|
||||||
|
{
|
||||||
|
"initialized": true
|
||||||
|
}
|
||||||
|
|
||||||
|
### Initialize [POST]
|
||||||
|
Initialize the vault. This is an unauthenticated request to initially
|
||||||
|
setup a new vault. Although this is unauthenticated, it is still safe:
|
||||||
|
data cannot be in vault prior to initialization, and any future
|
||||||
|
authentication will fail if you didn't initialize it yourself.
|
||||||
|
Additionally, once initialized, a vault cannot be reinitialized.
|
||||||
|
|
||||||
|
This API is the only time Vault will ever be aware of your keys, and
|
||||||
|
the only time the keys will ever be returned in one unit. Care should
|
||||||
|
be taken to ensure that the output of this request is never logged,
|
||||||
|
and that the keys are properly distributed.
|
||||||
|
|
||||||
|
+ Request (application/json)
|
||||||
|
|
||||||
|
{
|
||||||
|
"secret_shares": 5,
|
||||||
|
"secret_threshold": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
+ Response 200 (application/json)
|
||||||
|
|
||||||
|
{
|
||||||
|
"keys": ["one", "two", "three"]
|
||||||
|
}
|
||||||
|
|
||||||
# Group Seal/Unseal
|
# Group Seal/Unseal
|
||||||
|
|
||||||
## Seal Status [/sys/seal-status]
|
## Seal Status [/sys/seal-status]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
// its own to mount the Vault API within another web server.
|
// its own to mount the Vault API within another web server.
|
||||||
func Handler(core *vault.Core) http.Handler {
|
func Handler(core *vault.Core) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/v1/sys/init", handleSysInit(core))
|
||||||
mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core))
|
mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core))
|
||||||
mux.Handle("/v1/sys/seal", handleSysSeal(core))
|
mux.Handle("/v1/sys/seal", handleSysSeal(core))
|
||||||
mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
|
mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
|
||||||
|
|||||||
75
http/sys_init.go
Normal file
75
http/sys_init.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/vault"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleSysInit(core *vault.Core) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case "GET":
|
||||||
|
handleSysInitGet(core, w, r)
|
||||||
|
case "PUT":
|
||||||
|
handleSysInitPut(core, w, r)
|
||||||
|
default:
|
||||||
|
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSysInitGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||||
|
init, err := core.Initialized()
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondOk(w, &InitStatusResponse{
|
||||||
|
Initialized: init,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Parse the request
|
||||||
|
var req InitRequest
|
||||||
|
if err := parseRequest(r, &req); err != nil {
|
||||||
|
respondError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
result, err := core.Initialize(&vault.SealConfig{
|
||||||
|
SecretShares: req.SecretShares,
|
||||||
|
SecretThreshold: req.SecretThreshold,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the keys
|
||||||
|
keys := make([]string, 0, len(result.SecretShares))
|
||||||
|
for _, k := range result.SecretShares {
|
||||||
|
keys = append(keys, hex.EncodeToString(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
respondOk(w, &InitResponse{
|
||||||
|
Keys: keys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitRequest struct {
|
||||||
|
SecretShares int `json:"secret_shares"`
|
||||||
|
SecretThreshold int `json:"secret_threshold"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitResponse struct {
|
||||||
|
Keys []string `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitStatusResponse struct {
|
||||||
|
Initialized bool `json:"initialized"`
|
||||||
|
}
|
||||||
90
http/sys_init_test.go
Normal file
90
http/sys_init_test.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSysInit_get(t *testing.T) {
|
||||||
|
core := testCore(t)
|
||||||
|
ln, addr := testServer(t, core)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
{
|
||||||
|
// Pre-init
|
||||||
|
resp, err := http.Get(addr + "/v1/sys/init")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual map[string]interface{}
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"initialized": false,
|
||||||
|
}
|
||||||
|
testResponseStatus(t, resp, 200)
|
||||||
|
testResponseBody(t, resp, &actual)
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCoreInit(t, core)
|
||||||
|
|
||||||
|
{
|
||||||
|
// Post-init
|
||||||
|
resp, err := http.Get(addr + "/v1/sys/init")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual map[string]interface{}
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"initialized": true,
|
||||||
|
}
|
||||||
|
testResponseStatus(t, resp, 200)
|
||||||
|
testResponseBody(t, resp, &actual)
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSysInit_put(t *testing.T) {
|
||||||
|
core := testCore(t)
|
||||||
|
ln, addr := testServer(t, core)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
resp := testHttpPut(t, addr+"/v1/sys/init", map[string]interface{}{
|
||||||
|
"secret_shares": 5,
|
||||||
|
"secret_threshold": 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
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 _, key := range keysRaw.([]interface{}) {
|
||||||
|
keySlice, err := hex.DecodeString(key.(string))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := core.Unseal(keySlice); err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seal, err := core.Sealed()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if seal {
|
||||||
|
t.Fatal("should not be sealed")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user