|
@@ -21,6 +21,7 @@ import (
|
|
|
"github.com/docker/swarmkit/log"
|
|
|
"github.com/docker/swarmkit/remotes"
|
|
|
"github.com/pkg/errors"
|
|
|
+ "google.golang.org/grpc/credentials"
|
|
|
|
|
|
"golang.org/x/net/context"
|
|
|
)
|
|
@@ -52,8 +53,13 @@ const (
|
|
|
// SecurityConfig is used to represent a node's security configuration. It includes information about
|
|
|
// the RootCA and ServerTLSCreds/ClientTLSCreds transport authenticators to be used for MTLS
|
|
|
type SecurityConfig struct {
|
|
|
+ // mu protects against concurrent access to fields inside the structure.
|
|
|
mu sync.Mutex
|
|
|
|
|
|
+ // renewalMu makes sure only one certificate renewal attempt happens at
|
|
|
+ // a time. It should never be locked after mu is already locked.
|
|
|
+ renewalMu sync.Mutex
|
|
|
+
|
|
|
rootCA *RootCA
|
|
|
externalCA *ExternalCA
|
|
|
keyReadWriter *KeyReadWriter
|
|
@@ -234,96 +240,148 @@ func DownloadRootCA(ctx context.Context, paths CertPaths, token string, r remote
|
|
|
return rootCA, nil
|
|
|
}
|
|
|
|
|
|
-// LoadOrCreateSecurityConfig encapsulates the security logic behind joining a cluster.
|
|
|
-// Every node requires at least a set of TLS certificates with which to join the cluster with.
|
|
|
-// In the case of a manager, these certificates will be used both for client and server credentials.
|
|
|
-func LoadOrCreateSecurityConfig(ctx context.Context, rootCA RootCA, token, proposedRole string, remotes remotes.Remotes, nodeInfo chan<- api.IssueNodeCertificateResponse, krw *KeyReadWriter) (*SecurityConfig, error) {
|
|
|
+// LoadSecurityConfig loads TLS credentials from disk, or returns an error if
|
|
|
+// these credentials do not exist or are unusable.
|
|
|
+func LoadSecurityConfig(ctx context.Context, rootCA RootCA, krw *KeyReadWriter) (*SecurityConfig, error) {
|
|
|
ctx = log.WithModule(ctx, "tls")
|
|
|
|
|
|
// At this point we've successfully loaded the CA details from disk, or
|
|
|
// successfully downloaded them remotely. The next step is to try to
|
|
|
// load our certificates.
|
|
|
- clientTLSCreds, serverTLSCreds, err := LoadTLSCreds(rootCA, krw)
|
|
|
+
|
|
|
+ // Read both the Cert and Key from disk
|
|
|
+ cert, key, err := krw.Read()
|
|
|
if err != nil {
|
|
|
- if _, ok := errors.Cause(err).(ErrInvalidKEK); ok {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
|
|
|
- log.G(ctx).WithError(err).Debugf("no node credentials found in: %s", krw.Target())
|
|
|
+ // Create an x509 certificate out of the contents on disk
|
|
|
+ certBlock, _ := pem.Decode([]byte(cert))
|
|
|
+ if certBlock == nil {
|
|
|
+ return nil, errors.New("failed to parse certificate PEM")
|
|
|
+ }
|
|
|
|
|
|
- var (
|
|
|
- tlsKeyPair *tls.Certificate
|
|
|
- err error
|
|
|
- )
|
|
|
+ // Create an X509Cert so we can .Verify()
|
|
|
+ X509Cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
|
|
|
- if rootCA.CanSign() {
|
|
|
- // Create a new random ID for this certificate
|
|
|
- cn := identity.NewID()
|
|
|
- org := identity.NewID()
|
|
|
+ // Include our root pool
|
|
|
+ opts := x509.VerifyOptions{
|
|
|
+ Roots: rootCA.Pool,
|
|
|
+ }
|
|
|
|
|
|
- if nodeInfo != nil {
|
|
|
- nodeInfo <- api.IssueNodeCertificateResponse{
|
|
|
- NodeID: cn,
|
|
|
- NodeMembership: api.NodeMembershipAccepted,
|
|
|
- }
|
|
|
- }
|
|
|
- tlsKeyPair, err = rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org)
|
|
|
- if err != nil {
|
|
|
- log.G(ctx).WithFields(logrus.Fields{
|
|
|
- "node.id": cn,
|
|
|
- "node.role": proposedRole,
|
|
|
- }).WithError(err).Errorf("failed to issue and save new certificate")
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+ // Check to see if this certificate was signed by our CA, and isn't expired
|
|
|
+ if _, err := X509Cert.Verify(opts); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now that we know this certificate is valid, create a TLS Certificate for our
|
|
|
+ // credentials
|
|
|
+ keyPair, err := tls.X509KeyPair(cert, key)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load the Certificates as server credentials
|
|
|
+ serverTLSCreds, err := rootCA.NewServerTLSCredentials(&keyPair)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
|
|
|
+ // Load the Certificates also as client credentials.
|
|
|
+ // Both workers and managers always connect to remote managers,
|
|
|
+ // so ServerName is always set to ManagerRole here.
|
|
|
+ clientTLSCreds, err := rootCA.NewClientTLSCredentials(&keyPair, ManagerRole)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ log.G(ctx).WithFields(logrus.Fields{
|
|
|
+ "node.id": clientTLSCreds.NodeID(),
|
|
|
+ "node.role": clientTLSCreds.Role(),
|
|
|
+ }).Debug("loaded node credentials")
|
|
|
+
|
|
|
+ return NewSecurityConfig(&rootCA, krw, clientTLSCreds, serverTLSCreds), nil
|
|
|
+}
|
|
|
+
|
|
|
+// CertificateRequestConfig contains the information needed to request a
|
|
|
+// certificate from a remote CA.
|
|
|
+type CertificateRequestConfig struct {
|
|
|
+ // Token is the join token that authenticates us with the CA.
|
|
|
+ Token string
|
|
|
+ // Remotes is the set of remote CAs.
|
|
|
+ Remotes remotes.Remotes
|
|
|
+ // Credentials provides transport credentials for communicating with the
|
|
|
+ // remote server.
|
|
|
+ Credentials credentials.TransportCredentials
|
|
|
+}
|
|
|
+
|
|
|
+// CreateSecurityConfig creates a new key and cert for this node, either locally
|
|
|
+// or via a remote CA.
|
|
|
+func (rootCA RootCA) CreateSecurityConfig(ctx context.Context, krw *KeyReadWriter, config CertificateRequestConfig) (*SecurityConfig, error) {
|
|
|
+ ctx = log.WithModule(ctx, "tls")
|
|
|
+
|
|
|
+ var (
|
|
|
+ tlsKeyPair *tls.Certificate
|
|
|
+ err error
|
|
|
+ )
|
|
|
+
|
|
|
+ if rootCA.CanSign() {
|
|
|
+ // Create a new random ID for this certificate
|
|
|
+ cn := identity.NewID()
|
|
|
+ org := identity.NewID()
|
|
|
+
|
|
|
+ proposedRole := ManagerRole
|
|
|
+ tlsKeyPair, err = rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org)
|
|
|
+ if err != nil {
|
|
|
log.G(ctx).WithFields(logrus.Fields{
|
|
|
"node.id": cn,
|
|
|
"node.role": proposedRole,
|
|
|
- }).Debug("issued new TLS certificate")
|
|
|
- } else {
|
|
|
- // There was an error loading our Credentials, let's get a new certificate issued
|
|
|
- // Last argument is nil because at this point we don't have any valid TLS creds
|
|
|
- tlsKeyPair, err = rootCA.RequestAndSaveNewCertificates(ctx, krw, token, remotes, nil, nodeInfo)
|
|
|
- if err != nil {
|
|
|
- log.G(ctx).WithError(err).Error("failed to request save new certificate")
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- }
|
|
|
- // Create the Server TLS Credentials for this node. These will not be used by workers.
|
|
|
- serverTLSCreds, err = rootCA.NewServerTLSCredentials(tlsKeyPair)
|
|
|
- if err != nil {
|
|
|
+ }).WithError(err).Errorf("failed to issue and save new certificate")
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- // Create a TLSConfig to be used when this node connects as a client to another remote node.
|
|
|
- // We're using ManagerRole as remote serverName for TLS host verification
|
|
|
- clientTLSCreds, err = rootCA.NewClientTLSCredentials(tlsKeyPair, ManagerRole)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
log.G(ctx).WithFields(logrus.Fields{
|
|
|
- "node.id": clientTLSCreds.NodeID(),
|
|
|
- "node.role": clientTLSCreds.Role(),
|
|
|
- }).Debugf("new node credentials generated: %s", krw.Target())
|
|
|
+ "node.id": cn,
|
|
|
+ "node.role": proposedRole,
|
|
|
+ }).Debug("issued new TLS certificate")
|
|
|
} else {
|
|
|
- if nodeInfo != nil {
|
|
|
- nodeInfo <- api.IssueNodeCertificateResponse{
|
|
|
- NodeID: clientTLSCreds.NodeID(),
|
|
|
- NodeMembership: api.NodeMembershipAccepted,
|
|
|
- }
|
|
|
+ // Request certificate issuance from a remote CA.
|
|
|
+ // Last argument is nil because at this point we don't have any valid TLS creds
|
|
|
+ tlsKeyPair, err = rootCA.RequestAndSaveNewCertificates(ctx, krw, config)
|
|
|
+ if err != nil {
|
|
|
+ log.G(ctx).WithError(err).Error("failed to request save new certificate")
|
|
|
+ return nil, err
|
|
|
}
|
|
|
- log.G(ctx).WithFields(logrus.Fields{
|
|
|
- "node.id": clientTLSCreds.NodeID(),
|
|
|
- "node.role": clientTLSCreds.Role(),
|
|
|
- }).Debug("loaded node credentials")
|
|
|
+ }
|
|
|
+ // Create the Server TLS Credentials for this node. These will not be used by workers.
|
|
|
+ serverTLSCreds, err := rootCA.NewServerTLSCredentials(tlsKeyPair)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
+ // Create a TLSConfig to be used when this node connects as a client to another remote node.
|
|
|
+ // We're using ManagerRole as remote serverName for TLS host verification
|
|
|
+ clientTLSCreds, err := rootCA.NewClientTLSCredentials(tlsKeyPair, ManagerRole)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ log.G(ctx).WithFields(logrus.Fields{
|
|
|
+ "node.id": clientTLSCreds.NodeID(),
|
|
|
+ "node.role": clientTLSCreds.Role(),
|
|
|
+ }).Debugf("new node credentials generated: %s", krw.Target())
|
|
|
+
|
|
|
return NewSecurityConfig(&rootCA, krw, clientTLSCreds, serverTLSCreds), nil
|
|
|
}
|
|
|
|
|
|
// RenewTLSConfigNow gets a new TLS cert and key, and updates the security config if provided. This is similar to
|
|
|
// RenewTLSConfig, except while that monitors for expiry, and periodically renews, this renews once and is blocking
|
|
|
func RenewTLSConfigNow(ctx context.Context, s *SecurityConfig, r remotes.Remotes) error {
|
|
|
+ s.renewalMu.Lock()
|
|
|
+ defer s.renewalMu.Unlock()
|
|
|
+
|
|
|
ctx = log.WithModule(ctx, "tls")
|
|
|
log := log.G(ctx).WithFields(logrus.Fields{
|
|
|
"node.id": s.ClientTLSCreds.NodeID(),
|
|
@@ -334,10 +392,10 @@ func RenewTLSConfigNow(ctx context.Context, s *SecurityConfig, r remotes.Remotes
|
|
|
rootCA := s.RootCA()
|
|
|
tlsKeyPair, err := rootCA.RequestAndSaveNewCertificates(ctx,
|
|
|
s.KeyWriter(),
|
|
|
- "",
|
|
|
- r,
|
|
|
- s.ClientTLSCreds,
|
|
|
- nil)
|
|
|
+ CertificateRequestConfig{
|
|
|
+ Remotes: r,
|
|
|
+ Credentials: s.ClientTLSCreds,
|
|
|
+ })
|
|
|
if err != nil {
|
|
|
log.WithError(err).Errorf("failed to renew the certificate")
|
|
|
return err
|
|
@@ -463,61 +521,6 @@ func calculateRandomExpiry(validFrom, validUntil time.Time) time.Duration {
|
|
|
return expiry
|
|
|
}
|
|
|
|
|
|
-// LoadTLSCreds loads tls credentials from the specified path and verifies that
|
|
|
-// thay are valid for the RootCA.
|
|
|
-func LoadTLSCreds(rootCA RootCA, kr KeyReader) (*MutableTLSCreds, *MutableTLSCreds, error) {
|
|
|
- // Read both the Cert and Key from disk
|
|
|
- cert, key, err := kr.Read()
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // Create an x509 certificate out of the contents on disk
|
|
|
- certBlock, _ := pem.Decode([]byte(cert))
|
|
|
- if certBlock == nil {
|
|
|
- return nil, nil, errors.New("failed to parse certificate PEM")
|
|
|
- }
|
|
|
-
|
|
|
- // Create an X509Cert so we can .Verify()
|
|
|
- X509Cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // Include our root pool
|
|
|
- opts := x509.VerifyOptions{
|
|
|
- Roots: rootCA.Pool,
|
|
|
- }
|
|
|
-
|
|
|
- // Check to see if this certificate was signed by our CA, and isn't expired
|
|
|
- if _, err := X509Cert.Verify(opts); err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // Now that we know this certificate is valid, create a TLS Certificate for our
|
|
|
- // credentials
|
|
|
- keyPair, err := tls.X509KeyPair(cert, key)
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // Load the Certificates as server credentials
|
|
|
- serverTLSCreds, err := rootCA.NewServerTLSCredentials(&keyPair)
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // Load the Certificates also as client credentials.
|
|
|
- // Both workers and managers always connect to remote managers,
|
|
|
- // so ServerName is always set to ManagerRole here.
|
|
|
- clientTLSCreds, err := rootCA.NewClientTLSCredentials(&keyPair, ManagerRole)
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
-
|
|
|
- return clientTLSCreds, serverTLSCreds, nil
|
|
|
-}
|
|
|
-
|
|
|
// NewServerTLSConfig returns a tls.Config configured for a TLS Server, given a tls.Certificate
|
|
|
// and the PEM-encoded root CA Certificate
|
|
|
func NewServerTLSConfig(cert *tls.Certificate, rootCAPool *x509.CertPool) (*tls.Config, error) {
|
|
@@ -554,8 +557,8 @@ func NewClientTLSConfig(cert *tls.Certificate, rootCAPool *x509.CertPool, server
|
|
|
|
|
|
// NewClientTLSCredentials returns GRPC credentials for a TLS GRPC client, given a tls.Certificate
|
|
|
// a PEM-Encoded root CA Certificate, and the name of the remote server the client wants to connect to.
|
|
|
-func (rca *RootCA) NewClientTLSCredentials(cert *tls.Certificate, serverName string) (*MutableTLSCreds, error) {
|
|
|
- tlsConfig, err := NewClientTLSConfig(cert, rca.Pool, serverName)
|
|
|
+func (rootCA *RootCA) NewClientTLSCredentials(cert *tls.Certificate, serverName string) (*MutableTLSCreds, error) {
|
|
|
+ tlsConfig, err := NewClientTLSConfig(cert, rootCA.Pool, serverName)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -567,8 +570,8 @@ func (rca *RootCA) NewClientTLSCredentials(cert *tls.Certificate, serverName str
|
|
|
|
|
|
// NewServerTLSCredentials returns GRPC credentials for a TLS GRPC client, given a tls.Certificate
|
|
|
// a PEM-Encoded root CA Certificate, and the name of the remote server the client wants to connect to.
|
|
|
-func (rca *RootCA) NewServerTLSCredentials(cert *tls.Certificate) (*MutableTLSCreds, error) {
|
|
|
- tlsConfig, err := NewServerTLSConfig(cert, rca.Pool)
|
|
|
+func (rootCA *RootCA) NewServerTLSCredentials(cert *tls.Certificate) (*MutableTLSCreds, error) {
|
|
|
+ tlsConfig, err := NewServerTLSConfig(cert, rootCA.Pool)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|