cluster.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package controlapi
  2. import (
  3. "strings"
  4. "time"
  5. "github.com/docker/swarmkit/api"
  6. "github.com/docker/swarmkit/ca"
  7. "github.com/docker/swarmkit/manager/encryption"
  8. "github.com/docker/swarmkit/manager/state/store"
  9. "github.com/docker/swarmkit/protobuf/ptypes"
  10. "golang.org/x/net/context"
  11. "google.golang.org/grpc"
  12. "google.golang.org/grpc/codes"
  13. )
  14. const (
  15. // expiredCertGrace is the amount of time to keep a node in the
  16. // blacklist beyond its certificate expiration timestamp.
  17. expiredCertGrace = 24 * time.Hour * 7
  18. )
  19. func validateClusterSpec(spec *api.ClusterSpec) error {
  20. if spec == nil {
  21. return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  22. }
  23. // Validate that expiry time being provided is valid, and over our minimum
  24. if spec.CAConfig.NodeCertExpiry != nil {
  25. expiry, err := ptypes.Duration(spec.CAConfig.NodeCertExpiry)
  26. if err != nil {
  27. return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  28. }
  29. if expiry < ca.MinNodeCertExpiration {
  30. return grpc.Errorf(codes.InvalidArgument, "minimum certificate expiry time is: %s", ca.MinNodeCertExpiration)
  31. }
  32. }
  33. // Validate that AcceptancePolicies only include Secrets that are bcrypted
  34. // TODO(diogo): Add a global list of acceptace algorithms. We only support bcrypt for now.
  35. if len(spec.AcceptancePolicy.Policies) > 0 {
  36. for _, policy := range spec.AcceptancePolicy.Policies {
  37. if policy.Secret != nil && strings.ToLower(policy.Secret.Alg) != "bcrypt" {
  38. return grpc.Errorf(codes.InvalidArgument, "hashing algorithm is not supported: %s", policy.Secret.Alg)
  39. }
  40. }
  41. }
  42. // Validate that heartbeatPeriod time being provided is valid
  43. if spec.Dispatcher.HeartbeatPeriod != nil {
  44. heartbeatPeriod, err := ptypes.Duration(spec.Dispatcher.HeartbeatPeriod)
  45. if err != nil {
  46. return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  47. }
  48. if heartbeatPeriod < 0 {
  49. return grpc.Errorf(codes.InvalidArgument, "heartbeat time period cannot be a negative duration")
  50. }
  51. }
  52. return nil
  53. }
  54. // GetCluster returns a Cluster given a ClusterID.
  55. // - Returns `InvalidArgument` if ClusterID is not provided.
  56. // - Returns `NotFound` if the Cluster is not found.
  57. func (s *Server) GetCluster(ctx context.Context, request *api.GetClusterRequest) (*api.GetClusterResponse, error) {
  58. if request.ClusterID == "" {
  59. return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  60. }
  61. var cluster *api.Cluster
  62. s.store.View(func(tx store.ReadTx) {
  63. cluster = store.GetCluster(tx, request.ClusterID)
  64. })
  65. if cluster == nil {
  66. return nil, grpc.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID)
  67. }
  68. redactedClusters := redactClusters([]*api.Cluster{cluster})
  69. // WARN: we should never return cluster here. We need to redact the private fields first.
  70. return &api.GetClusterResponse{
  71. Cluster: redactedClusters[0],
  72. }, nil
  73. }
  74. // UpdateCluster updates a Cluster referenced by ClusterID with the given ClusterSpec.
  75. // - Returns `NotFound` if the Cluster is not found.
  76. // - Returns `InvalidArgument` if the ClusterSpec is malformed.
  77. // - Returns `Unimplemented` if the ClusterSpec references unimplemented features.
  78. // - Returns an error if the update fails.
  79. func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRequest) (*api.UpdateClusterResponse, error) {
  80. if request.ClusterID == "" || request.ClusterVersion == nil {
  81. return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  82. }
  83. if err := validateClusterSpec(request.Spec); err != nil {
  84. return nil, err
  85. }
  86. var cluster *api.Cluster
  87. err := s.store.Update(func(tx store.Tx) error {
  88. cluster = store.GetCluster(tx, request.ClusterID)
  89. if cluster == nil {
  90. return nil
  91. }
  92. cluster.Meta.Version = *request.ClusterVersion
  93. cluster.Spec = *request.Spec.Copy()
  94. expireBlacklistedCerts(cluster)
  95. if request.Rotation.WorkerJoinToken {
  96. cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(s.rootCA)
  97. }
  98. if request.Rotation.ManagerJoinToken {
  99. cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(s.rootCA)
  100. }
  101. var unlockKeys []*api.EncryptionKey
  102. var managerKey *api.EncryptionKey
  103. for _, eKey := range cluster.UnlockKeys {
  104. if eKey.Subsystem == ca.ManagerRole {
  105. if !cluster.Spec.EncryptionConfig.AutoLockManagers {
  106. continue
  107. }
  108. managerKey = eKey
  109. }
  110. unlockKeys = append(unlockKeys, eKey)
  111. }
  112. switch {
  113. case !cluster.Spec.EncryptionConfig.AutoLockManagers:
  114. break
  115. case managerKey == nil:
  116. unlockKeys = append(unlockKeys, &api.EncryptionKey{
  117. Subsystem: ca.ManagerRole,
  118. Key: encryption.GenerateSecretKey(),
  119. })
  120. case request.Rotation.ManagerUnlockKey:
  121. managerKey.Key = encryption.GenerateSecretKey()
  122. }
  123. cluster.UnlockKeys = unlockKeys
  124. return store.UpdateCluster(tx, cluster)
  125. })
  126. if err != nil {
  127. return nil, err
  128. }
  129. if cluster == nil {
  130. return nil, grpc.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID)
  131. }
  132. redactedClusters := redactClusters([]*api.Cluster{cluster})
  133. // WARN: we should never return cluster here. We need to redact the private fields first.
  134. return &api.UpdateClusterResponse{
  135. Cluster: redactedClusters[0],
  136. }, nil
  137. }
  138. func filterClusters(candidates []*api.Cluster, filters ...func(*api.Cluster) bool) []*api.Cluster {
  139. result := []*api.Cluster{}
  140. for _, c := range candidates {
  141. match := true
  142. for _, f := range filters {
  143. if !f(c) {
  144. match = false
  145. break
  146. }
  147. }
  148. if match {
  149. result = append(result, c)
  150. }
  151. }
  152. return result
  153. }
  154. // ListClusters returns a list of all clusters.
  155. func (s *Server) ListClusters(ctx context.Context, request *api.ListClustersRequest) (*api.ListClustersResponse, error) {
  156. var (
  157. clusters []*api.Cluster
  158. err error
  159. )
  160. s.store.View(func(tx store.ReadTx) {
  161. switch {
  162. case request.Filters != nil && len(request.Filters.Names) > 0:
  163. clusters, err = store.FindClusters(tx, buildFilters(store.ByName, request.Filters.Names))
  164. case request.Filters != nil && len(request.Filters.NamePrefixes) > 0:
  165. clusters, err = store.FindClusters(tx, buildFilters(store.ByNamePrefix, request.Filters.NamePrefixes))
  166. case request.Filters != nil && len(request.Filters.IDPrefixes) > 0:
  167. clusters, err = store.FindClusters(tx, buildFilters(store.ByIDPrefix, request.Filters.IDPrefixes))
  168. default:
  169. clusters, err = store.FindClusters(tx, store.All)
  170. }
  171. })
  172. if err != nil {
  173. return nil, err
  174. }
  175. if request.Filters != nil {
  176. clusters = filterClusters(clusters,
  177. func(e *api.Cluster) bool {
  178. return filterContains(e.Spec.Annotations.Name, request.Filters.Names)
  179. },
  180. func(e *api.Cluster) bool {
  181. return filterContainsPrefix(e.Spec.Annotations.Name, request.Filters.NamePrefixes)
  182. },
  183. func(e *api.Cluster) bool {
  184. return filterContainsPrefix(e.ID, request.Filters.IDPrefixes)
  185. },
  186. func(e *api.Cluster) bool {
  187. return filterMatchLabels(e.Spec.Annotations.Labels, request.Filters.Labels)
  188. },
  189. )
  190. }
  191. // WARN: we should never return cluster here. We need to redact the private fields first.
  192. return &api.ListClustersResponse{
  193. Clusters: redactClusters(clusters),
  194. }, nil
  195. }
  196. // redactClusters is a method that enforces a whitelist of fields that are ok to be
  197. // returned in the Cluster object. It should filter out all sensitive information.
  198. func redactClusters(clusters []*api.Cluster) []*api.Cluster {
  199. var redactedClusters []*api.Cluster
  200. // Only add public fields to the new clusters
  201. for _, cluster := range clusters {
  202. // Copy all the mandatory fields
  203. // Do not copy secret key
  204. newCluster := &api.Cluster{
  205. ID: cluster.ID,
  206. Meta: cluster.Meta,
  207. Spec: cluster.Spec,
  208. RootCA: api.RootCA{
  209. CACert: cluster.RootCA.CACert,
  210. CACertHash: cluster.RootCA.CACertHash,
  211. JoinTokens: cluster.RootCA.JoinTokens,
  212. },
  213. BlacklistedCertificates: cluster.BlacklistedCertificates,
  214. }
  215. redactedClusters = append(redactedClusters, newCluster)
  216. }
  217. return redactedClusters
  218. }
  219. func expireBlacklistedCerts(cluster *api.Cluster) {
  220. nowMinusGrace := time.Now().Add(-expiredCertGrace)
  221. for cn, blacklistedCert := range cluster.BlacklistedCertificates {
  222. if blacklistedCert.Expiry == nil {
  223. continue
  224. }
  225. expiry, err := ptypes.Timestamp(blacklistedCert.Expiry)
  226. if err == nil && nowMinusGrace.After(expiry) {
  227. delete(cluster.BlacklistedCertificates, cn)
  228. }
  229. }
  230. }