From 6fc21d88e22070ecbfc84e60d9e39f80c53a1cba Mon Sep 17 00:00:00 2001 From: Brian Kassouf Date: Fri, 10 Nov 2017 10:19:42 -0800 Subject: [PATCH] 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 --- api/sys_generate_root.go | 40 ++++++++++++++++++--- command/generate-root.go | 69 +++++++++++++++++++++++++++---------- http/handler.go | 4 +-- http/sys_generate_root.go | 12 +++---- vault/generate_root.go | 69 +++++++++++++++++++++++++++++-------- vault/generate_root_test.go | 22 ++++++------ 6 files changed, 159 insertions(+), 57 deletions(-) diff --git a/api/sys_generate_root.go b/api/sys_generate_root.go index 8dc2095f35..8f6e460ba5 100644 --- a/api/sys_generate_root.go +++ b/api/sys_generate_root.go @@ -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 } diff --git a/command/generate-root.go b/command/generate-root.go index 2d9521b7df..9402b8a24c 100644 --- a/command/generate-root.go +++ b/command/generate-root.go @@ -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) } diff --git a/http/handler.go b/http/handler.go index 4b5b90f71a..e8f7ca9e26 100644 --- a/http/handler.go +++ b/http/handler.go @@ -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))) diff --git a/http/sys_generate_root.go b/http/sys_generate_root.go index 3697f80358..1911e42590 100644 --- a/http/sys_generate_root.go +++ b/http/sys_generate_root.go @@ -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 diff --git a/vault/generate_root.go b/vault/generate_root.go index 4278b022f3..13d2806826 100644 --- a/vault/generate_root.go +++ b/vault/generate_root.go @@ -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") } diff --git a/vault/generate_root_test.go b/vault/generate_root_test.go index 38b4774a47..50dad32f27 100644 --- a/vault/generate_root_test.go +++ b/vault/generate_root_test.go @@ -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) }