Add the ability to generate root tokens via unseal keys.
This commit is contained in:
parent
1692471445
commit
3b994dbc7f
|
@ -0,0 +1,73 @@
|
|||
package api
|
||||
|
||||
func (c *Sys) RootGenerationStatus() (*RootGenerationStatusResponse, error) {
|
||||
r := c.c.NewRequest("GET", "/v1/sys/root-generation/attempt")
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RootGenerationStatusResponse
|
||||
err = resp.DecodeJSON(&result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (c *Sys) RootGenerationInit(otp, pgpKey string) error {
|
||||
body := map[string]interface{}{
|
||||
"otp": otp,
|
||||
"pgp_key": pgpKey,
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("PUT", "/v1/sys/root-generation/attempt")
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Sys) RootGenerationCancel() error {
|
||||
r := c.c.NewRequest("DELETE", "/v1/sys/root-generation/attempt")
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Sys) RootGenerationUpdate(shard, nonce string) (*RootGenerationStatusResponse, error) {
|
||||
body := map[string]interface{}{
|
||||
"key": shard,
|
||||
"nonce": nonce,
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("PUT", "/v1/sys/root-generation/update")
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RootGenerationStatusResponse
|
||||
err = resp.DecodeJSON(&result)
|
||||
return &result, err
|
||||
}
|
||||
|
||||
type RootGenerationStatusResponse struct {
|
||||
Nonce string
|
||||
Started bool
|
||||
Progress int
|
||||
Required int
|
||||
Complete bool
|
||||
EncodedRootToken string `json:"encoded_root_token"`
|
||||
PGPFingerprint string `json:"pgp_fingerprint"`
|
||||
}
|
|
@ -182,6 +182,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
|
|||
}, nil
|
||||
},
|
||||
|
||||
"generate-root": func() (cli.Command, error) {
|
||||
return &command.GenerateRootCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"renew": func() (cli.Command, error) {
|
||||
return &command.RenewCommand{
|
||||
Meta: meta,
|
||||
|
|
|
@ -0,0 +1,348 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/password"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/helper/xor"
|
||||
)
|
||||
|
||||
// GenerateRootCommand is a Command that generates a new root token.
|
||||
type GenerateRootCommand struct {
|
||||
Meta
|
||||
|
||||
// Key can be used to pre-seed the key. If it is set, it will not
|
||||
// be asked with the `password` helper.
|
||||
Key string
|
||||
|
||||
// The nonce for the rekey request to send along
|
||||
Nonce string
|
||||
}
|
||||
|
||||
func (c *GenerateRootCommand) Run(args []string) int {
|
||||
var init, cancel, status, genotp bool
|
||||
var nonce, decode, otp, pgpKey string
|
||||
var pgpKeyArr pgpkeys.PubKeyFilesFlag
|
||||
flags := c.Meta.FlagSet("generate-root", FlagSetDefault)
|
||||
flags.BoolVar(&init, "init", false, "")
|
||||
flags.BoolVar(&cancel, "cancel", false, "")
|
||||
flags.BoolVar(&status, "status", false, "")
|
||||
flags.BoolVar(&genotp, "genotp", false, "")
|
||||
flags.StringVar(&decode, "decode", "", "")
|
||||
flags.StringVar(&otp, "otp", "", "")
|
||||
flags.StringVar(&nonce, "nonce", "", "")
|
||||
flags.Var(&pgpKeyArr, "pgp-key", "")
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if genotp {
|
||||
buf := make([]byte, 16)
|
||||
readLen, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading random bytes: %s", err))
|
||||
return 1
|
||||
}
|
||||
if readLen != 16 {
|
||||
c.Ui.Error(fmt.Sprintf("Read %d bytes when we should have read 16", readLen))
|
||||
return 1
|
||||
}
|
||||
c.Ui.Output(fmt.Sprintf("OTP: %s", base64.StdEncoding.EncodeToString(buf)))
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(decode) > 0 {
|
||||
if len(otp) == 0 {
|
||||
c.Ui.Error("Both the value to decode and the OTP must be passed in")
|
||||
return 1
|
||||
}
|
||||
return c.decode(decode, otp)
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error initializing client: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Check if the root generation is started
|
||||
rootGenerationStatus, err := client.Sys().RootGenerationStatus()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// If we are initing, or if we are not started but are not running a
|
||||
// special function, check otp and pgpkey
|
||||
if init ||
|
||||
(!init && !cancel && !status && !genotp && len(decode) == 0 && !rootGenerationStatus.Started) {
|
||||
switch {
|
||||
case len(otp) == 0 && (pgpKeyArr == nil || len(pgpKeyArr) == 0):
|
||||
c.Ui.Error("-otp or -pgp-key must be specified")
|
||||
return 1
|
||||
case len(otp) != 0 && pgpKeyArr != nil && len(pgpKeyArr) != 0:
|
||||
c.Ui.Error("Only one of -otp or -pgp-key must be specified")
|
||||
return 1
|
||||
case len(otp) != 0:
|
||||
err := c.verifyOTP(otp)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error verifying the provided OTP: %s", err))
|
||||
return 1
|
||||
}
|
||||
case pgpKeyArr != nil:
|
||||
if len(pgpKeyArr) != 1 {
|
||||
c.Ui.Error("Could not parse PGP key")
|
||||
return 1
|
||||
}
|
||||
if len(pgpKeyArr[0]) == 0 {
|
||||
c.Ui.Error("Got an empty PGP key")
|
||||
return 1
|
||||
}
|
||||
pgpKey = pgpKeyArr[0]
|
||||
default:
|
||||
panic("unreachable case")
|
||||
}
|
||||
}
|
||||
|
||||
if nonce != "" {
|
||||
c.Nonce = nonce
|
||||
}
|
||||
|
||||
// Check if we are running doing any restricted variants
|
||||
switch {
|
||||
case init:
|
||||
return c.initRootGeneration(client, otp, pgpKey)
|
||||
case cancel:
|
||||
return c.cancelRootGeneration(client)
|
||||
case status:
|
||||
return c.rootGenerationStatus(client)
|
||||
}
|
||||
|
||||
// Start the root generation process if not started
|
||||
if !rootGenerationStatus.Started {
|
||||
err = client.Sys().RootGenerationInit(otp, pgpKey)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
|
||||
return 1
|
||||
}
|
||||
rootGenerationStatus, err = client.Sys().RootGenerationStatus()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
|
||||
return 1
|
||||
}
|
||||
c.Nonce = rootGenerationStatus.Nonce
|
||||
}
|
||||
|
||||
serverNonce := rootGenerationStatus.Nonce
|
||||
|
||||
// Get the unseal key
|
||||
args = flags.Args()
|
||||
key := c.Key
|
||||
if len(args) > 0 {
|
||||
key = args[0]
|
||||
}
|
||||
if key == "" {
|
||||
c.Nonce = serverNonce
|
||||
fmt.Printf("Root generation operation nonce: %s\n", serverNonce)
|
||||
fmt.Printf("Key (will be hidden): ")
|
||||
key, err = password.Read(os.Stdin)
|
||||
fmt.Printf("\n")
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error attempting to ask for password. The raw error message\n"+
|
||||
"is shown below, but the most common reason for this error is\n"+
|
||||
"that you attempted to pipe a value into unseal or you're\n"+
|
||||
"executing `vault generate-root` from outside of a terminal.\n\n"+
|
||||
"You should use `vault generate-root` from a terminal for maximum\n"+
|
||||
"security. If this isn't an option, the unseal key can be passed\n"+
|
||||
"in using the first parameter.\n\n"+
|
||||
"Raw error: %s", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Provide the key, this may potentially complete the update
|
||||
statusResp, err := client.Sys().RootGenerationUpdate(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)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *GenerateRootCommand) verifyOTP(otp string) error {
|
||||
if len(otp) == 0 {
|
||||
return fmt.Errorf("No OTP passed in")
|
||||
}
|
||||
otpBytes, err := base64.StdEncoding.DecodeString(otp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error decoding base64 OTP value: %s", err)
|
||||
}
|
||||
if otpBytes == nil || len(otpBytes) != 16 {
|
||||
return fmt.Errorf("Decoded OTP value is invalid or wrong length")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GenerateRootCommand) decode(encodedVal, otp string) int {
|
||||
tokenBytes, err := xor.XORBase64(encodedVal, otp)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
token, err := uuid.FormatUUID(tokenBytes)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error formatting base64 token value: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf("Root token: %s", token))
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// initRootGeneration is used to start the generation process
|
||||
func (c *GenerateRootCommand) initRootGeneration(client *api.Client, otp string, pgpKey string) int {
|
||||
// Start the rekey
|
||||
err := client.Sys().RootGenerationInit(otp, pgpKey)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Provide the current status
|
||||
return c.rootGenerationStatus(client)
|
||||
}
|
||||
|
||||
// cancelRootGeneration is used to abort the generation process
|
||||
func (c *GenerateRootCommand) cancelRootGeneration(client *api.Client) int {
|
||||
err := client.Sys().RootGenerationCancel()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to cancel root generation: %s", err))
|
||||
return 1
|
||||
}
|
||||
c.Ui.Output("Root generation canceled.")
|
||||
return 0
|
||||
}
|
||||
|
||||
// rootGenerationStatus is used just to fetch and dump the status
|
||||
func (c *GenerateRootCommand) rootGenerationStatus(client *api.Client) int {
|
||||
// Check the status
|
||||
status, err := client.Sys().RootGenerationStatus()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.dumpStatus(status)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// dumpStatus dumps the status to output
|
||||
func (c *GenerateRootCommand) dumpStatus(status *api.RootGenerationStatusResponse) {
|
||||
// Dump the status
|
||||
statString := fmt.Sprintf(
|
||||
"Nonce: %s\n"+
|
||||
"Started: %v\n"+
|
||||
"Rekey Progress: %d\n"+
|
||||
"Required Keys: %d\n"+
|
||||
"Complete: %t",
|
||||
status.Nonce,
|
||||
status.Started,
|
||||
status.Progress,
|
||||
status.Required,
|
||||
status.Complete,
|
||||
)
|
||||
if len(status.PGPFingerprint) > 0 {
|
||||
statString = fmt.Sprintf("%s\nPGP Fingerprint: %s", statString, status.PGPFingerprint)
|
||||
}
|
||||
if len(status.EncodedRootToken) > 0 {
|
||||
statString = fmt.Sprintf("%s\n\nEncoded root token: %s", statString, status.EncodedRootToken)
|
||||
}
|
||||
c.Ui.Output(statString)
|
||||
}
|
||||
|
||||
func (c *GenerateRootCommand) Synopsis() string {
|
||||
return "Promotes a token to a root token"
|
||||
}
|
||||
|
||||
func (c *GenerateRootCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault generate-root [options] [key]
|
||||
|
||||
'generate-root' is used to create a new root token.
|
||||
|
||||
Root generation can only be done when the Vault is already unsealed. The
|
||||
operation is done online, but requires that a threshold of the current unseal
|
||||
keys be provided.
|
||||
|
||||
One (and only one) of the following must be provided at attempt
|
||||
initialization time:
|
||||
|
||||
1) A 16-byte, base64-encoded One Time Password (OTP) provided in the '-otp'
|
||||
flag; the token is XOR'd with this value before it is returned once the final
|
||||
unseal key has been provided. The '-decode' operation can be used with this
|
||||
value and the OTP to output the final token value. The '-genotp' flag can be
|
||||
used to generate a suitable value.
|
||||
|
||||
or
|
||||
|
||||
2) A file containing a PGP key (binary or base64-encoded) or a Keybase.io
|
||||
username in the format of "keybase:<username>" in the '-pgp-key' flag. The
|
||||
final token value will be encrypted with this public key and base64-encoded.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage() + `
|
||||
|
||||
Rekey Options:
|
||||
|
||||
-init Initialize the root generation attempt. This can only
|
||||
be done if no generation is already initiated.
|
||||
|
||||
-cancel Reset the root generation process by throwing away
|
||||
prior unseal keys and the configuration.
|
||||
|
||||
-status Prints the status of the current attempt. This can be
|
||||
used to see the status without attempting to provide
|
||||
an unseal key.
|
||||
|
||||
-decode=abcd Decodes and outputs the generated root token. The OTP
|
||||
used at '-init' time must be provided in the '-otp'
|
||||
parameter.
|
||||
|
||||
-genotp Returns a high-quality OTP suitable for passing into
|
||||
the '-init' method.
|
||||
|
||||
-otp=abcd The base64-encoded 16-byte OTP for use with the
|
||||
'-init' or '-decode' methods.
|
||||
|
||||
-pgp-key A file on disk containing a binary- or base64-format
|
||||
public PGP key, or a Keybase username specified as
|
||||
"keybase:<username>". The output root token will be
|
||||
encrypted and base64-encoded, in order, with the given
|
||||
public key.
|
||||
|
||||
-nonce=abcd The nonce provided at initialization time. This same
|
||||
nonce value must be provided with each unseal key. If
|
||||
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.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/helper/xor"
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestGenerateRoot_Cancel(t *testing.T) {
|
||||
core, key, _ := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &GenerateRootCommand{
|
||||
Key: hex.EncodeToString(key),
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
args := []string{"-address", addr, "-init", "-otp", otp}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
args = []string{"-address", addr, "-cancel"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
config, err := core.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if config != nil {
|
||||
t.Fatal("should not have a config for root generation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRoot_status(t *testing.T) {
|
||||
core, key, _ := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &GenerateRootCommand{
|
||||
Key: hex.EncodeToString(key),
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
args := []string{"-address", addr, "-init", "-otp", otp}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
args = []string{"-address", addr, "-status"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(string(ui.OutputWriter.Bytes()), "Started: true") {
|
||||
t.Fatalf("bad: %s", ui.OutputWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRoot_OTP(t *testing.T) {
|
||||
core, ts, key, _ := vault.TestCoreWithTokenStore(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &GenerateRootCommand{
|
||||
Key: hex.EncodeToString(key),
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
// Generate an OTP
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
// Init the attempt
|
||||
args := []string{
|
||||
"-address", addr,
|
||||
"-init",
|
||||
"-otp", otp,
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
config, err := core.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
c.Nonce = config.Nonce
|
||||
|
||||
// Provide the key
|
||||
args = []string{
|
||||
"-address", addr,
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
beforeNAfter := strings.Split(ui.OutputWriter.String(), "Encoded root token: ")
|
||||
if len(beforeNAfter) != 2 {
|
||||
t.Fatalf("did not find encoded root token in %s", ui.OutputWriter.String())
|
||||
}
|
||||
encodedToken := strings.TrimSpace(beforeNAfter[1])
|
||||
|
||||
decodedToken, err := xor.XORBase64(encodedToken, otp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
token, err := uuid.FormatUUID(decodedToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "lookup-self")
|
||||
req.ClientToken = token
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("error running token lookup-self: %v", err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatalf("got nil resp with token lookup-self")
|
||||
}
|
||||
if resp.Data == nil {
|
||||
t.Fatalf("got nil resp.Data with token lookup-self")
|
||||
}
|
||||
|
||||
if resp.Data["orphan"].(bool) != true ||
|
||||
resp.Data["ttl"].(int64) != 0 ||
|
||||
resp.Data["num_uses"].(int) != 0 ||
|
||||
resp.Data["meta"].(map[string]string) != map[string]string(nil) ||
|
||||
len(resp.Data["policies"].([]string)) != 1 ||
|
||||
resp.Data["policies"].([]string)[0] != "root" {
|
||||
t.Fatalf("bad: %#v", resp.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRoot_PGP(t *testing.T) {
|
||||
core, ts, key, _ := vault.TestCoreWithTokenStore(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &GenerateRootCommand{
|
||||
Key: hex.EncodeToString(key),
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
tempDir, pubFiles, err := getPubKeyFiles(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Init the attempt
|
||||
args := []string{
|
||||
"-address", addr,
|
||||
"-init",
|
||||
"-pgp-key", pubFiles[0],
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
config, err := core.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
c.Nonce = config.Nonce
|
||||
|
||||
// Provide the key
|
||||
args = []string{
|
||||
"-address", addr,
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
beforeNAfter := strings.Split(ui.OutputWriter.String(), "Encoded root token: ")
|
||||
if len(beforeNAfter) != 2 {
|
||||
t.Fatalf("did not find encoded root token in %s", ui.OutputWriter.String())
|
||||
}
|
||||
encodedToken := strings.TrimSpace(beforeNAfter[1])
|
||||
|
||||
ptBuf, err := pgpkeys.DecryptBytes(encodedToken, pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ptBuf == nil {
|
||||
t.Fatal("returned plain text buffer is nil")
|
||||
}
|
||||
|
||||
token := ptBuf.String()
|
||||
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "lookup-self")
|
||||
req.ClientToken = token
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("error running token lookup-self: %v", err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatalf("got nil resp with token lookup-self")
|
||||
}
|
||||
if resp.Data == nil {
|
||||
t.Fatalf("got nil resp.Data with token lookup-self")
|
||||
}
|
||||
|
||||
if resp.Data["orphan"].(bool) != true ||
|
||||
resp.Data["ttl"].(int64) != 0 ||
|
||||
resp.Data["num_uses"].(int) != 0 ||
|
||||
resp.Data["meta"].(map[string]string) != map[string]string(nil) ||
|
||||
len(resp.Data["policies"].([]string)) != 1 ||
|
||||
resp.Data["policies"].([]string)[0] != "root" {
|
||||
t.Fatalf("bad: %#v", resp.Data)
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
|
@ -29,7 +30,7 @@ func getPubKeyFiles(t *testing.T) (string, []string, error) {
|
|||
tempDir + "/aapubkey1",
|
||||
}
|
||||
decoder := base64.StdEncoding
|
||||
pub1Bytes, err := decoder.DecodeString(pubKey1)
|
||||
pub1Bytes, err := decoder.DecodeString(pgpkeys.TestPubKey1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding bytes for public key 1: %s", err)
|
||||
}
|
||||
|
@ -37,7 +38,7 @@ func getPubKeyFiles(t *testing.T) (string, []string, error) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error writing pub key 1 to temp file: %s", err)
|
||||
}
|
||||
pub2Bytes, err := decoder.DecodeString(pubKey2)
|
||||
pub2Bytes, err := decoder.DecodeString(pgpkeys.TestPubKey2)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding bytes for public key 2: %s", err)
|
||||
}
|
||||
|
@ -45,7 +46,7 @@ func getPubKeyFiles(t *testing.T) (string, []string, error) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error writing pub key 2 to temp file: %s", err)
|
||||
}
|
||||
pub3Bytes, err := decoder.DecodeString(pubKey3)
|
||||
pub3Bytes, err := decoder.DecodeString(pgpkeys.TestPubKey3)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding bytes for public key 3: %s", err)
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ func getPubKeyFiles(t *testing.T) (string, []string, error) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error writing pub key 3 to temp file: %s", err)
|
||||
}
|
||||
err = ioutil.WriteFile(pubFiles[3], []byte(aaPubKey1), 0755)
|
||||
err = ioutil.WriteFile(pubFiles[3], []byte(pgpkeys.TestAAPubKey1), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing aa pub key 1 to temp file: %s", err)
|
||||
}
|
||||
|
@ -67,15 +68,15 @@ func parseDecryptAndTestUnsealKeys(t *testing.T,
|
|||
backupKeys map[string][]string,
|
||||
core *vault.Core) {
|
||||
decoder := base64.StdEncoding
|
||||
priv1Bytes, err := decoder.DecodeString(privKey1)
|
||||
priv1Bytes, err := decoder.DecodeString(pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding bytes for private key 1: %s", err)
|
||||
}
|
||||
priv2Bytes, err := decoder.DecodeString(privKey2)
|
||||
priv2Bytes, err := decoder.DecodeString(pgpkeys.TestPrivKey2)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding bytes for private key 2: %s", err)
|
||||
}
|
||||
priv3Bytes, err := decoder.DecodeString(privKey3)
|
||||
priv3Bytes, err := decoder.DecodeString(pgpkeys.TestPrivKey3)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding bytes for private key 3: %s", err)
|
||||
}
|
||||
|
@ -130,12 +131,12 @@ func parseDecryptAndTestUnsealKeys(t *testing.T,
|
|||
|
||||
unsealKeys := []string{}
|
||||
ptBuf := bytes.NewBuffer(nil)
|
||||
for i, keyHex := range privBytes {
|
||||
for i, privKeyBytes := range privBytes {
|
||||
if i > 2 {
|
||||
break
|
||||
}
|
||||
ptBuf.Reset()
|
||||
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(keyHex)))
|
||||
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(privKeyBytes)))
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing private key %d: %s", i, err)
|
||||
}
|
||||
|
@ -172,265 +173,3 @@ func parseDecryptAndTestUnsealKeys(t *testing.T,
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
const privKey1 = `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
|
||||
7u2CfYyFPu3AlUaGNMBlvy6PEpU=`
|
||||
const privKey2 = `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
|
||||
dXlg3mimA+iK7tABQfG0RJ9YzWs=`
|
||||
const privKey3 = `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
|
||||
KFIzGEPKVoBF9U7VBXpyxpsz+A==`
|
||||
const pubKey1 = `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
|
||||
OIu60YPNE4+h7u2CfYyFPu3AlUaGNMBlvy6PEpU=`
|
||||
const pubKey2 = `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
|
||||
MFOMVRn1dc3qdXlg3mimA+iK7tABQfG0RJ9YzWs=`
|
||||
const pubKey3 = `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
|
||||
puKcgucA0LotbihSMxhDylaARfVO1QV6csabM/g=`
|
||||
const aaPubKey1 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
mQENBFXbjPUBCADjNjCUQwfxKL+RR2GA6pv/1K+zJZ8UWIF9S0lk7cVIEfJiprzz
|
||||
wiMwBS5cD0darGin1FHvIWOZxujA7oW0O2TUuatqI3aAYDTfRYurh6iKLC+VS+F7
|
||||
H+/mhfFvKmgr0Y5kDCF1j0T/063QZ84IRGucR/X43IY7kAtmxGXH0dYOCzOe5UBX
|
||||
1fTn3mXGe2ImCDWBH7gOViynXmb6XNvXkP0fsF5St9jhO7mbZU9EFkv9O3t3EaUR
|
||||
fHopsCVDOlCkFCw5ArY+DUORHRzoMX0PnkyQb5OzibkChzpg8hQssKeVGpuskTdz
|
||||
5Q7PtdW71jXd4fFVzoNH8fYwRpziD2xNvi6HABEBAAG0EFZhdWx0IFRlc3QgS2V5
|
||||
IDGJATgEEwECACIFAlXbjPUCGy8GCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJ
|
||||
EOfLr44BHbeTo+sH/i7bapIgPnZsJ81hmxPj4W12uvunksGJiC7d4hIHsG7kmJRT
|
||||
JfjECi+AuTGeDwBy84TDcRaOB6e79fj65Fg6HgSahDUtKJbGxj/lWzmaBuTzlN3C
|
||||
Ee8cMwIPqPT2kajJVdOyrvkyuFOdPFOEA7bdCH0MqgIdM2SdF8t40k/ATfuD2K1Z
|
||||
mumJ508I3gF39jgTnPzD4C8quswrMQ3bzfvKC3klXRlBC0yoArn+0QA3cf2B9T4z
|
||||
J2qnvgotVbeK/b1OJRNj6Poeo+SsWNc/A5mw7lGScnDgL3yfwCm1gQXaQKfOt5x+
|
||||
7GqhWDw10q+bJpJlI10FfzAnhMF9etSqSeURBRW5AQ0EVduM9QEIAL53hJ5bZJ7o
|
||||
EDCnaY+SCzt9QsAfnFTAnZJQrvkvusJzrTQ088eUQmAjvxkfRqnv981fFwGnh2+I
|
||||
1Ktm698UAZS9Jt8yjak9wWUICKQO5QUt5k8cHwldQXNXVXFa+TpQWQR5yW1a9okj
|
||||
h5o/3d4cBt1yZPUJJyLKY43Wvptb6EuEsScO2DnRkh5wSMDQ7dTooddJCmaq3LTj
|
||||
OleRFQbu9ij386Do6jzK69mJU56TfdcydkxkWF5NZLGnED3lq+hQNbe+8UI5tD2o
|
||||
P/3r5tXKgMy1R/XPvR/zbfwvx4FAKFOP01awLq4P3d/2xOkMu4Lu9p315E87DOle
|
||||
Ywxk+FoTqXEAEQEAAYkCPgQYAQIACQUCVduM9QIbLgEpCRDny6+OAR23k8BdIAQZ
|
||||
AQIABgUCVduM9QAKCRAID0JGyHtSGmqYB/4m4rJbbWa7dBJ8VqRU7ZKnNRDR9CVh
|
||||
EGipBmpDGRYulEimOPzLUX/ZXZmTZzgemeXLBaJJlWnopVUWuAsyjQuZAfdd8nHk
|
||||
GRHG0/DGum0l4sKTta3OPGHNC1z1dAcQ1RCr9bTD3PxjLBczdGqhzw71trkQRBRd
|
||||
tPiUchltPMIyjUHqVJ0xmg0hPqFic0fICsr0YwKoz3h9+QEcZHvsjSZjgydKvfLY
|
||||
cm+4DDMCCqcHuJrbXJKUWmJcXR0y/+HQONGrGJ5xWdO+6eJioPn2jVMnXCm4EKc7
|
||||
fcLFrz/LKmJ8seXhxjM3EdFtylBGCrx3xdK0f+JDNQaC/rhUb5V2XuX6VwoH/AtY
|
||||
+XsKVYRfNIupLOUcf/srsm3IXT4SXWVomOc9hjGQiJ3rraIbADsc+6bCAr4XNZS7
|
||||
moViAAcIPXFv3m3WfUlnG/om78UjQqyVACRZqqAGmuPq+TSkRUCpt9h+A39LQWko
|
||||
jHqyob3cyLgy6z9Q557O9uK3lQozbw2gH9zC0RqnePl+rsWIUU/ga16fH6pWc1uJ
|
||||
iEBt8UZGypQ/E56/343epmYAe0a87sHx8iDV+dNtDVKfPRENiLOOc19MmS+phmUy
|
||||
rbHqI91c0pmysYcJZCD3a502X1gpjFbPZcRtiTmGnUKdOIu60YPNE4+h7u2CfYyF
|
||||
Pu3AlUaGNMBlvy6PEpU=
|
||||
=NUTS
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
|
|
|
@ -3,21 +3,20 @@ package pgpkeys
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
// EncryptShares takes an ordered set of Shamir key share fragments and
|
||||
// PGP public keys and encrypts each Shamir key fragment with the corresponding
|
||||
// public key
|
||||
// EncryptShares takes an ordered set of byte slices to encrypt and the
|
||||
// corresponding base64-encoded public keys to encrypt them with, encrypts each
|
||||
// byte slice with the corresponding public key.
|
||||
//
|
||||
// Note: There is no corresponding test function; this functionality is
|
||||
// thoroughly tested in the init and rekey command unit tests
|
||||
func EncryptShares(secretShares [][]byte, pgpKeys []string) ([]string, [][]byte, error) {
|
||||
if len(secretShares) != len(pgpKeys) {
|
||||
func EncryptShares(input [][]byte, pgpKeys []string) ([]string, [][]byte, error) {
|
||||
if len(input) != len(pgpKeys) {
|
||||
return nil, nil, fmt.Errorf("Mismatch between number of generated shares and number of PGP keys")
|
||||
}
|
||||
encryptedShares := make([][]byte, 0, len(pgpKeys))
|
||||
|
@ -31,7 +30,7 @@ func EncryptShares(secretShares [][]byte, pgpKeys []string) ([]string, [][]byte,
|
|||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error setting up encryption for PGP message: %s", err)
|
||||
}
|
||||
_, err = pt.Write([]byte(hex.EncodeToString(secretShares[i])))
|
||||
_, err = pt.Write(input[i])
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error encrypting PGP message: %s", err)
|
||||
}
|
||||
|
@ -47,6 +46,9 @@ func EncryptShares(secretShares [][]byte, pgpKeys []string) ([]string, [][]byte,
|
|||
return fingerprints, encryptedShares, nil
|
||||
}
|
||||
|
||||
// GetFingerprints takes in a list of openpgp Entities and returns the
|
||||
// fingerprints. If entities is nil, it will instead parse both entities and
|
||||
// fingerprints from the pgpKeys string slice.
|
||||
func GetFingerprints(pgpKeys []string, entities []*openpgp.Entity) ([]string, error) {
|
||||
if entities == nil {
|
||||
var err error
|
||||
|
@ -63,6 +65,8 @@ func GetFingerprints(pgpKeys []string, entities []*openpgp.Entity) ([]string, er
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
// GetEntities takes in a string array of base64-encoded PGP keys and returns
|
||||
// the openpgp Entities
|
||||
func GetEntities(pgpKeys []string) ([]*openpgp.Entity, error) {
|
||||
ret := make([]*openpgp.Entity, 0, len(pgpKeys))
|
||||
for _, keystring := range pgpKeys {
|
||||
|
@ -78,3 +82,36 @@ func GetEntities(pgpKeys []string) ([]*openpgp.Entity, error) {
|
|||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// DecryptBytes takes in base64-encoded encrypted bytes and the base64-encoded
|
||||
// private key and decrypts it. A bytes.Buffer is returned to allow the caller
|
||||
// to do useful thing with it (get it as a []byte, get it as a string, use it
|
||||
// as an io.Reader, etc), and also because this function doesn't know if what
|
||||
// comes out is binary data or a string, so let the caller decide.
|
||||
func DecryptBytes(encodedCrypt, privKey string) (*bytes.Buffer, error) {
|
||||
privKeyBytes, err := base64.StdEncoding.DecodeString(privKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decoding base64 private key: %s", err)
|
||||
}
|
||||
|
||||
cryptBytes, err := base64.StdEncoding.DecodeString(encodedCrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decoding base64 crypted bytes: %s", err)
|
||||
}
|
||||
|
||||
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(privKeyBytes)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing private key: %s", err)
|
||||
}
|
||||
|
||||
entityList := &openpgp.EntityList{entity}
|
||||
md, err := openpgp.ReadMessage(bytes.NewBuffer(cryptBytes), entityList, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decrypting the messages: %s", err)
|
||||
}
|
||||
|
||||
ptBuf := bytes.NewBuffer(nil)
|
||||
ptBuf.ReadFrom(md.UnverifiedBody)
|
||||
|
||||
return ptBuf, nil
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package pgpkeys
|
||||
|
||||
const (
|
||||
TestPrivKey1 = `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
|
||||
7u2CfYyFPu3AlUaGNMBlvy6PEpU=`
|
||||
|
||||
TestPrivKey2 = `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
|
||||
dXlg3mimA+iK7tABQfG0RJ9YzWs=`
|
||||
|
||||
TestPrivKey3 = `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
|
||||
KFIzGEPKVoBF9U7VBXpyxpsz+A==`
|
||||
|
||||
TestPubKey1 = `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
|
||||
OIu60YPNE4+h7u2CfYyFPu3AlUaGNMBlvy6PEpU=`
|
||||
|
||||
TestPubKey2 = `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
|
||||
MFOMVRn1dc3qdXlg3mimA+iK7tABQfG0RJ9YzWs=`
|
||||
|
||||
TestPubKey3 = `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
|
||||
puKcgucA0LotbihSMxhDylaARfVO1QV6csabM/g=`
|
||||
|
||||
TestAAPubKey1 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
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
|
||||
Pu3AlUaGNMBlvy6PEpU=
|
||||
=NUTS
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
)
|
|
@ -0,0 +1,71 @@
|
|||
package xor
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func GenerateRandBytes(length int) ([]byte, error) {
|
||||
if length < 0 {
|
||||
return nil, fmt.Errorf("length must be >= 0")
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
if length == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
n, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != length {
|
||||
return nil, fmt.Errorf("unable to read %d bytes; only read %d", length, n)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func XORBuffers(a, b []byte) ([]byte, error) {
|
||||
if len(a) != len(b) {
|
||||
return nil, fmt.Errorf("length of buffers is not equivalent: %d != %d", len(a), len(b))
|
||||
}
|
||||
|
||||
buf := make([]byte, len(a))
|
||||
|
||||
for i, _ := range a {
|
||||
buf[i] = a[i] ^ b[i]
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func XORBase64(a, b string) ([]byte, error) {
|
||||
aBytes, err := base64.StdEncoding.DecodeString(a)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding first base64 value: %v", err)
|
||||
}
|
||||
if aBytes == nil || len(aBytes) == 0 {
|
||||
return nil, fmt.Errorf("decoded first base64 value is nil or empty")
|
||||
}
|
||||
|
||||
bBytes, err := base64.StdEncoding.DecodeString(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding second base64 value: %v", err)
|
||||
}
|
||||
if bBytes == nil || len(bBytes) == 0 {
|
||||
return nil, fmt.Errorf("decoded second base64 value is nil or empty")
|
||||
}
|
||||
|
||||
if len(aBytes) != len(bBytes) {
|
||||
return nil, fmt.Errorf("decoded values are not same length: %d != %d", len(aBytes), len(bBytes))
|
||||
}
|
||||
|
||||
buf := make([]byte, len(aBytes))
|
||||
for i, _ := range aBytes {
|
||||
buf[i] = aBytes[i] ^ bBytes[i]
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
|
@ -41,6 +41,8 @@ func Handler(core *vault.Core) http.Handler {
|
|||
mux.Handle("/v1/sys/health", handleSysHealth(core))
|
||||
mux.Handle("/v1/sys/rotate", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/key-status", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/root-generation/attempt", handleSysRootGenerationInit(core))
|
||||
mux.Handle("/v1/sys/root-generation/update", handleSysRootGenerationUpdate(core))
|
||||
mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core))
|
||||
mux.Handle("/v1/sys/rekey/backup", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core))
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func handleSysRootGenerationInit(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
handleSysRootGenerationInitGet(core, w, r)
|
||||
case "POST", "PUT":
|
||||
handleSysRootGenerationInitPut(core, w, r)
|
||||
case "DELETE":
|
||||
handleSysRootGenerationInitDelete(core, w, r)
|
||||
default:
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysRootGenerationInitGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
// Get the current seal configuration
|
||||
sealConfig, err := core.SealConfig()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if sealConfig == nil {
|
||||
respondError(w, http.StatusBadRequest, fmt.Errorf(
|
||||
"server is not yet initialized"))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the generation configuration
|
||||
generationConfig, err := core.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the progress
|
||||
progress, err := core.RootGenerationProgress()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Format the status
|
||||
status := &RootGenerationStatusResponse{
|
||||
Started: false,
|
||||
Progress: progress,
|
||||
Required: sealConfig.SecretThreshold,
|
||||
Complete: false,
|
||||
}
|
||||
if generationConfig != nil {
|
||||
status.Nonce = generationConfig.Nonce
|
||||
status.Started = true
|
||||
status.PGPFingerprint = generationConfig.PGPFingerprint
|
||||
}
|
||||
|
||||
respondOk(w, status)
|
||||
}
|
||||
|
||||
func handleSysRootGenerationInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request
|
||||
var req RootGenerationInitRequest
|
||||
if err := parseRequest(r, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.OTP) > 0 && len(req.PGPKey) > 0 {
|
||||
respondError(w, http.StatusBadRequest, fmt.Errorf("only one of \"otp\" and \"pgp_key\" must be specified"))
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the generation
|
||||
err := core.RootGenerationInit(req.OTP, req.PGPKey)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
respondOk(w, nil)
|
||||
}
|
||||
|
||||
func handleSysRootGenerationInitDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
err := core.RootGenerationCancel()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
respondOk(w, nil)
|
||||
}
|
||||
|
||||
func handleSysRootGenerationUpdate(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request
|
||||
var req RootGenerationUpdateRequest
|
||||
if err := parseRequest(r, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if req.Key == "" {
|
||||
respondError(
|
||||
w, http.StatusBadRequest,
|
||||
errors.New("'key' must specified in request body as JSON"))
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the key, which is hex encoded
|
||||
key, err := hex.DecodeString(req.Key)
|
||||
if err != nil {
|
||||
respondError(
|
||||
w, http.StatusBadRequest,
|
||||
errors.New("'key' must be a valid hex-string"))
|
||||
return
|
||||
}
|
||||
|
||||
// Use the key to make progress on root generation
|
||||
result, err := core.RootGenerationUpdate(key, req.Nonce)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &RootGenerationStatusResponse{
|
||||
Complete: result.Progress == result.Required,
|
||||
Nonce: req.Nonce,
|
||||
Progress: result.Progress,
|
||||
Required: result.Required,
|
||||
Started: true,
|
||||
EncodedRootToken: result.EncodedRootToken,
|
||||
PGPFingerprint: result.PGPFingerprint,
|
||||
}
|
||||
|
||||
respondOk(w, resp)
|
||||
})
|
||||
}
|
||||
|
||||
type RootGenerationInitRequest struct {
|
||||
OTP string `json:"otp"`
|
||||
PGPKey string `json:"pgp_key"`
|
||||
}
|
||||
|
||||
type RootGenerationStatusResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Started bool `json:"started"`
|
||||
Progress int `json:"progress"`
|
||||
Required int `json:"required"`
|
||||
Complete bool `json:"complete"`
|
||||
EncodedRootToken string `json:"encoded_root_token"`
|
||||
PGPFingerprint string `json:"pgp_fingerprint"`
|
||||
}
|
||||
|
||||
type RootGenerationUpdateRequest struct {
|
||||
Nonce string
|
||||
Key string
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/helper/xor"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestSysRootGenerationInit_Status(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp, err := http.Get(addr + "/v1/sys/root-generation/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": false,
|
||||
"progress": float64(0),
|
||||
"required": float64(1),
|
||||
"complete": false,
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["nonce"] = actual["nonce"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRootGenerationInit_Setup_OTP(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"otp": otp,
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/root-generation/attempt")
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": float64(0),
|
||||
"required": float64(1),
|
||||
"complete": false,
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["nonce"] = actual["nonce"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRootGenerationInit_Setup_PGP(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/root-generation/attempt")
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": float64(0),
|
||||
"required": float64(1),
|
||||
"complete": false,
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["nonce"] = actual["nonce"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRootGenerationInit_Cancel(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"otp": otp,
|
||||
})
|
||||
|
||||
resp = testHttpDelete(t, token, addr+"/v1/sys/root-generation/attempt")
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp, err = http.Get(addr + "/v1/sys/root-generation/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": false,
|
||||
"progress": float64(0),
|
||||
"required": float64(1),
|
||||
"complete": false,
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["nonce"] = actual["nonce"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRootGeneration_badKey(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/update", map[string]interface{}{
|
||||
"key": "0123",
|
||||
"otp": otp,
|
||||
})
|
||||
testResponseStatus(t, resp, 400)
|
||||
}
|
||||
|
||||
func TestSysRootGeneration_ReInitUpdate(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"otp": otp,
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpDelete(t, token, addr+"/v1/sys/root-generation/attempt")
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
|
||||
testResponseStatus(t, resp, 204)
|
||||
}
|
||||
|
||||
func TestSysRootGeneration_Update_OTP(t *testing.T) {
|
||||
core, master, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"otp": otp,
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// We need to get the nonce first before we update
|
||||
resp, err = http.Get(addr + "/v1/sys/root-generation/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
var rootGenerationStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &rootGenerationStatus)
|
||||
|
||||
resp = testHttpPut(t, token, addr+"/v1/sys/root-generation/update", map[string]interface{}{
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(master),
|
||||
})
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"complete": true,
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"progress": float64(1),
|
||||
"required": float64(1),
|
||||
"started": true,
|
||||
"pgp_fingerprint": "",
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
if actual["encoded_root_token"] == nil {
|
||||
t.Fatalf("no encoded root token found in response")
|
||||
}
|
||||
expected["encoded_root_token"] = actual["encoded_root_token"]
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
|
||||
decodedToken, err := xor.XORBase64(otp, actual["encoded_root_token"].(string))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newRootToken, err := uuid.FormatUUID(decodedToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"id": newRootToken,
|
||||
"display_name": "root",
|
||||
"meta": interface{}(nil),
|
||||
"num_uses": float64(0),
|
||||
"policies": []interface{}{"root"},
|
||||
"orphan": true,
|
||||
"ttl": float64(0),
|
||||
"path": "auth/token/root",
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"]
|
||||
|
||||
if !reflect.DeepEqual(actual["data"], expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRootGeneration_Update_PGP(t *testing.T) {
|
||||
core, master, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/root-generation/attempt", map[string]interface{}{
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// We need to get the nonce first before we update
|
||||
resp, err := http.Get(addr + "/v1/sys/root-generation/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
var rootGenerationStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &rootGenerationStatus)
|
||||
|
||||
resp = testHttpPut(t, token, addr+"/v1/sys/root-generation/update", map[string]interface{}{
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(master),
|
||||
})
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"complete": true,
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"progress": float64(1),
|
||||
"required": float64(1),
|
||||
"started": true,
|
||||
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
if actual["encoded_root_token"] == nil {
|
||||
t.Fatalf("no encoded root token found in response")
|
||||
}
|
||||
expected["encoded_root_token"] = actual["encoded_root_token"]
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
|
||||
decodedTokenBuf, err := pgpkeys.DecryptBytes(actual["encoded_root_token"].(string), pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decodedTokenBuf == nil {
|
||||
t.Fatal("decoded root token buffer is nil")
|
||||
}
|
||||
|
||||
newRootToken := decodedTokenBuf.String()
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"id": newRootToken,
|
||||
"display_name": "root",
|
||||
"meta": interface{}(nil),
|
||||
"num_uses": float64(0),
|
||||
"policies": []interface{}{"root"},
|
||||
"orphan": true,
|
||||
"ttl": float64(0),
|
||||
"path": "auth/token/root",
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"]
|
||||
|
||||
if !reflect.DeepEqual(actual["data"], expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"])
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package vault
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -213,6 +214,12 @@ type Core struct {
|
|||
// the threshold number of parts is available.
|
||||
unlockParts [][]byte
|
||||
|
||||
// rootGenerationProgress holds the shares until we reach enough
|
||||
// to verify the master key
|
||||
rootGenerationConfig *RootGenerationConfig
|
||||
rootGenerationProgress [][]byte
|
||||
rootGenerationLock sync.Mutex
|
||||
|
||||
// rekeyProgress holds the shares we have until we reach enough
|
||||
// to verify the master key.
|
||||
rekeyConfig *SealConfig
|
||||
|
@ -869,7 +876,11 @@ func (c *Core) Initialize(config *SealConfig) (*InitResult, error) {
|
|||
}
|
||||
|
||||
if len(config.PGPKeys) > 0 {
|
||||
_, encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, config.PGPKeys)
|
||||
hexEncodedShares := make([][]byte, len(results.SecretShares))
|
||||
for i, _ := range results.SecretShares {
|
||||
hexEncodedShares[i] = []byte(hex.EncodeToString(results.SecretShares[i]))
|
||||
}
|
||||
_, encryptedShares, err := pgpkeys.EncryptShares(hexEncodedShares, config.PGPKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -904,7 +915,7 @@ func (c *Core) Initialize(config *SealConfig) (*InitResult, error) {
|
|||
}
|
||||
|
||||
// Generate a new root token
|
||||
rootToken, err := c.tokenStore.rootToken()
|
||||
rootToken, err := c.tokenStore.rootToken("")
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: root token generation failed: %v", err)
|
||||
return nil, err
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
// mockExpiration returns a mock expiration manager
|
||||
func mockExpiration(t *testing.T) *ExpirationManager {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
return ts.expiration
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ func TestExpiration_Register(t *testing.T) {
|
|||
|
||||
func TestExpiration_RegisterAuth(t *testing.T) {
|
||||
exp := mockExpiration(t)
|
||||
root, err := exp.tokenStore.rootToken()
|
||||
root, err := exp.tokenStore.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ func TestExpiration_RegisterAuth(t *testing.T) {
|
|||
|
||||
func TestExpiration_RegisterAuth_NoLease(t *testing.T) {
|
||||
exp := mockExpiration(t)
|
||||
root, err := exp.tokenStore.rootToken()
|
||||
root, err := exp.tokenStore.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -400,7 +400,7 @@ func TestExpiration_RevokeByToken(t *testing.T) {
|
|||
|
||||
func TestExpiration_RenewToken(t *testing.T) {
|
||||
exp := mockExpiration(t)
|
||||
root, err := exp.tokenStore.rootToken()
|
||||
root, err := exp.tokenStore.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -431,7 +431,7 @@ func TestExpiration_RenewToken(t *testing.T) {
|
|||
|
||||
func TestExpiration_RenewToken_NotRenewable(t *testing.T) {
|
||||
exp := mockExpiration(t)
|
||||
root, err := exp.tokenStore.rootToken()
|
||||
root, err := exp.tokenStore.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -688,7 +688,7 @@ func TestExpiration_revokeEntry(t *testing.T) {
|
|||
|
||||
func TestExpiration_revokeEntry_token(t *testing.T) {
|
||||
exp := mockExpiration(t)
|
||||
root, err := exp.tokenStore.rootToken()
|
||||
root, err := exp.tokenStore.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/helper/xor"
|
||||
"github.com/hashicorp/vault/shamir"
|
||||
)
|
||||
|
||||
// RootGenerationConfig holds the configuration for a root generation
|
||||
// command.
|
||||
type RootGenerationConfig struct {
|
||||
Nonce string
|
||||
PGPKey string
|
||||
PGPFingerprint string
|
||||
OTP string
|
||||
}
|
||||
|
||||
// RootGenerationResult holds the result of a root generation update
|
||||
// command
|
||||
type RootGenerationResult struct {
|
||||
Progress int
|
||||
Required int
|
||||
EncodedRootToken string
|
||||
PGPFingerprint string
|
||||
}
|
||||
|
||||
// RootGeneration is used to return the root generation progress (num shares)
|
||||
func (c *Core) RootGenerationProgress() (int, error) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if c.sealed {
|
||||
return 0, ErrSealed
|
||||
}
|
||||
if c.standby {
|
||||
return 0, ErrStandby
|
||||
}
|
||||
|
||||
c.rootGenerationLock.Lock()
|
||||
defer c.rootGenerationLock.Unlock()
|
||||
|
||||
return len(c.rootGenerationProgress), nil
|
||||
}
|
||||
|
||||
// RootGenerationConfig is used to read the root generation configuration
|
||||
// It stubbornly refuses to return the OTP if one is there.
|
||||
func (c *Core) RootGenerationConfiguration() (*RootGenerationConfig, error) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if c.sealed {
|
||||
return nil, ErrSealed
|
||||
}
|
||||
if c.standby {
|
||||
return nil, ErrStandby
|
||||
}
|
||||
|
||||
c.rootGenerationLock.Lock()
|
||||
defer c.rootGenerationLock.Unlock()
|
||||
|
||||
// Copy the config if any
|
||||
var conf *RootGenerationConfig
|
||||
if c.rootGenerationConfig != nil {
|
||||
conf = new(RootGenerationConfig)
|
||||
*conf = *c.rootGenerationConfig
|
||||
conf.OTP = ""
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// RootGenerationInit is used to initialize the root generation settings
|
||||
func (c *Core) RootGenerationInit(otp, pgpKey string) error {
|
||||
var fingerprint string
|
||||
switch {
|
||||
case len(otp) > 0:
|
||||
otpBytes, err := base64.StdEncoding.DecodeString(otp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding base64 OTP value: %s", err)
|
||||
}
|
||||
if otpBytes == nil || len(otpBytes) != 16 {
|
||||
return fmt.Errorf("decoded OTP value is invalid or wrong length")
|
||||
}
|
||||
|
||||
case len(pgpKey) > 0:
|
||||
fingerprints, err := pgpkeys.GetFingerprints([]string{pgpKey}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing PGP key: %s", err)
|
||||
}
|
||||
if len(fingerprints) != 1 || fingerprints[0] == "" {
|
||||
return fmt.Errorf("could not acquire PGP key entity")
|
||||
}
|
||||
fingerprint = fingerprints[0]
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unreachable condition")
|
||||
}
|
||||
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if c.sealed {
|
||||
return ErrSealed
|
||||
}
|
||||
if c.standby {
|
||||
return ErrStandby
|
||||
}
|
||||
|
||||
c.rootGenerationLock.Lock()
|
||||
defer c.rootGenerationLock.Unlock()
|
||||
|
||||
// Prevent multiple concurrent root generations
|
||||
if c.rootGenerationConfig != nil {
|
||||
return fmt.Errorf("root generation already in progress")
|
||||
}
|
||||
|
||||
// Copy the configuration
|
||||
generationNonce, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.rootGenerationConfig = &RootGenerationConfig{
|
||||
Nonce: generationNonce,
|
||||
OTP: otp,
|
||||
PGPKey: pgpKey,
|
||||
PGPFingerprint: fingerprint,
|
||||
}
|
||||
|
||||
c.logger.Printf("[INFO] core: root generation initialized (nonce: %s)",
|
||||
c.rootGenerationConfig.Nonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RootGenerationUpdate is used to provide a new key part
|
||||
func (c *Core) RootGenerationUpdate(key []byte, nonce string) (*RootGenerationResult, error) {
|
||||
// Verify the key length
|
||||
min, max := c.barrier.KeyLength()
|
||||
max += shamir.ShareOverhead
|
||||
if len(key) < min {
|
||||
return nil, &ErrInvalidKey{fmt.Sprintf("key is shorter than minimum %d bytes", min)}
|
||||
}
|
||||
if len(key) > max {
|
||||
return nil, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
|
||||
}
|
||||
|
||||
// Get the seal configuration
|
||||
config, err := c.SealConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure the barrier is initialized
|
||||
if config == nil {
|
||||
return nil, ErrNotInit
|
||||
}
|
||||
|
||||
// Ensure we are already unsealed
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if c.sealed {
|
||||
return nil, ErrSealed
|
||||
}
|
||||
if c.standby {
|
||||
return nil, ErrStandby
|
||||
}
|
||||
|
||||
c.rootGenerationLock.Lock()
|
||||
defer c.rootGenerationLock.Unlock()
|
||||
|
||||
// Ensure a rootGeneration is in progress
|
||||
if c.rootGenerationConfig == nil {
|
||||
return nil, fmt.Errorf("no root generation in progress")
|
||||
}
|
||||
|
||||
if nonce != c.rootGenerationConfig.Nonce {
|
||||
return nil, fmt.Errorf("incorrect nonce supplied; nonce for this root generation operation is %s", c.rootGenerationConfig.Nonce)
|
||||
}
|
||||
|
||||
// Check if we already have this piece
|
||||
for _, existing := range c.rootGenerationProgress {
|
||||
if bytes.Equal(existing, key) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Store this key
|
||||
c.rootGenerationProgress = append(c.rootGenerationProgress, key)
|
||||
progress := len(c.rootGenerationProgress)
|
||||
|
||||
// Check if we don't have enough keys to unlock
|
||||
if len(c.rootGenerationProgress) < config.SecretThreshold {
|
||||
c.logger.Printf("[DEBUG] core: cannot generate root, have %d of %d keys",
|
||||
progress, config.SecretThreshold)
|
||||
return &RootGenerationResult{
|
||||
Progress: progress,
|
||||
Required: config.SecretThreshold,
|
||||
PGPFingerprint: c.rootGenerationConfig.PGPFingerprint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Recover the master key
|
||||
var masterKey []byte
|
||||
if config.SecretThreshold == 1 {
|
||||
masterKey = c.rootGenerationProgress[0]
|
||||
c.rootGenerationProgress = nil
|
||||
} else {
|
||||
masterKey, err = shamir.Combine(c.rootGenerationProgress)
|
||||
c.rootGenerationProgress = nil
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute master key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the master key
|
||||
if err := c.barrier.VerifyMaster(masterKey); err != nil {
|
||||
c.logger.Printf("[ERR] core: root generation aborted, master key verification failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the raw token bytes
|
||||
buf := make([]byte, 16)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
c.logger.Printf("failed to read random bytes: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uuidStr, err := uuid.FormatUUID(buf)
|
||||
if err != nil {
|
||||
c.logger.Printf("error formatting token: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tokenBytes []byte
|
||||
// Get the encoded value first so that if there is an error we don't create
|
||||
// the root token.
|
||||
switch {
|
||||
case len(c.rootGenerationConfig.OTP) > 0:
|
||||
// This function performs decoding checks so rather than decode the OTP,
|
||||
// just encode the value we're passing in.
|
||||
tokenBytes, err = xor.XORBase64(c.rootGenerationConfig.OTP, base64.StdEncoding.EncodeToString(buf))
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: xor of root token failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case len(c.rootGenerationConfig.PGPKey) > 0:
|
||||
_, tokenBytesArr, err := pgpkeys.EncryptShares([][]byte{[]byte(uuidStr)}, []string{c.rootGenerationConfig.PGPKey})
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: error encrypting new root token: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
tokenBytes = tokenBytesArr[0]
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unreachable condition")
|
||||
}
|
||||
|
||||
_, err = c.tokenStore.rootToken(uuidStr)
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: root token generation failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := &RootGenerationResult{
|
||||
Progress: progress,
|
||||
Required: config.SecretThreshold,
|
||||
EncodedRootToken: base64.StdEncoding.EncodeToString(tokenBytes),
|
||||
PGPFingerprint: c.rootGenerationConfig.PGPFingerprint,
|
||||
}
|
||||
|
||||
c.logger.Printf("[INFO] core: root generation finished (nonce: %s)",
|
||||
c.rootGenerationConfig.Nonce)
|
||||
|
||||
c.rootGenerationProgress = nil
|
||||
c.rootGenerationConfig = nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// RootGenerationCancel is used to cancel an in-progress root generation
|
||||
func (c *Core) RootGenerationCancel() error {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if c.sealed {
|
||||
return ErrSealed
|
||||
}
|
||||
if c.standby {
|
||||
return ErrStandby
|
||||
}
|
||||
|
||||
c.rootGenerationLock.Lock()
|
||||
defer c.rootGenerationLock.Unlock()
|
||||
|
||||
// Clear any progress or config
|
||||
c.rootGenerationConfig = nil
|
||||
c.rootGenerationProgress = nil
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/helper/xor"
|
||||
)
|
||||
|
||||
func TestCore_RootGeneration_Lifecycle(t *testing.T) {
|
||||
c, master, _ := TestCoreUnsealed(t)
|
||||
|
||||
// Verify update not allowed
|
||||
if _, err := c.RootGenerationUpdate(master, ""); err == nil {
|
||||
t.Fatalf("no root generation in progress")
|
||||
}
|
||||
|
||||
// Should be no progress
|
||||
num, err := c.RootGenerationProgress()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if num != 0 {
|
||||
t.Fatalf("bad: %d", num)
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if conf != nil {
|
||||
t.Fatalf("bad: %v", conf)
|
||||
}
|
||||
|
||||
// Cancel should be idempotent
|
||||
err = c.RootGenerationCancel()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Start a root generation
|
||||
err = c.RootGenerationInit(base64.StdEncoding.EncodeToString(otpBytes), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should get config
|
||||
conf, err = c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Cancel should be clear
|
||||
err = c.RootGenerationCancel()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err = c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if conf != nil {
|
||||
t.Fatalf("bad: %v", conf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_RootGeneration_Init(t *testing.T) {
|
||||
c, _, _ := TestCoreUnsealed(t)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.RootGenerationInit(base64.StdEncoding.EncodeToString(otpBytes), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Second should fail
|
||||
err = c.RootGenerationInit("", pgpkeys.TestPubKey1)
|
||||
if err == nil {
|
||||
t.Fatalf("should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_RootGeneration_InvalidMaster(t *testing.T) {
|
||||
c, master, _ := TestCoreUnsealed(t)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.RootGenerationInit(base64.StdEncoding.EncodeToString(otpBytes), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rgconf, err := c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if rgconf == nil {
|
||||
t.Fatalf("bad: no rekey config received")
|
||||
}
|
||||
|
||||
// Provide the master (invalid)
|
||||
master[0]++
|
||||
_, err = c.RootGenerationUpdate(master, rgconf.Nonce)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_RootGeneration_InvalidNonce(t *testing.T) {
|
||||
c, master, _ := TestCoreUnsealed(t)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.RootGenerationInit(base64.StdEncoding.EncodeToString(otpBytes), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Provide the nonce (invalid)
|
||||
_, err = c.RootGenerationUpdate(master, "abcd")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_RootGeneration_Update_OTP(t *testing.T) {
|
||||
c, master, _ := TestCoreUnsealed(t)
|
||||
|
||||
otpBytes, err := xor.GenerateRandBytes(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
otp := base64.StdEncoding.EncodeToString(otpBytes)
|
||||
// Start a root generation
|
||||
err = c.RootGenerationInit(otp, "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err := c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if rkconf == nil {
|
||||
t.Fatalf("bad: no root generation config received")
|
||||
}
|
||||
|
||||
// Provide the master
|
||||
result, err := c.RootGenerationUpdate(master, rkconf.Nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatalf("Bad, result is nil")
|
||||
}
|
||||
|
||||
encodedRootToken := result.EncodedRootToken
|
||||
|
||||
// Should be no progress
|
||||
num, err := c.RootGenerationProgress()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if num != 0 {
|
||||
t.Fatalf("bad: %d", num)
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if conf != nil {
|
||||
t.Fatalf("bad: %v", conf)
|
||||
}
|
||||
|
||||
tokenBytes, err := xor.XORBase64(encodedRootToken, otp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token, err := uuid.FormatUUID(tokenBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure that the token is a root token
|
||||
te, err := c.tokenStore.Lookup(token)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if te == nil {
|
||||
t.Fatalf("token was nil")
|
||||
}
|
||||
if te.ID != token || te.Parent != "" ||
|
||||
len(te.Policies) != 1 || te.Policies[0] != "root" {
|
||||
t.Fatalf("bad: %#v", *te)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_RootGeneration_Update_PGP(t *testing.T) {
|
||||
c, master, _ := TestCoreUnsealed(t)
|
||||
|
||||
// Start a root generation
|
||||
err := c.RootGenerationInit("", pgpkeys.TestPubKey1)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err := c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if rkconf == nil {
|
||||
t.Fatalf("bad: no root generation config received")
|
||||
}
|
||||
|
||||
// Provide the master
|
||||
result, err := c.RootGenerationUpdate(master, rkconf.Nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatalf("Bad, result is nil")
|
||||
}
|
||||
|
||||
encodedRootToken := result.EncodedRootToken
|
||||
|
||||
// Should be no progress
|
||||
num, err := c.RootGenerationProgress()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if num != 0 {
|
||||
t.Fatalf("bad: %d", num)
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.RootGenerationConfiguration()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if conf != nil {
|
||||
t.Fatalf("bad: %v", conf)
|
||||
}
|
||||
|
||||
ptBuf, err := pgpkeys.DecryptBytes(encodedRootToken, pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ptBuf == nil {
|
||||
t.Fatal("Got nil plaintext key")
|
||||
}
|
||||
|
||||
token := ptBuf.String()
|
||||
|
||||
// Ensure that the token is a root token
|
||||
te, err := c.tokenStore.Lookup(token)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if te == nil {
|
||||
t.Fatalf("token was nil")
|
||||
}
|
||||
if te.ID != token || te.Parent != "" ||
|
||||
len(te.Policies) != 1 || te.Policies[0] != "root" {
|
||||
t.Fatalf("bad: %#v", *te)
|
||||
}
|
||||
}
|
|
@ -4,13 +4,16 @@ import (
|
|||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/salt"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
|
@ -144,6 +147,40 @@ func TestCoreUnsealed(t *testing.T) (*Core, []byte, string) {
|
|||
return core, key, token
|
||||
}
|
||||
|
||||
// TestCoreWithTokenStore returns an in-memory core that has a token store
|
||||
// mounted, so that logical token functions can be used
|
||||
func TestCoreWithTokenStore(t *testing.T) (*Core, *TokenStore, []byte, string) {
|
||||
c, key, root := TestCoreUnsealed(t)
|
||||
|
||||
me := &MountEntry{
|
||||
Path: "token/",
|
||||
Type: "token",
|
||||
Description: "token based credentials",
|
||||
}
|
||||
|
||||
meUUID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
me.UUID = meUUID
|
||||
|
||||
view := NewBarrierView(c.barrier, credentialBarrierPrefix+me.UUID+"/")
|
||||
|
||||
tokenstore, _ := c.newCredentialBackend("token", c.mountEntrySysView(me), view, nil)
|
||||
ts := tokenstore.(*TokenStore)
|
||||
|
||||
router := NewRouter()
|
||||
router.Mount(ts, "auth/token/", &MountEntry{UUID: ""}, ts.view)
|
||||
|
||||
subview := c.systemBarrierView.SubView(expirationSubPath)
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
||||
exp := NewExpirationManager(router, subview, ts, logger)
|
||||
ts.SetExpirationManager(exp)
|
||||
|
||||
return c, ts, key, root
|
||||
}
|
||||
|
||||
// TestKeyCopy is a silly little function to just copy the key so that
|
||||
// it can be used with Unseal easily.
|
||||
func TestKeyCopy(key []byte) []byte {
|
||||
|
|
|
@ -290,8 +290,9 @@ func (ts *TokenStore) SaltID(id string) string {
|
|||
}
|
||||
|
||||
// RootToken is used to generate a new token with root privileges and no parent
|
||||
func (ts *TokenStore) rootToken() (*TokenEntry, error) {
|
||||
func (ts *TokenStore) rootToken(id string) (*TokenEntry, error) {
|
||||
te := &TokenEntry{
|
||||
ID: id,
|
||||
Policies: []string{"root"},
|
||||
Path: "auth/token/root",
|
||||
DisplayName: "root",
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
|
@ -21,42 +18,10 @@ func getBackendConfig(c *Core) *logical.BackendConfig {
|
|||
}
|
||||
}
|
||||
|
||||
func mockTokenStore(t *testing.T) (*Core, *TokenStore, string) {
|
||||
c, _, root := TestCoreUnsealed(t)
|
||||
|
||||
me := &MountEntry{
|
||||
Path: "token/",
|
||||
Type: "token",
|
||||
Description: "token based credentials",
|
||||
}
|
||||
|
||||
uuid, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
me.UUID = uuid
|
||||
|
||||
view := NewBarrierView(c.barrier, credentialBarrierPrefix+me.UUID+"/")
|
||||
|
||||
tokenstore, _ := c.newCredentialBackend("token", c.mountEntrySysView(me), view, nil)
|
||||
ts := tokenstore.(*TokenStore)
|
||||
|
||||
router := NewRouter()
|
||||
router.Mount(ts, "auth/token/", &MountEntry{UUID: ""}, ts.view)
|
||||
|
||||
subview := c.systemBarrierView.SubView(expirationSubPath)
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
||||
exp := NewExpirationManager(router, subview, ts, logger)
|
||||
ts.SetExpirationManager(exp)
|
||||
|
||||
return c, ts, root
|
||||
}
|
||||
|
||||
func TestTokenStore_RootToken(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
te, err := ts.rootToken()
|
||||
te, err := ts.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -74,7 +39,7 @@ func TestTokenStore_RootToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_CreateLookup(t *testing.T) {
|
||||
c, ts, _ := mockTokenStore(t)
|
||||
c, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
|
||||
if err := ts.create(ent); err != nil {
|
||||
|
@ -109,7 +74,7 @@ func TestTokenStore_CreateLookup(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
|
||||
c, ts, _ := mockTokenStore(t)
|
||||
c, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{
|
||||
ID: "foobarbaz",
|
||||
|
@ -148,7 +113,7 @@ func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_UseToken(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
// Lookup the root token
|
||||
ent, err := ts.Lookup(root)
|
||||
|
@ -214,7 +179,7 @@ func TestTokenStore_UseToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_Revoke(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
|
||||
if err := ts.create(ent); err != nil {
|
||||
|
@ -240,7 +205,7 @@ func TestTokenStore_Revoke(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_Revoke_Leases(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
// Mount a noop backend
|
||||
noop := &NoopBackend{}
|
||||
|
@ -290,7 +255,7 @@ func TestTokenStore_Revoke_Leases(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_Revoke_Orphan(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
|
||||
if err := ts.create(ent); err != nil {
|
||||
|
@ -317,7 +282,7 @@ func TestTokenStore_Revoke_Orphan(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_RevokeTree(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
ent1 := &TokenEntry{}
|
||||
if err := ts.create(ent1); err != nil {
|
||||
|
@ -361,7 +326,7 @@ func TestTokenStore_RevokeTree(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_RevokeSelf(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
ent1 := &TokenEntry{}
|
||||
if err := ts.create(ent1); err != nil {
|
||||
|
@ -404,7 +369,7 @@ func TestTokenStore_RevokeSelf(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_DisplayName(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -434,7 +399,7 @@ func TestTokenStore_HandleRequest_CreateToken_DisplayName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NumUses(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -465,7 +430,7 @@ func TestTokenStore_HandleRequest_CreateToken_NumUses(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NumUses_Invalid(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -478,7 +443,7 @@ func TestTokenStore_HandleRequest_CreateToken_NumUses_Invalid(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NumUses_Restricted(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -498,7 +463,7 @@ func TestTokenStore_HandleRequest_CreateToken_NumUses_Restricted(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -527,7 +492,7 @@ func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_BadParent(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
_, ts, _, _ := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = "random"
|
||||
|
@ -542,7 +507,7 @@ func TestTokenStore_HandleRequest_CreateToken_BadParent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -558,7 +523,7 @@ func TestTokenStore_HandleRequest_CreateToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_RootID(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -575,7 +540,7 @@ func TestTokenStore_HandleRequest_CreateToken_RootID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", "", []string{"foo"})
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
|
@ -593,7 +558,7 @@ func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", "", []string{"foo", "bar"})
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
|
@ -610,7 +575,7 @@ func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRoot_InvalidSubset(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", "", []string{"foo", "bar"})
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
|
@ -627,7 +592,7 @@ func TestTokenStore_HandleRequest_CreateToken_NonRoot_InvalidSubset(t *testing.T
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRoot_NoParent(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", "", []string{"foo"})
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
|
@ -645,7 +610,7 @@ func TestTokenStore_HandleRequest_CreateToken_NonRoot_NoParent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_Root_NoParent(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -667,7 +632,7 @@ func TestTokenStore_HandleRequest_CreateToken_Root_NoParent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_PathBased_NoParent(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create-orphan")
|
||||
req.ClientToken = root
|
||||
|
@ -688,7 +653,7 @@ func TestTokenStore_HandleRequest_CreateToken_PathBased_NoParent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_Metadata(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -714,7 +679,7 @@ func TestTokenStore_HandleRequest_CreateToken_Metadata(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_Lease(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -737,7 +702,7 @@ func TestTokenStore_HandleRequest_CreateToken_Lease(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_TTL(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
@ -760,7 +725,7 @@ func TestTokenStore_HandleRequest_CreateToken_TTL(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_Revoke(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "child", "", []string{"root", "foo"})
|
||||
testMakeToken(t, ts, "child", "sub-child", "", []string{"foo"})
|
||||
|
||||
|
@ -792,7 +757,7 @@ func TestTokenStore_HandleRequest_Revoke(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "child", "", []string{"root", "foo"})
|
||||
testMakeToken(t, ts, "child", "sub-child", "", []string{"foo"})
|
||||
|
||||
|
@ -825,7 +790,7 @@ func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
testMakeToken(t, ts, root, "child", "", []string{"foo"})
|
||||
|
||||
out, err := ts.Lookup("child")
|
||||
|
@ -854,7 +819,7 @@ func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
|
||||
c, ts, root := mockTokenStore(t)
|
||||
c, ts, _, root := TestCoreWithTokenStore(t)
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "lookup/"+root)
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
|
@ -944,7 +909,7 @@ func TestTokenStore_HandleRequest_RevokePrefix(t *testing.T) {
|
|||
ts := exp.tokenStore
|
||||
|
||||
// Create new token
|
||||
root, err := ts.rootToken()
|
||||
root, err := ts.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -980,7 +945,7 @@ func TestTokenStore_HandleRequest_RevokePrefix(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
_, ts, _, root := TestCoreWithTokenStore(t)
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "lookup-self")
|
||||
req.ClientToken = root
|
||||
resp, err := ts.HandleRequest(req)
|
||||
|
@ -1017,7 +982,7 @@ func TestTokenStore_HandleRequest_Renew(t *testing.T) {
|
|||
ts := exp.tokenStore
|
||||
|
||||
// Create new token
|
||||
root, err := ts.rootToken()
|
||||
root, err := ts.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -1061,7 +1026,7 @@ func TestTokenStore_HandleRequest_RenewSelf(t *testing.T) {
|
|||
ts := exp.tokenStore
|
||||
|
||||
// Create new token
|
||||
root, err := ts.rootToken()
|
||||
root, err := ts.rootToken("")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/root-generation/"
|
||||
sidebar_current: "docs-http-sys-root-generation"
|
||||
description: |-
|
||||
The `/sys/root-generation/` endpoints are used to create a new root key for Vault.
|
||||
---
|
||||
|
||||
# /sys/root-generation/attempt
|
||||
|
||||
## GET
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Reads the configuration and progress of the current root generation
|
||||
attempt.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>GET</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/root-generation/attempt`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
If a root generation is started, `progress` is how many unseal keys have
|
||||
been provided for this generation attempt, where `required` must be reached
|
||||
to complete. The `nonce` for the current attempt and whether the attempt is
|
||||
complete is also displayed. If a PGP key is being used to encrypt the final
|
||||
root token, its fingerprint will be returned. Note that if an OTP is being
|
||||
used to encode the final root token, it will never be returned.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"started": true,
|
||||
"nonce": "2dbd10f1-8528-6246-09e7-82b25b8aba63",
|
||||
"progress": 1,
|
||||
"required": 3,
|
||||
"pgp_fingerprint": "",
|
||||
"complete": false
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## PUT
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Initializes a new root generation attempt. Only a single root generation
|
||||
attempt can take place at a time. One (and only one) of `otp` or `pgp_key`
|
||||
are required.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>PUT</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/root-generation/attempt`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">otp</span>
|
||||
<span class="param-flags">optional</span>
|
||||
A base64-encoded 16-byte value. The raw bytes of the token will be
|
||||
XOR'd with this value before being returned to the final unseal key
|
||||
provider.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">pgp_key</span>
|
||||
<span class="param-flags">optional</span>
|
||||
A base64-encoded PGP public key. The raw bytes of the token will be
|
||||
encrypted with this value before being returned to the final unseal key
|
||||
provider.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
The current progress.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"started": true,
|
||||
"nonce": "2dbd10f1-8528-6246-09e7-82b25b8aba63",
|
||||
"progress": 1,
|
||||
"required": 3,
|
||||
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
|
||||
"complete": false
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## DELETE
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Cancels any in-progress root generation attempt. This clears any progress
|
||||
made. This must be called to change the OTP or PGP key being used.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>DELETE</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/root-generation/attempt`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>`204` response code.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
# /sys/root-generation/update
|
||||
|
||||
## PUT
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Enter a single master key share to progress the root generation attempt.
|
||||
If the threshold number of master key shares is reached, Vault will
|
||||
complete the root generation and issue the new token. Otherwise, this API
|
||||
must be called multiple times until that threshold is met. The attempt
|
||||
nonce must be provided with each call.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>PUT</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/root-generation/update`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">key</span>
|
||||
<span class="param-flags">required</span>
|
||||
A single master share key.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">nonce</span>
|
||||
<span class="param-flags">required</span>
|
||||
The nonce of the attempt.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
A JSON-encoded object indicating the attempt nonce, and completion status,
|
||||
and the encoded root token, if the attempt is complete.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"started": true,
|
||||
"nonce": "2dbd10f1-8528-6246-09e7-82b25b8aba63",
|
||||
"progress": 3,
|
||||
"required": 3,
|
||||
"pgp_fingerprint": "",
|
||||
"complete": true,
|
||||
"encoded_root_token": "FPzkNBvwNDeFh4SmGA8c+w=="
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
|
@ -17,11 +17,15 @@
|
|||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-sys-init") %>>
|
||||
<a href="#">Initialization</a>
|
||||
<a href="#">Initialization/Recovery</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-http-sys-init") %>>
|
||||
<a href="/docs/http/sys-init.html">/sys/init</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-sys-root-generation") %>>
|
||||
<a href="/docs/http/sys-root-generation.html">/sys/root-generation</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
|
Loading…
Reference in New Issue