Add API methods for creating a DR Operation Token and make generate root accept strategy types (#3565)

* Add API and Command code for generating a DR Operation Token

* Update generate root to accept different token strategies
This commit is contained in:
Brian Kassouf 2017-11-10 10:19:42 -08:00 committed by GitHub
parent c3b382d010
commit ab3b625a3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 159 additions and 57 deletions

View File

@ -1,7 +1,15 @@
package api
func (c *Sys) GenerateRootStatus() (*GenerateRootStatusResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/generate-root/attempt")
return c.generateRootStatusCommon("/v1/sys/generate-root/attempt")
}
func (c *Sys) GenerateDROperationTokenStatus() (*GenerateRootStatusResponse, error) {
return c.generateRootStatusCommon("/v1/sys/generate-dr-operation-token/attempt")
}
func (c *Sys) generateRootStatusCommon(path string) (*GenerateRootStatusResponse, error) {
r := c.c.NewRequest("GET", path)
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
@ -14,12 +22,20 @@ func (c *Sys) GenerateRootStatus() (*GenerateRootStatusResponse, error) {
}
func (c *Sys) GenerateRootInit(otp, pgpKey string) (*GenerateRootStatusResponse, error) {
return c.generateRootInitCommon("/v1/sys/generate-root/attempt", otp, pgpKey)
}
func (c *Sys) GenerateDROperationTokenInit(otp, pgpKey string) (*GenerateRootStatusResponse, error) {
return c.generateRootInitCommon("/v1/sys/generate-dr-operation-token/attempt", otp, pgpKey)
}
func (c *Sys) generateRootInitCommon(path, otp, pgpKey string) (*GenerateRootStatusResponse, error) {
body := map[string]interface{}{
"otp": otp,
"pgp_key": pgpKey,
}
r := c.c.NewRequest("PUT", "/v1/sys/generate-root/attempt")
r := c.c.NewRequest("PUT", path)
if err := r.SetJSONBody(body); err != nil {
return nil, err
}
@ -36,7 +52,15 @@ func (c *Sys) GenerateRootInit(otp, pgpKey string) (*GenerateRootStatusResponse,
}
func (c *Sys) GenerateRootCancel() error {
r := c.c.NewRequest("DELETE", "/v1/sys/generate-root/attempt")
return c.generateRootCancelCommon("/v1/sys/generate-root/attempt")
}
func (c *Sys) GenerateDROperationTokenCancel() error {
return c.generateRootCancelCommon("/v1/sys/generate-dr-operation-token/attempt")
}
func (c *Sys) generateRootCancelCommon(path string) error {
r := c.c.NewRequest("DELETE", path)
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
@ -45,12 +69,20 @@ func (c *Sys) GenerateRootCancel() error {
}
func (c *Sys) GenerateRootUpdate(shard, nonce string) (*GenerateRootStatusResponse, error) {
return c.generateRootUpdateCommon("/v1/sys/generate-root/update", shard, nonce)
}
func (c *Sys) GenerateDROperationTokenUpdate(shard, nonce string) (*GenerateRootStatusResponse, error) {
return c.generateRootUpdateCommon("/v1/sys/generate-dr-operation-token/update", shard, nonce)
}
func (c *Sys) generateRootUpdateCommon(path, shard, nonce string) (*GenerateRootStatusResponse, error) {
body := map[string]interface{}{
"key": shard,
"nonce": nonce,
}
r := c.c.NewRequest("PUT", "/v1/sys/generate-root/update")
r := c.c.NewRequest("PUT", path)
if err := r.SetJSONBody(body); err != nil {
return nil, err
}

View File

@ -29,11 +29,12 @@ type GenerateRootCommand struct {
}
func (c *GenerateRootCommand) Run(args []string) int {
var init, cancel, status, genotp bool
var init, cancel, status, genotp, drToken bool
var nonce, decode, otp, pgpKey string
var pgpKeyArr pgpkeys.PubKeyFilesFlag
flags := c.Meta.FlagSet("generate-root", meta.FlagSetDefault)
flags.BoolVar(&init, "init", false, "")
flags.BoolVar(&drToken, "dr-token", false, "")
flags.BoolVar(&cancel, "cancel", false, "")
flags.BoolVar(&status, "status", false, "")
flags.BoolVar(&genotp, "genotp", false, "")
@ -77,7 +78,11 @@ func (c *GenerateRootCommand) Run(args []string) int {
}
// Check if the root generation is started
rootGenerationStatus, err := client.Sys().GenerateRootStatus()
f := client.Sys().GenerateRootStatus
if drToken {
f = client.Sys().GenerateDROperationTokenStatus
}
rootGenerationStatus, err := f()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
return 1
@ -133,16 +138,20 @@ func (c *GenerateRootCommand) Run(args []string) int {
// Check if we are running doing any restricted variants
switch {
case init:
return c.initGenerateRoot(client, otp, pgpKey)
return c.initGenerateRoot(client, otp, pgpKey, drToken)
case cancel:
return c.cancelGenerateRoot(client)
return c.cancelGenerateRoot(client, drToken)
case status:
return c.rootGenerationStatus(client)
return c.rootGenerationStatus(client, drToken)
}
// Start the root generation process if not started
if !rootGenerationStatus.Started {
rootGenerationStatus, err = client.Sys().GenerateRootInit(otp, pgpKey)
f := client.Sys().GenerateRootInit
if drToken {
f = client.Sys().GenerateDROperationTokenInit
}
rootGenerationStatus, err = f(otp, pgpKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 1
@ -179,14 +188,19 @@ func (c *GenerateRootCommand) Run(args []string) int {
}
// Provide the key, this may potentially complete the update
statusResp, err := client.Sys().GenerateRootUpdate(strings.TrimSpace(key), c.Nonce)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error attempting generate-root update: %s", err))
return 1
{
f := client.Sys().GenerateRootUpdate
if drToken {
f = client.Sys().GenerateDROperationTokenUpdate
}
statusResp, err := f(strings.TrimSpace(key), c.Nonce)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error attempting generate-root update: %s", err))
return 1
}
c.dumpStatus(statusResp)
}
c.dumpStatus(statusResp)
return 0
}
@ -224,9 +238,14 @@ func (c *GenerateRootCommand) decode(encodedVal, otp string) int {
}
// initGenerateRoot is used to start the generation process
func (c *GenerateRootCommand) initGenerateRoot(client *api.Client, otp string, pgpKey string) int {
func (c *GenerateRootCommand) initGenerateRoot(client *api.Client, otp string, pgpKey string, drToken bool) int {
// Start the rekey
status, err := client.Sys().GenerateRootInit(otp, pgpKey)
f := client.Sys().GenerateRootInit
if drToken {
f = client.Sys().GenerateDROperationTokenInit
}
status, err := f(otp, pgpKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 1
@ -238,8 +257,12 @@ func (c *GenerateRootCommand) initGenerateRoot(client *api.Client, otp string, p
}
// cancelGenerateRoot is used to abort the generation process
func (c *GenerateRootCommand) cancelGenerateRoot(client *api.Client) int {
err := client.Sys().GenerateRootCancel()
func (c *GenerateRootCommand) cancelGenerateRoot(client *api.Client, drToken bool) int {
f := client.Sys().GenerateRootCancel
if drToken {
f = client.Sys().GenerateDROperationTokenCancel
}
err := f()
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to cancel root generation: %s", err))
return 1
@ -249,9 +272,13 @@ func (c *GenerateRootCommand) cancelGenerateRoot(client *api.Client) int {
}
// rootGenerationStatus is used just to fetch and dump the status
func (c *GenerateRootCommand) rootGenerationStatus(client *api.Client) int {
func (c *GenerateRootCommand) rootGenerationStatus(client *api.Client, drToken bool) int {
// Check the status
status, err := client.Sys().GenerateRootStatus()
f := client.Sys().GenerateRootStatus
if drToken {
f = client.Sys().GenerateDROperationTokenStatus
}
status, err := f()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
return 1
@ -350,6 +377,10 @@ Generate Root Options:
the unseal key is not being passed in via the command
line the nonce parameter is not required, and will
instead be displayed with the key prompt.
-dr-token Generate a Disaster Recovery operation token. This flag
should be set on '-init', '-cancel', and every time a
key is provided to specify the type of token to generate.
`
return strings.TrimSpace(helpText)
}

View File

@ -67,8 +67,8 @@ func Handler(core *vault.Core) http.Handler {
mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
mux.Handle("/v1/sys/leader", handleSysLeader(core))
mux.Handle("/v1/sys/health", handleSysHealth(core))
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core, handleSysGenerateRootAttempt(core)))
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core, handleSysGenerateRootUpdate(core)))
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy)))
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy)))
mux.Handle("/v1/sys/rekey/init", handleRequestForwarding(core, handleSysRekeyInit(core, false)))
mux.Handle("/v1/sys/rekey/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, false)))
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))

View File

@ -10,13 +10,13 @@ import (
"github.com/hashicorp/vault/vault"
)
func handleSysGenerateRootAttempt(core *vault.Core) http.Handler {
func handleSysGenerateRootAttempt(core *vault.Core, generateStrategy vault.GenerateRootStrategy) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
handleSysGenerateRootAttemptGet(core, w, r)
case "POST", "PUT":
handleSysGenerateRootAttemptPut(core, w, r)
handleSysGenerateRootAttemptPut(core, w, r, generateStrategy)
case "DELETE":
handleSysGenerateRootAttemptDelete(core, w, r)
default:
@ -77,7 +77,7 @@ func handleSysGenerateRootAttemptGet(core *vault.Core, w http.ResponseWriter, r
respondOk(w, status)
}
func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r *http.Request) {
func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r *http.Request, generateStrategy vault.GenerateRootStrategy) {
// Parse the request
var req GenerateRootInitRequest
if err := parseRequest(r, w, &req); err != nil {
@ -91,7 +91,7 @@ func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r
}
// Attemptialize the generation
err := core.GenerateRootInit(req.OTP, req.PGPKey)
err := core.GenerateRootInit(req.OTP, req.PGPKey, generateStrategy)
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
@ -109,7 +109,7 @@ func handleSysGenerateRootAttemptDelete(core *vault.Core, w http.ResponseWriter,
respondOk(w, nil)
}
func handleSysGenerateRootUpdate(core *vault.Core) http.Handler {
func handleSysGenerateRootUpdate(core *vault.Core, generateStrategy vault.GenerateRootStrategy) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Parse the request
var req GenerateRootUpdateRequest
@ -141,7 +141,7 @@ func handleSysGenerateRootUpdate(core *vault.Core) http.Handler {
}
// Use the key to make progress on root generation
result, err := core.GenerateRootUpdate(key, req.Nonce)
result, err := core.GenerateRootUpdate(key, req.Nonce, generateStrategy)
if err != nil {
respondError(w, http.StatusBadRequest, err)
return

View File

@ -12,6 +12,42 @@ import (
"github.com/hashicorp/vault/shamir"
)
const coreDROperationTokenPath = "core/dr-operation-token"
var (
// GenerateStandardRootTokenStrategy is the strategy used to generate a
// typical root token
GenerateStandardRootTokenStrategy GenerateRootStrategy = generateStandardRootToken{}
)
// GenerateRootStrategy allows us to swap out the strategy we want to use to
// create a token upon completion of the generate root process.
type GenerateRootStrategy interface {
generate(*Core) (string, func(), error)
}
// generateStandardRootToken implements the GenerateRootStrategy and is in
// charge of creating standard root tokens.
type generateStandardRootToken struct{}
func (g generateStandardRootToken) generate(c *Core) (string, func(), error) {
te, err := c.tokenStore.rootToken()
if err != nil {
c.logger.Error("core: root token generation failed", "error", err)
return "", nil, err
}
if te == nil {
c.logger.Error("core: got nil token entry back from root generation")
return "", nil, fmt.Errorf("got nil token entry back from root generation")
}
cleanupFunc := func() {
c.tokenStore.Revoke(te.ID)
}
return te.ID, cleanupFunc, nil
}
// GenerateRootConfig holds the configuration for a root generation
// command.
type GenerateRootConfig struct {
@ -19,6 +55,7 @@ type GenerateRootConfig struct {
PGPKey string
PGPFingerprint string
OTP string
Strategy GenerateRootStrategy
}
// GenerateRootResult holds the result of a root generation update
@ -68,12 +105,13 @@ func (c *Core) GenerateRootConfiguration() (*GenerateRootConfig, error) {
conf = new(GenerateRootConfig)
*conf = *c.generateRootConfig
conf.OTP = ""
conf.Strategy = nil
}
return conf, nil
}
// GenerateRootInit is used to initialize the root generation settings
func (c *Core) GenerateRootInit(otp, pgpKey string) error {
func (c *Core) GenerateRootInit(otp, pgpKey string, strategy GenerateRootStrategy) error {
var fingerprint string
switch {
case len(otp) > 0:
@ -127,6 +165,7 @@ func (c *Core) GenerateRootInit(otp, pgpKey string) error {
OTP: otp,
PGPKey: pgpKey,
PGPFingerprint: fingerprint,
Strategy: strategy,
}
if c.logger.IsInfo() {
@ -136,7 +175,7 @@ func (c *Core) GenerateRootInit(otp, pgpKey string) error {
}
// GenerateRootUpdate is used to provide a new key part
func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult, error) {
func (c *Core) GenerateRootUpdate(key []byte, nonce string, strategy GenerateRootStrategy) (*GenerateRootResult, error) {
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
@ -189,6 +228,10 @@ func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult
return nil, fmt.Errorf("incorrect nonce supplied; nonce for this root generation operation is %s", c.generateRootConfig.Nonce)
}
if strategy != c.generateRootConfig.Strategy {
return nil, fmt.Errorf("incorrect stategy supplied; a generate root operation of another type is already in progress")
}
// Check if we already have this piece
for _, existing := range c.generateRootProgress {
if bytes.Equal(existing, key) {
@ -238,24 +281,20 @@ func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult
}
}
te, err := c.tokenStore.rootToken()
// Run the generate strategy
tokenUUID, cleanupFunc, err := strategy.generate(c)
if err != nil {
c.logger.Error("core: root token generation failed", "error", err)
return nil, err
}
if te == nil {
c.logger.Error("core: got nil token entry back from root generation")
return nil, fmt.Errorf("got nil token entry back from root generation")
}
uuidBytes, err := uuid.ParseUUID(te.ID)
uuidBytes, err := uuid.ParseUUID(tokenUUID)
if err != nil {
c.tokenStore.Revoke(te.ID)
cleanupFunc()
c.logger.Error("core: error getting generated token bytes", "error", err)
return nil, err
}
if uuidBytes == nil {
c.tokenStore.Revoke(te.ID)
cleanupFunc()
c.logger.Error("core: got nil parsed UUID bytes")
return nil, fmt.Errorf("got nil parsed UUID bytes")
}
@ -269,22 +308,22 @@ func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult
// just encode the value we're passing in.
tokenBytes, err = xor.XORBase64(c.generateRootConfig.OTP, base64.StdEncoding.EncodeToString(uuidBytes))
if err != nil {
c.tokenStore.Revoke(te.ID)
cleanupFunc()
c.logger.Error("core: xor of root token failed", "error", err)
return nil, err
}
case len(c.generateRootConfig.PGPKey) > 0:
_, tokenBytesArr, err := pgpkeys.EncryptShares([][]byte{[]byte(te.ID)}, []string{c.generateRootConfig.PGPKey})
_, tokenBytesArr, err := pgpkeys.EncryptShares([][]byte{[]byte(tokenUUID)}, []string{c.generateRootConfig.PGPKey})
if err != nil {
c.tokenStore.Revoke(te.ID)
cleanupFunc()
c.logger.Error("core: error encrypting new root token", "error", err)
return nil, err
}
tokenBytes = tokenBytesArr[0]
default:
c.tokenStore.Revoke(te.ID)
cleanupFunc()
return nil, fmt.Errorf("unreachable condition")
}

View File

@ -17,7 +17,7 @@ func TestCore_GenerateRoot_Lifecycle(t *testing.T) {
func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte) {
// Verify update not allowed
if _, err := c.GenerateRootUpdate(keys[0], ""); err == nil {
if _, err := c.GenerateRootUpdate(keys[0], "", GenerateStandardRootTokenStrategy); err == nil {
t.Fatalf("no root generation in progress")
}
@ -51,7 +51,7 @@ func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte
}
// Start a root generation
err = c.GenerateRootInit(base64.StdEncoding.EncodeToString(otpBytes), "")
err = c.GenerateRootInit(base64.StdEncoding.EncodeToString(otpBytes), "", GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -93,13 +93,13 @@ func testCore_GenerateRoot_Init_Common(t *testing.T, c *Core) {
t.Fatal(err)
}
err = c.GenerateRootInit(base64.StdEncoding.EncodeToString(otpBytes), "")
err = c.GenerateRootInit(base64.StdEncoding.EncodeToString(otpBytes), "", GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}
// Second should fail
err = c.GenerateRootInit("", pgpkeys.TestPubKey1)
err = c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy)
if err == nil {
t.Fatalf("should fail")
}
@ -121,7 +121,7 @@ func testCore_GenerateRoot_InvalidMasterNonce_Common(t *testing.T, c *Core, keys
t.Fatal(err)
}
err = c.GenerateRootInit(base64.StdEncoding.EncodeToString(otpBytes), "")
err = c.GenerateRootInit(base64.StdEncoding.EncodeToString(otpBytes), "", GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -136,14 +136,14 @@ func testCore_GenerateRoot_InvalidMasterNonce_Common(t *testing.T, c *Core, keys
}
// Provide the nonce (invalid)
_, err = c.GenerateRootUpdate(keys[0], "abcd")
_, err = c.GenerateRootUpdate(keys[0], "abcd", GenerateStandardRootTokenStrategy)
if err == nil {
t.Fatalf("expected error")
}
// Provide the master (invalid)
for _, key := range keys {
_, err = c.GenerateRootUpdate(key, rgconf.Nonce)
_, err = c.GenerateRootUpdate(key, rgconf.Nonce, GenerateStandardRootTokenStrategy)
}
if err == nil {
t.Fatalf("expected error")
@ -164,7 +164,7 @@ func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byt
otp := base64.StdEncoding.EncodeToString(otpBytes)
// Start a root generation
err = c.GenerateRootInit(otp, "")
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -181,7 +181,7 @@ func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byt
// Provide the keys
var result *GenerateRootResult
for _, key := range keys {
result, err = c.GenerateRootUpdate(key, rkconf.Nonce)
result, err = c.GenerateRootUpdate(key, rkconf.Nonce, GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -241,7 +241,7 @@ func TestCore_GenerateRoot_Update_PGP(t *testing.T) {
func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byte) {
// Start a root generation
err := c.GenerateRootInit("", pgpkeys.TestPubKey1)
err := c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -258,7 +258,7 @@ func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byt
// Provide the keys
var result *GenerateRootResult
for _, key := range keys {
result, err = c.GenerateRootUpdate(key, rkconf.Nonce)
result, err = c.GenerateRootUpdate(key, rkconf.Nonce, GenerateStandardRootTokenStrategy)
if err != nil {
t.Fatalf("err: %v", err)
}