d0e2badbae
The result will still pass gofmtcheck and won't trigger additional changes if someone isn't using goimports, but it will avoid the piecemeal imports changes we've been seeing.
370 lines
9.9 KiB
Go
370 lines
9.9 KiB
Go
package vault
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/helper/consts"
|
|
"github.com/hashicorp/vault/helper/pgpkeys"
|
|
"github.com/hashicorp/vault/helper/xor"
|
|
"github.com/hashicorp/vault/shamir"
|
|
)
|
|
|
|
const coreDROperationTokenPath = "core/dr-operation-token"
|
|
|
|
var (
|
|
// GenerateStandardRootTokenStrategy is the strategy used to generate a
|
|
// typical root token
|
|
GenerateStandardRootTokenStrategy GenerateRootStrategy = generateStandardRootToken{}
|
|
|
|
// GenerateDROperationTokenStrategy is the strategy used to generate a
|
|
// DR operational token
|
|
GenerateDROperationTokenStrategy GenerateRootStrategy = generateStandardRootToken{}
|
|
)
|
|
|
|
// GenerateRootStrategy allows us to swap out the strategy we want to use to
|
|
// create a token upon completion of the generate root process.
|
|
type GenerateRootStrategy interface {
|
|
generate(context.Context, *Core) (string, func(), error)
|
|
}
|
|
|
|
// generateStandardRootToken implements the GenerateRootStrategy and is in
|
|
// charge of creating standard root tokens.
|
|
type generateStandardRootToken struct{}
|
|
|
|
func (g generateStandardRootToken) generate(ctx context.Context, c *Core) (string, func(), error) {
|
|
te, err := c.tokenStore.rootToken(ctx)
|
|
if err != nil {
|
|
c.logger.Error("root token generation failed", "error", err)
|
|
return "", nil, err
|
|
}
|
|
if te == nil {
|
|
c.logger.Error("got nil token entry back from root generation")
|
|
return "", nil, fmt.Errorf("got nil token entry back from root generation")
|
|
}
|
|
|
|
cleanupFunc := func() {
|
|
c.tokenStore.revokeOrphan(ctx, te.ID)
|
|
}
|
|
|
|
return te.ID, cleanupFunc, nil
|
|
}
|
|
|
|
// GenerateRootConfig holds the configuration for a root generation
|
|
// command.
|
|
type GenerateRootConfig struct {
|
|
Nonce string
|
|
PGPKey string
|
|
PGPFingerprint string
|
|
OTP string
|
|
Strategy GenerateRootStrategy
|
|
}
|
|
|
|
// GenerateRootResult holds the result of a root generation update
|
|
// command
|
|
type GenerateRootResult struct {
|
|
Progress int
|
|
Required int
|
|
EncodedToken string
|
|
PGPFingerprint string
|
|
}
|
|
|
|
// GenerateRootProgress is used to return the root generation progress (num shares)
|
|
func (c *Core) GenerateRootProgress() (int, error) {
|
|
c.stateLock.RLock()
|
|
defer c.stateLock.RUnlock()
|
|
if c.Sealed() {
|
|
return 0, consts.ErrSealed
|
|
}
|
|
if c.standby {
|
|
return 0, consts.ErrStandby
|
|
}
|
|
|
|
c.generateRootLock.Lock()
|
|
defer c.generateRootLock.Unlock()
|
|
|
|
return len(c.generateRootProgress), nil
|
|
}
|
|
|
|
// GenerateRootConfiguration is used to read the root generation configuration
|
|
// It stubbornly refuses to return the OTP if one is there.
|
|
func (c *Core) GenerateRootConfiguration() (*GenerateRootConfig, error) {
|
|
c.stateLock.RLock()
|
|
defer c.stateLock.RUnlock()
|
|
if c.Sealed() {
|
|
return nil, consts.ErrSealed
|
|
}
|
|
if c.standby {
|
|
return nil, consts.ErrStandby
|
|
}
|
|
|
|
c.generateRootLock.Lock()
|
|
defer c.generateRootLock.Unlock()
|
|
|
|
// Copy the config if any
|
|
var conf *GenerateRootConfig
|
|
if c.generateRootConfig != nil {
|
|
conf = new(GenerateRootConfig)
|
|
*conf = *c.generateRootConfig
|
|
conf.OTP = ""
|
|
conf.Strategy = nil
|
|
}
|
|
return conf, nil
|
|
}
|
|
|
|
// GenerateRootInit is used to initialize the root generation settings
|
|
func (c *Core) GenerateRootInit(otp, pgpKey string, strategy GenerateRootStrategy) error {
|
|
var fingerprint string
|
|
switch {
|
|
case len(otp) > 0:
|
|
if len(otp) != TokenLength+2 {
|
|
return fmt.Errorf("OTP string is wrong length")
|
|
}
|
|
|
|
case len(pgpKey) > 0:
|
|
fingerprints, err := pgpkeys.GetFingerprints([]string{pgpKey}, nil)
|
|
if err != nil {
|
|
return errwrap.Wrapf("error parsing PGP key: {{err}}", err)
|
|
}
|
|
if len(fingerprints) != 1 || fingerprints[0] == "" {
|
|
return fmt.Errorf("could not acquire PGP key entity")
|
|
}
|
|
fingerprint = fingerprints[0]
|
|
|
|
default:
|
|
return fmt.Errorf("otp or pgp_key parameter must be provided")
|
|
}
|
|
|
|
c.stateLock.RLock()
|
|
defer c.stateLock.RUnlock()
|
|
if c.Sealed() {
|
|
return consts.ErrSealed
|
|
}
|
|
if c.standby {
|
|
return consts.ErrStandby
|
|
}
|
|
|
|
c.generateRootLock.Lock()
|
|
defer c.generateRootLock.Unlock()
|
|
|
|
// Prevent multiple concurrent root generations
|
|
if c.generateRootConfig != nil {
|
|
return fmt.Errorf("root generation already in progress")
|
|
}
|
|
|
|
// Copy the configuration
|
|
generationNonce, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.generateRootConfig = &GenerateRootConfig{
|
|
Nonce: generationNonce,
|
|
OTP: otp,
|
|
PGPKey: pgpKey,
|
|
PGPFingerprint: fingerprint,
|
|
Strategy: strategy,
|
|
}
|
|
|
|
if c.logger.IsInfo() {
|
|
switch strategy.(type) {
|
|
case generateStandardRootToken:
|
|
c.logger.Info("root generation initialized", "nonce", c.generateRootConfig.Nonce)
|
|
default:
|
|
c.logger.Info("dr operation token generation initialized", "nonce", c.generateRootConfig.Nonce)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateRootUpdate is used to provide a new key part
|
|
func (c *Core) GenerateRootUpdate(ctx context.Context, key []byte, nonce string, strategy GenerateRootStrategy) (*GenerateRootResult, 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
|
|
var config *SealConfig
|
|
var err error
|
|
if c.seal.RecoveryKeySupported() {
|
|
config, err = c.seal.RecoveryConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
config, err = c.seal.BarrierConfig(ctx)
|
|
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, consts.ErrSealed
|
|
}
|
|
if c.standby {
|
|
return nil, consts.ErrStandby
|
|
}
|
|
|
|
c.generateRootLock.Lock()
|
|
defer c.generateRootLock.Unlock()
|
|
|
|
// Ensure a generateRoot is in progress
|
|
if c.generateRootConfig == nil {
|
|
return nil, fmt.Errorf("no root generation in progress")
|
|
}
|
|
|
|
if nonce != c.generateRootConfig.Nonce {
|
|
return nil, fmt.Errorf("incorrect nonce supplied; nonce for this root generation operation is %q", c.generateRootConfig.Nonce)
|
|
}
|
|
|
|
if strategy != c.generateRootConfig.Strategy {
|
|
return nil, fmt.Errorf("incorrect strategy supplied; a generate root operation of another type is already in progress")
|
|
}
|
|
|
|
// Check if we already have this piece
|
|
for _, existing := range c.generateRootProgress {
|
|
if bytes.Equal(existing, key) {
|
|
return nil, fmt.Errorf("given key has already been provided during this generation operation")
|
|
}
|
|
}
|
|
|
|
// Store this key
|
|
c.generateRootProgress = append(c.generateRootProgress, key)
|
|
progress := len(c.generateRootProgress)
|
|
|
|
// Check if we don't have enough keys to unlock
|
|
if len(c.generateRootProgress) < config.SecretThreshold {
|
|
if c.logger.IsDebug() {
|
|
c.logger.Debug("cannot generate root, not enough keys", "keys", progress, "threshold", config.SecretThreshold)
|
|
}
|
|
return &GenerateRootResult{
|
|
Progress: progress,
|
|
Required: config.SecretThreshold,
|
|
PGPFingerprint: c.generateRootConfig.PGPFingerprint,
|
|
}, nil
|
|
}
|
|
|
|
// Recover the master key
|
|
var masterKey []byte
|
|
if config.SecretThreshold == 1 {
|
|
masterKey = c.generateRootProgress[0]
|
|
c.generateRootProgress = nil
|
|
} else {
|
|
masterKey, err = shamir.Combine(c.generateRootProgress)
|
|
c.generateRootProgress = nil
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("failed to compute master key: {{err}}", err)
|
|
}
|
|
}
|
|
|
|
// Verify the master key
|
|
if c.seal.RecoveryKeySupported() {
|
|
if err := c.seal.VerifyRecoveryKey(ctx, masterKey); err != nil {
|
|
c.logger.Error("root generation aborted, recovery key verification failed", "error", err)
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if err := c.barrier.VerifyMaster(masterKey); err != nil {
|
|
c.logger.Error("root generation aborted, master key verification failed", "error", err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Run the generate strategy
|
|
token, cleanupFunc, err := strategy.generate(ctx, c)
|
|
if err != nil {
|
|
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.generateRootConfig.OTP) > 0:
|
|
// This function performs decoding checks so rather than decode the OTP,
|
|
// just encode the value we're passing in.
|
|
tokenBytes, err = xor.XORBytes([]byte(c.generateRootConfig.OTP), []byte(token))
|
|
if err != nil {
|
|
cleanupFunc()
|
|
c.logger.Error("xor of root token failed", "error", err)
|
|
return nil, err
|
|
}
|
|
token = base64.RawStdEncoding.EncodeToString(tokenBytes)
|
|
|
|
case len(c.generateRootConfig.PGPKey) > 0:
|
|
_, tokenBytesArr, err := pgpkeys.EncryptShares([][]byte{[]byte(token)}, []string{c.generateRootConfig.PGPKey})
|
|
if err != nil {
|
|
cleanupFunc()
|
|
c.logger.Error("error encrypting new root token", "error", err)
|
|
return nil, err
|
|
}
|
|
token = base64.StdEncoding.EncodeToString(tokenBytesArr[0])
|
|
|
|
default:
|
|
cleanupFunc()
|
|
return nil, fmt.Errorf("unreachable condition")
|
|
}
|
|
|
|
results := &GenerateRootResult{
|
|
Progress: progress,
|
|
Required: config.SecretThreshold,
|
|
EncodedToken: token,
|
|
PGPFingerprint: c.generateRootConfig.PGPFingerprint,
|
|
}
|
|
|
|
switch strategy.(type) {
|
|
case generateStandardRootToken:
|
|
if c.logger.IsInfo() {
|
|
c.logger.Info("root generation finished", "nonce", c.generateRootConfig.Nonce)
|
|
}
|
|
default:
|
|
if c.logger.IsInfo() {
|
|
c.logger.Info("dr operation token generation finished", "nonce", c.generateRootConfig.Nonce)
|
|
}
|
|
}
|
|
|
|
c.generateRootProgress = nil
|
|
c.generateRootConfig = nil
|
|
return results, nil
|
|
}
|
|
|
|
// GenerateRootCancel is used to cancel an in-progress root generation
|
|
func (c *Core) GenerateRootCancel() error {
|
|
c.stateLock.RLock()
|
|
defer c.stateLock.RUnlock()
|
|
if c.Sealed() {
|
|
return consts.ErrSealed
|
|
}
|
|
if c.standby {
|
|
return consts.ErrStandby
|
|
}
|
|
|
|
c.generateRootLock.Lock()
|
|
defer c.generateRootLock.Unlock()
|
|
|
|
// Clear any progress or config
|
|
c.generateRootConfig = nil
|
|
c.generateRootProgress = nil
|
|
return nil
|
|
}
|