// BootstrapCluster receives a directory and creates both new Root CA key material // and a ManagerRole key/certificate pair to be used by the initial cluster manager func BootstrapCluster(baseCertDir string) error { paths := NewConfigPaths(baseCertDir) rootCA, err := CreateAndWriteRootCA(rootCN, paths.RootCA) if err != nil { return err } nodeID := identity.NewNodeID() newOrg := identity.NewID() _, err = GenerateAndSignNewTLSCert(rootCA, nodeID, ManagerRole, newOrg, paths.Node) return err }
// IssueNodeCertificate is responsible for gatekeeping both certificate requests from new nodes in the swarm, // and authorizing certificate renewals. // If a node presented a valid certificate, the corresponding certificate is set in a RENEW state. // If a node failed to present a valid certificate, we enforce all the policies currently configured in // the swarm for node acceptance: check for the validity of the presented secret and check what is the // acceptance state the certificate should be put in (PENDING or ACCEPTED). // After going through the configured policies, a new random node ID is generated, and the corresponding node // entry is created. IssueNodeCertificate is the only place where new node entries to raft should be created. func (s *Server) IssueNodeCertificate(ctx context.Context, request *api.IssueNodeCertificateRequest) (*api.IssueNodeCertificateResponse, error) { // First, let's see if the remote node is proposing to be added as a valid node, and with a non-empty CSR if len(request.CSR) == 0 || (request.Role != api.NodeRoleWorker && request.Role != api.NodeRoleManager) { return nil, grpc.Errorf(codes.InvalidArgument, codes.InvalidArgument.String()) } if err := s.addTask(); err != nil { return nil, err } defer s.doneTask() // If the remote node is an Agent (either forwarded by a manager, or calling directly), // issue a renew agent certificate entry with the correct ID nodeID, err := AuthorizeForwardedRoleAndOrg(ctx, []string{AgentRole}, []string{ManagerRole}, s.securityConfig.ClientTLSCreds.Organization()) if err == nil { return s.issueRenewCertificate(ctx, nodeID, request.CSR) } // If the remote node is a Manager (either forwarded by another manager, or calling directly), // issue a renew certificate entry with the correct ID nodeID, err = AuthorizeForwardedRoleAndOrg(ctx, []string{ManagerRole}, []string{ManagerRole}, s.securityConfig.ClientTLSCreds.Organization()) if err == nil { return s.issueRenewCertificate(ctx, nodeID, request.CSR) } // The remote node didn't successfully present a valid MTLS certificate, let's issue a PENDING // certificate with a new random ID nodeMembership := api.NodeMembershipPending // If there are acceptance policies configured in the system, we should enforce them policy := s.getRolePolicy(request.Role) if policy != nil { // If the policy has a Secret set, let's verify it if policy.Secret != nil { if err := checkSecretValidity(policy, request.Secret); err != nil { return nil, grpc.Errorf(codes.InvalidArgument, "A valid secret token is necessary to join this cluster: %v", err) } } // Check to see if our autoacceptance policy allows this node to be issued without manual intervention if policy.Autoaccept { nodeMembership = api.NodeMembershipAccepted } } // Max number of collisions of ID or CN to tolerate before giving up maxRetries := 3 // Generate a random ID for this new node for i := 0; ; i++ { nodeID = identity.NewNodeID() // Create a new node err := s.store.Update(func(tx store.Tx) error { node := &api.Node{ ID: nodeID, Certificate: api.Certificate{ CSR: request.CSR, CN: nodeID, Role: request.Role, Status: api.IssuanceStatus{ State: api.IssuanceStatePending, }, }, Spec: api.NodeSpec{ Role: request.Role, Membership: nodeMembership, }, } return store.CreateNode(tx, node) }) if err == nil { log.G(ctx).WithFields(logrus.Fields{ "node.id": nodeID, "node.role": request.Role, "method": "IssueNodeCertificate", }).Debugf("new certificate entry added") break } if err != store.ErrExist { return nil, err } if i == maxRetries { return nil, err } log.G(ctx).WithFields(logrus.Fields{ "node.id": nodeID, "node.role": request.Role, "method": "IssueNodeCertificate", }).Errorf("randomly generated node ID collided with an existing one - retrying") } return &api.IssueNodeCertificateResponse{ NodeID: nodeID, }, 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, baseCertDir, caHash, secret, proposedRole string, picker *picker.Picker, nodeInfo chan<- string) (*SecurityConfig, error) { paths := NewConfigPaths(baseCertDir) var ( rootCA RootCA serverTLSCreds, clientTLSCreds *MutableTLSCreds err error ) // Check if we already have a CA certificate on disk. We need a CA to have a valid SecurityConfig rootCA, err = GetLocalRootCA(baseCertDir) switch err { case nil: log.Debugf("loaded local CA certificate: %s.", paths.RootCA.Cert) case ErrNoLocalRootCA: log.Debugf("no valid local CA certificate found: %v", err) // Get a digest for the optional CA hash string that we've been provided // If we were provided a non-empty string, and it is an invalid hash, return // otherwise, allow the invalid digest through. d, err := digest.ParseDigest(caHash) if err != nil && caHash != "" { return nil, err } // Get the remote CA certificate, verify integrity with the hash provided rootCA, err = GetRemoteCA(ctx, d, picker) if err != nil { return nil, err } // Save root CA certificate to disk if err = saveRootCA(rootCA, paths.RootCA); err != nil { return nil, err } log.Debugf("downloaded remote CA certificate.") default: return nil, err } // 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, paths.Node) if err != nil { log.Debugf("no valid local TLS credentials found: %v", err) var ( tlsKeyPair *tls.Certificate err error ) if rootCA.CanSign() { // Create a new random ID for this certificate cn := identity.NewNodeID() org := identity.NewID() if nodeInfo != nil { nodeInfo <- cn } tlsKeyPair, err = rootCA.IssueAndSaveNewCertificates(paths.Node, cn, proposedRole, org) } 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, paths.Node, proposedRole, secret, picker, nil, nodeInfo) if err != nil { return nil, err } } // Create the Server TLS Credentials for this node. These will not be used by agents. 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.Debugf("new TLS credentials generated: %s.", paths.Node.Cert) } else { if nodeInfo != nil { nodeInfo <- clientTLSCreds.NodeID() } log.Debugf("loaded local TLS credentials: %s.", paths.Node.Cert) } return &SecurityConfig{ rootCA: &rootCA, ServerTLSCreds: serverTLSCreds, ClientTLSCreds: clientTLSCreds, }, nil }