blob: e61979cb3617921255f48c12aa90ab6f10448ac4 [file] [log] [blame]
package keymanager
// keymanager does the allocation, rotation and distribution of symmetric
// keys to the agents. This is to securely bootstrap network communication
// between agents. It can be used for encrypting gossip between the agents
// which is used to exchange service discovery and overlay network control
// plane information. It can also be used to encrypt overlay data traffic.
import (
cryptorand "crypto/rand"
"encoding/binary"
"sync"
"time"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/log"
"github.com/docker/swarmkit/manager/state/store"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
const (
// DefaultKeyLen is the default length (in bytes) of the key allocated
DefaultKeyLen = 16
// DefaultKeyRotationInterval used by key manager
DefaultKeyRotationInterval = 12 * time.Hour
// SubsystemGossip handles gossip protocol between the agents
SubsystemGossip = "networking:gossip"
// SubsystemIPSec is overlay network data encryption subsystem
SubsystemIPSec = "networking:ipsec"
// DefaultSubsystem is gossip
DefaultSubsystem = SubsystemGossip
// number of keys to mainrain in the key ring.
keyringSize = 3
)
// map of subsystems and corresponding encryption algorithm. Initially only
// AES_128 in GCM mode is supported.
var subsysToAlgo = map[string]api.EncryptionKey_Algorithm{
SubsystemGossip: api.AES_128_GCM,
SubsystemIPSec: api.AES_128_GCM,
}
type keyRing struct {
lClock uint64
keys []*api.EncryptionKey
}
// Config for the keymanager that can be modified
type Config struct {
ClusterName string
Keylen int
RotationInterval time.Duration
Subsystems []string
}
// KeyManager handles key allocation, rotation & distribution
type KeyManager struct {
config *Config
store *store.MemoryStore
keyRing *keyRing
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
}
// DefaultConfig provides the default config for keymanager
func DefaultConfig() *Config {
return &Config{
ClusterName: store.DefaultClusterName,
Keylen: DefaultKeyLen,
RotationInterval: DefaultKeyRotationInterval,
Subsystems: []string{SubsystemGossip, SubsystemIPSec},
}
}
// New creates an instance of keymanager with the given config
func New(store *store.MemoryStore, config *Config) *KeyManager {
for _, subsys := range config.Subsystems {
if subsys != SubsystemGossip && subsys != SubsystemIPSec {
return nil
}
}
return &KeyManager{
config: config,
store: store,
keyRing: &keyRing{lClock: genSkew()},
}
}
func (k *KeyManager) allocateKey(ctx context.Context, subsys string) *api.EncryptionKey {
key := make([]byte, k.config.Keylen)
_, err := cryptorand.Read(key)
if err != nil {
panic(errors.Wrap(err, "key generated failed"))
}
k.keyRing.lClock++
return &api.EncryptionKey{
Subsystem: subsys,
Algorithm: subsysToAlgo[subsys],
Key: key,
LamportTime: k.keyRing.lClock,
}
}
func (k *KeyManager) updateKey(cluster *api.Cluster) error {
return k.store.Update(func(tx store.Tx) error {
cluster = store.GetCluster(tx, cluster.ID)
if cluster == nil {
return nil
}
cluster.EncryptionKeyLamportClock = k.keyRing.lClock
cluster.NetworkBootstrapKeys = k.keyRing.keys
return store.UpdateCluster(tx, cluster)
})
}
func (k *KeyManager) rotateKey(ctx context.Context) error {
var (
clusters []*api.Cluster
err error
)
k.store.View(func(readTx store.ReadTx) {
clusters, err = store.FindClusters(readTx, store.ByName(k.config.ClusterName))
})
if err != nil {
log.G(ctx).Errorf("reading cluster config failed, %v", err)
return err
}
cluster := clusters[0]
if len(cluster.NetworkBootstrapKeys) == 0 {
panic(errors.New("no key in the cluster config"))
}
subsysKeys := map[string][]*api.EncryptionKey{}
for _, key := range k.keyRing.keys {
subsysKeys[key.Subsystem] = append(subsysKeys[key.Subsystem], key)
}
k.keyRing.keys = []*api.EncryptionKey{}
// We maintain the latest key and the one before in the key ring to allow
// agents to communicate without disruption on key change.
for subsys, keys := range subsysKeys {
if len(keys) == keyringSize {
min := 0
for i, key := range keys[1:] {
if key.LamportTime < keys[min].LamportTime {
min = i
}
}
keys = append(keys[0:min], keys[min+1:]...)
}
keys = append(keys, k.allocateKey(ctx, subsys))
subsysKeys[subsys] = keys
}
for _, keys := range subsysKeys {
k.keyRing.keys = append(k.keyRing.keys, keys...)
}
return k.updateKey(cluster)
}
// Run starts the keymanager, it doesn't return
func (k *KeyManager) Run(ctx context.Context) error {
k.mu.Lock()
ctx = log.WithModule(ctx, "keymanager")
var (
clusters []*api.Cluster
err error
)
k.store.View(func(readTx store.ReadTx) {
clusters, err = store.FindClusters(readTx, store.ByName(k.config.ClusterName))
})
if err != nil {
log.G(ctx).Errorf("reading cluster config failed, %v", err)
k.mu.Unlock()
return err
}
cluster := clusters[0]
if len(cluster.NetworkBootstrapKeys) == 0 {
for _, subsys := range k.config.Subsystems {
for i := 0; i < keyringSize; i++ {
k.keyRing.keys = append(k.keyRing.keys, k.allocateKey(ctx, subsys))
}
}
if err := k.updateKey(cluster); err != nil {
log.G(ctx).Errorf("store update failed %v", err)
}
} else {
k.keyRing.lClock = cluster.EncryptionKeyLamportClock
k.keyRing.keys = cluster.NetworkBootstrapKeys
}
ticker := time.NewTicker(k.config.RotationInterval)
defer ticker.Stop()
k.ctx, k.cancel = context.WithCancel(ctx)
k.mu.Unlock()
for {
select {
case <-ticker.C:
k.rotateKey(ctx)
case <-k.ctx.Done():
return nil
}
}
}
// Stop stops the running instance of key manager
func (k *KeyManager) Stop() error {
k.mu.Lock()
defer k.mu.Unlock()
if k.cancel == nil {
return errors.New("keymanager is not started")
}
k.cancel()
return nil
}
// genSkew generates a random uint64 number between 0 and 65535
func genSkew() uint64 {
b := make([]byte, 2)
if _, err := cryptorand.Read(b); err != nil {
panic(err)
}
return uint64(binary.BigEndian.Uint16(b))
}