mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
2a68f0f001
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
269 lines
8.1 KiB
Go
269 lines
8.1 KiB
Go
package manager
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"fmt"
|
|
|
|
"github.com/docker/swarmkit/ca"
|
|
"github.com/docker/swarmkit/manager/encryption"
|
|
"github.com/docker/swarmkit/manager/state/raft"
|
|
)
|
|
|
|
const (
|
|
// the raft DEK (data encryption key) is stored in the TLS key as a header
|
|
// these are the header values
|
|
pemHeaderRaftDEK = "raft-dek"
|
|
pemHeaderRaftPendingDEK = "raft-dek-pending"
|
|
pemHeaderRaftDEKNeedsRotation = "raft-dek-needs-rotation"
|
|
)
|
|
|
|
// RaftDEKData contains all the data stored in TLS pem headers
|
|
type RaftDEKData struct {
|
|
raft.EncryptionKeys
|
|
NeedsRotation bool
|
|
}
|
|
|
|
// UnmarshalHeaders loads the state of the DEK manager given the current TLS headers
|
|
func (r RaftDEKData) UnmarshalHeaders(headers map[string]string, kekData ca.KEKData) (ca.PEMKeyHeaders, error) {
|
|
var (
|
|
currentDEK, pendingDEK []byte
|
|
err error
|
|
)
|
|
|
|
if currentDEKStr, ok := headers[pemHeaderRaftDEK]; ok {
|
|
currentDEK, err = decodePEMHeaderValue(currentDEKStr, kekData.KEK)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if pendingDEKStr, ok := headers[pemHeaderRaftPendingDEK]; ok {
|
|
pendingDEK, err = decodePEMHeaderValue(pendingDEKStr, kekData.KEK)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if pendingDEK != nil && currentDEK == nil {
|
|
return nil, fmt.Errorf("there is a pending DEK, but no current DEK")
|
|
}
|
|
|
|
_, ok := headers[pemHeaderRaftDEKNeedsRotation]
|
|
return RaftDEKData{
|
|
NeedsRotation: ok,
|
|
EncryptionKeys: raft.EncryptionKeys{
|
|
CurrentDEK: currentDEK,
|
|
PendingDEK: pendingDEK,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// MarshalHeaders returns new headers given the current KEK
|
|
func (r RaftDEKData) MarshalHeaders(kekData ca.KEKData) (map[string]string, error) {
|
|
headers := make(map[string]string)
|
|
for headerKey, contents := range map[string][]byte{
|
|
pemHeaderRaftDEK: r.CurrentDEK,
|
|
pemHeaderRaftPendingDEK: r.PendingDEK,
|
|
} {
|
|
if contents != nil {
|
|
dekStr, err := encodePEMHeaderValue(contents, kekData.KEK)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headers[headerKey] = dekStr
|
|
}
|
|
}
|
|
|
|
if r.NeedsRotation {
|
|
headers[pemHeaderRaftDEKNeedsRotation] = "true"
|
|
}
|
|
|
|
// return a function that updates the dek data on write success
|
|
return headers, nil
|
|
}
|
|
|
|
// UpdateKEK optionally sets NeedRotation to true if we go from unlocked to locked
|
|
func (r RaftDEKData) UpdateKEK(oldKEK, candidateKEK ca.KEKData) ca.PEMKeyHeaders {
|
|
if _, unlockedToLocked, err := compareKEKs(oldKEK, candidateKEK); err == nil && unlockedToLocked {
|
|
return RaftDEKData{
|
|
EncryptionKeys: r.EncryptionKeys,
|
|
NeedsRotation: true,
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Returns whether the old KEK should be replaced with the new KEK, whether we went from
|
|
// unlocked to locked, and whether there was an error (the versions are the same, but the
|
|
// keks are different)
|
|
func compareKEKs(oldKEK, candidateKEK ca.KEKData) (bool, bool, error) {
|
|
keksEqual := subtle.ConstantTimeCompare(oldKEK.KEK, candidateKEK.KEK) == 1
|
|
switch {
|
|
case oldKEK.Version == candidateKEK.Version && !keksEqual:
|
|
return false, false, fmt.Errorf("candidate KEK has the same version as the current KEK, but a different KEK value")
|
|
case oldKEK.Version >= candidateKEK.Version || keksEqual:
|
|
return false, false, nil
|
|
default:
|
|
return true, oldKEK.KEK == nil, nil
|
|
}
|
|
}
|
|
|
|
// RaftDEKManager manages the raft DEK keys using TLS headers
|
|
type RaftDEKManager struct {
|
|
kw ca.KeyWriter
|
|
rotationCh chan struct{}
|
|
}
|
|
|
|
var errNoUpdateNeeded = fmt.Errorf("don't need to rotate or update")
|
|
|
|
// this error is returned if the KeyReadWriter's PEMKeyHeaders object is no longer a RaftDEKData object -
|
|
// this can happen if the node is no longer a manager, for example
|
|
var errNotUsingRaftDEKData = fmt.Errorf("RaftDEKManager can no longer store and manage TLS key headers")
|
|
|
|
// NewRaftDEKManager returns a RaftDEKManager that uses the current key writer
|
|
// and header manager
|
|
func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) {
|
|
// If there is no current DEK, generate one and write it to disk
|
|
err := kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) {
|
|
dekData, ok := h.(RaftDEKData)
|
|
// it wasn't a raft DEK manager before - just replace it
|
|
if !ok || dekData.CurrentDEK == nil {
|
|
return RaftDEKData{
|
|
EncryptionKeys: raft.EncryptionKeys{
|
|
CurrentDEK: encryption.GenerateSecretKey(),
|
|
},
|
|
}, nil
|
|
}
|
|
return nil, errNoUpdateNeeded
|
|
})
|
|
if err != nil && err != errNoUpdateNeeded {
|
|
return nil, err
|
|
}
|
|
return &RaftDEKManager{
|
|
kw: kw,
|
|
rotationCh: make(chan struct{}, 1),
|
|
}, nil
|
|
}
|
|
|
|
// NeedsRotation returns a boolean about whether we should do a rotation
|
|
func (r *RaftDEKManager) NeedsRotation() bool {
|
|
h, _ := r.kw.GetCurrentState()
|
|
data, ok := h.(RaftDEKData)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return data.NeedsRotation || data.EncryptionKeys.PendingDEK != nil
|
|
}
|
|
|
|
// GetKeys returns the current set of DEKs. If NeedsRotation is true, and there
|
|
// is no existing PendingDEK, it will try to create one. If there are any errors
|
|
// doing so, just return the original.
|
|
func (r *RaftDEKManager) GetKeys() raft.EncryptionKeys {
|
|
var newKeys, originalKeys raft.EncryptionKeys
|
|
err := r.kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) {
|
|
data, ok := h.(RaftDEKData)
|
|
if !ok {
|
|
return nil, errNotUsingRaftDEKData
|
|
}
|
|
originalKeys = data.EncryptionKeys
|
|
if !data.NeedsRotation || data.PendingDEK != nil {
|
|
return nil, errNoUpdateNeeded
|
|
}
|
|
newKeys = raft.EncryptionKeys{
|
|
CurrentDEK: data.CurrentDEK,
|
|
PendingDEK: encryption.GenerateSecretKey(),
|
|
}
|
|
return RaftDEKData{EncryptionKeys: newKeys}, nil
|
|
})
|
|
if err != nil {
|
|
return originalKeys
|
|
}
|
|
return newKeys
|
|
}
|
|
|
|
// RotationNotify the channel used to notify subscribers as to whether there
|
|
// should be a rotation done
|
|
func (r *RaftDEKManager) RotationNotify() chan struct{} {
|
|
return r.rotationCh
|
|
}
|
|
|
|
// UpdateKeys will set the updated encryption keys in the headers. This finishes
|
|
// a rotation, and is expected to set the CurrentDEK to the previous PendingDEK.
|
|
func (r *RaftDEKManager) UpdateKeys(newKeys raft.EncryptionKeys) error {
|
|
return r.kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) {
|
|
data, ok := h.(RaftDEKData)
|
|
if !ok {
|
|
return nil, errNotUsingRaftDEKData
|
|
}
|
|
// If there is no current DEK, we are basically wiping out all DEKs (no header object)
|
|
if newKeys.CurrentDEK == nil {
|
|
return nil, nil
|
|
}
|
|
return RaftDEKData{
|
|
EncryptionKeys: newKeys,
|
|
NeedsRotation: data.NeedsRotation,
|
|
}, nil
|
|
})
|
|
}
|
|
|
|
// MaybeUpdateKEK does a KEK rotation if one is required. Returns whether
|
|
// the kek was updated, whether it went from unlocked to locked, and any errors.
|
|
func (r *RaftDEKManager) MaybeUpdateKEK(candidateKEK ca.KEKData) (bool, bool, error) {
|
|
var updated, unlockedToLocked bool
|
|
err := r.kw.ViewAndRotateKEK(func(currentKEK ca.KEKData, h ca.PEMKeyHeaders) (ca.KEKData, ca.PEMKeyHeaders, error) {
|
|
var err error
|
|
updated, unlockedToLocked, err = compareKEKs(currentKEK, candidateKEK)
|
|
if err == nil && !updated { // if we don't need to rotate the KEK, don't bother updating
|
|
err = errNoUpdateNeeded
|
|
}
|
|
if err != nil {
|
|
return ca.KEKData{}, nil, err
|
|
}
|
|
|
|
data, ok := h.(RaftDEKData)
|
|
if !ok {
|
|
return ca.KEKData{}, nil, errNotUsingRaftDEKData
|
|
}
|
|
|
|
if unlockedToLocked {
|
|
data.NeedsRotation = true
|
|
}
|
|
return candidateKEK, data, nil
|
|
})
|
|
if err == errNoUpdateNeeded {
|
|
err = nil
|
|
}
|
|
|
|
if err == nil && unlockedToLocked {
|
|
r.rotationCh <- struct{}{}
|
|
}
|
|
return updated, unlockedToLocked, err
|
|
}
|
|
|
|
func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) {
|
|
var decrypter encryption.Decrypter = encryption.NoopCrypter
|
|
if kek != nil {
|
|
_, decrypter = encryption.Defaults(kek)
|
|
}
|
|
valueBytes, err := base64.StdEncoding.DecodeString(headerValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, err := encryption.Decrypt(valueBytes, decrypter)
|
|
if err != nil {
|
|
return nil, ca.ErrInvalidKEK{Wrapped: err}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func encodePEMHeaderValue(headerValue []byte, kek []byte) (string, error) {
|
|
var encrypter encryption.Encrypter = encryption.NoopCrypter
|
|
if kek != nil {
|
|
encrypter, _ = encryption.Defaults(kek)
|
|
}
|
|
encrypted, err := encryption.Encrypt(headerValue, encrypter)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(encrypted), nil
|
|
}
|