func newListNodesFilters(filter filters.Args) (*swarmapi.ListNodesRequest_Filters, error) { accepted := map[string]bool{ "name": true, "id": true, "label": true, "role": true, "membership": true, } if err := filter.Validate(accepted); err != nil { return nil, err } f := &swarmapi.ListNodesRequest_Filters{ Names: filter.Get("name"), IDPrefixes: filter.Get("id"), Labels: runconfigopts.ConvertKVStringsToMap(filter.Get("label")), } for _, r := range filter.Get("role") { if role, ok := swarmapi.NodeRole_value[strings.ToUpper(r)]; ok { f.Roles = append(f.Roles, swarmapi.NodeRole(role)) } else if r != "" { return nil, fmt.Errorf("Invalid role filter: '%s'", r) } } for _, a := range filter.Get("membership") { if membership, ok := swarmapi.NodeSpec_Membership_value[strings.ToUpper(a)]; ok { f.Memberships = append(f.Memberships, swarmapi.NodeSpec_Membership(membership)) } else if a != "" { return nil, fmt.Errorf("Invalid membership filter: '%s'", a) } } return f, nil }
// NodeSpecToGRPC converts a NodeSpec to a grpc NodeSpec. func NodeSpecToGRPC(s types.NodeSpec) (swarmapi.NodeSpec, error) { spec := swarmapi.NodeSpec{ Annotations: swarmapi.Annotations{ Name: s.Name, Labels: s.Labels, }, } if role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(s.Role))]; ok { spec.Role = swarmapi.NodeRole(role) } else { return swarmapi.NodeSpec{}, fmt.Errorf("invalid Role: %q", s.Role) } if membership, ok := swarmapi.NodeSpec_Membership_value[strings.ToUpper(string(s.Membership))]; ok { spec.Membership = swarmapi.NodeSpec_Membership(membership) } else { return swarmapi.NodeSpec{}, fmt.Errorf("invalid Membership: %q", s.Membership) } if availability, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(s.Availability))]; ok { spec.Availability = swarmapi.NodeSpec_Availability(availability) } else { return swarmapi.NodeSpec{}, fmt.Errorf("invalid Availability: %q", s.Availability) } return spec, nil }
// SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy. func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy) error { spec.AcceptancePolicy.Policies = nil for _, p := range acceptancePolicy.Policies { role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))] if !ok { return fmt.Errorf("invalid Role: %q", p.Role) } policy := &swarmapi.AcceptancePolicy_RoleAdmissionPolicy{ Role: swarmapi.NodeRole(role), Autoaccept: p.Autoaccept, } if p.Secret != "" { hashPwd, _ := bcrypt.GenerateFromPassword([]byte(p.Secret), 0) policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{ Data: hashPwd, Alg: "bcrypt", } } spec.AcceptancePolicy.Policies = append(spec.AcceptancePolicy.Policies, policy) } return nil }
// SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy. func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy, oldSpec *swarmapi.ClusterSpec) error { spec.AcceptancePolicy.Policies = nil hashs := make(map[string][]byte) for _, p := range acceptancePolicy.Policies { role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))] if !ok { return fmt.Errorf("invalid Role: %q", p.Role) } policy := &swarmapi.AcceptancePolicy_RoleAdmissionPolicy{ Role: swarmapi.NodeRole(role), Autoaccept: p.Autoaccept, } if p.Secret != nil { if *p.Secret == "" { // if provided secret is empty, it means erase previous secret. policy.Secret = nil } else { // if provided secret is not empty, we generate a new one. hashPwd, ok := hashs[*p.Secret] if !ok { hashPwd, _ = bcrypt.GenerateFromPassword([]byte(*p.Secret), 0) hashs[*p.Secret] = hashPwd } policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_Secret{ Data: hashPwd, Alg: "bcrypt", } } } else if oldSecret := getOldSecret(oldSpec, policy.Role); oldSecret != nil { // else use the old one. policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_Secret{ Data: oldSecret.Data, Alg: oldSecret.Alg, } } spec.AcceptancePolicy.Policies = append(spec.AcceptancePolicy.Policies, policy) } return nil }
// 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 check for a valid join token and set the // role accordingly. 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 presenting a non-empty CSR if len(request.CSR) == 0 { 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 // certificate with a new random ID role := api.NodeRole(-1) s.mu.Lock() if subtle.ConstantTimeCompare([]byte(s.joinTokens.Manager), []byte(request.Token)) == 1 { role = api.NodeRoleManager } else if subtle.ConstantTimeCompare([]byte(s.joinTokens.Worker), []byte(request.Token)) == 1 { role = api.NodeRoleWorker } s.mu.Unlock() if role < 0 { return nil, grpc.Errorf(codes.InvalidArgument, "A valid join token is necessary to join this cluster") } // 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.NewID() // 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: role, Status: api.IssuanceStatus{ State: api.IssuanceStatePending, }, }, Spec: api.NodeSpec{ Role: role, Membership: api.NodeMembershipAccepted, }, } return store.CreateNode(tx, node) }) if err == nil { log.G(ctx).WithFields(logrus.Fields{ "node.id": nodeID, "node.role": 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": role, "method": "IssueNodeCertificate", }).Errorf("randomly generated node ID collided with an existing one - retrying") } return &api.IssueNodeCertificateResponse{ NodeID: nodeID, NodeMembership: api.NodeMembershipAccepted, }, nil }
// 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 check for a valid join token and set the // role accordingly. 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 presenting a non-empty CSR if len(request.CSR) == 0 { return nil, grpc.Errorf(codes.InvalidArgument, codes.InvalidArgument.String()) } if _, err := s.isRunningLocked(); err != nil { return nil, err } var ( blacklistedCerts map[string]*api.BlacklistedCertificate clusters []*api.Cluster err error ) s.store.View(func(readTx store.ReadTx) { clusters, err = store.FindClusters(readTx, store.ByName("default")) }) // Not having a cluster object yet means we can't check // the blacklist. if err == nil && len(clusters) == 1 { blacklistedCerts = clusters[0].BlacklistedCertificates } // Renewing the cert with a local (unix socket) is always valid. localNodeInfo := ctx.Value(LocalRequestKey) if localNodeInfo != nil { nodeInfo, ok := localNodeInfo.(RemoteNodeInfo) if ok && nodeInfo.NodeID != "" { return s.issueRenewCertificate(ctx, nodeInfo.NodeID, request.CSR) } } // If the remote node is a worker (either forwarded by a manager, or calling directly), // issue a renew worker certificate entry with the correct ID nodeID, err := AuthorizeForwardedRoleAndOrg(ctx, []string{WorkerRole}, []string{ManagerRole}, s.securityConfig.ClientTLSCreds.Organization(), blacklistedCerts) 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(), blacklistedCerts) 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 // certificate with a new random ID role := api.NodeRole(-1) s.mu.Lock() if subtle.ConstantTimeCompare([]byte(s.joinTokens.Manager), []byte(request.Token)) == 1 { role = api.NodeRoleManager } else if subtle.ConstantTimeCompare([]byte(s.joinTokens.Worker), []byte(request.Token)) == 1 { role = api.NodeRoleWorker } s.mu.Unlock() if role < 0 { return nil, grpc.Errorf(codes.InvalidArgument, "A valid join token is necessary to join this cluster") } // 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.NewID() // Create a new node err := s.store.Update(func(tx store.Tx) error { node := &api.Node{ Role: role, ID: nodeID, Certificate: api.Certificate{ CSR: request.CSR, CN: nodeID, Role: role, Status: api.IssuanceStatus{ State: api.IssuanceStatePending, }, }, Spec: api.NodeSpec{ DesiredRole: role, Membership: api.NodeMembershipAccepted, Availability: request.Availability, }, } return store.CreateNode(tx, node) }) if err == nil { log.G(ctx).WithFields(logrus.Fields{ "node.id": nodeID, "node.role": 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": role, "method": "IssueNodeCertificate", }).Errorf("randomly generated node ID collided with an existing one - retrying") } return &api.IssueNodeCertificateResponse{ NodeID: nodeID, NodeMembership: api.NodeMembershipAccepted, }, nil }