cluster.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. package controlapi
  2. import (
  3. "context"
  4. "strings"
  5. "time"
  6. "github.com/docker/swarmkit/api"
  7. "github.com/docker/swarmkit/ca"
  8. "github.com/docker/swarmkit/log"
  9. "github.com/docker/swarmkit/manager/encryption"
  10. "github.com/docker/swarmkit/manager/state/store"
  11. gogotypes "github.com/gogo/protobuf/types"
  12. "google.golang.org/grpc/codes"
  13. "google.golang.org/grpc/status"
  14. )
  15. const (
  16. // expiredCertGrace is the amount of time to keep a node in the
  17. // blacklist beyond its certificate expiration timestamp.
  18. expiredCertGrace = 24 * time.Hour * 7
  19. // inbuilt default subnet size
  20. inbuiltSubnetSize = 24
  21. // VXLAN default port
  22. defaultVXLANPort = 4789
  23. )
  24. var (
  25. // inbuilt default address pool
  26. inbuiltDefaultAddressPool = []string{"10.0.0.0/8"}
  27. )
  28. func validateClusterSpec(spec *api.ClusterSpec) error {
  29. if spec == nil {
  30. return status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  31. }
  32. // Validate that expiry time being provided is valid, and over our minimum
  33. if spec.CAConfig.NodeCertExpiry != nil {
  34. expiry, err := gogotypes.DurationFromProto(spec.CAConfig.NodeCertExpiry)
  35. if err != nil {
  36. return status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  37. }
  38. if expiry < ca.MinNodeCertExpiration {
  39. return status.Errorf(codes.InvalidArgument, "minimum certificate expiry time is: %s", ca.MinNodeCertExpiration)
  40. }
  41. }
  42. // Validate that AcceptancePolicies only include Secrets that are bcrypted
  43. // TODO(diogo): Add a global list of acceptance algorithms. We only support bcrypt for now.
  44. if len(spec.AcceptancePolicy.Policies) > 0 {
  45. for _, policy := range spec.AcceptancePolicy.Policies {
  46. if policy.Secret != nil && strings.ToLower(policy.Secret.Alg) != "bcrypt" {
  47. return status.Errorf(codes.InvalidArgument, "hashing algorithm is not supported: %s", policy.Secret.Alg)
  48. }
  49. }
  50. }
  51. // Validate that heartbeatPeriod time being provided is valid
  52. if spec.Dispatcher.HeartbeatPeriod != nil {
  53. heartbeatPeriod, err := gogotypes.DurationFromProto(spec.Dispatcher.HeartbeatPeriod)
  54. if err != nil {
  55. return status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  56. }
  57. if heartbeatPeriod < 0 {
  58. return status.Errorf(codes.InvalidArgument, "heartbeat time period cannot be a negative duration")
  59. }
  60. }
  61. if spec.Annotations.Name != store.DefaultClusterName {
  62. return status.Errorf(codes.InvalidArgument, "modification of cluster name is not allowed")
  63. }
  64. return nil
  65. }
  66. // GetCluster returns a Cluster given a ClusterID.
  67. // - Returns `InvalidArgument` if ClusterID is not provided.
  68. // - Returns `NotFound` if the Cluster is not found.
  69. func (s *Server) GetCluster(ctx context.Context, request *api.GetClusterRequest) (*api.GetClusterResponse, error) {
  70. if request.ClusterID == "" {
  71. return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  72. }
  73. var cluster *api.Cluster
  74. s.store.View(func(tx store.ReadTx) {
  75. cluster = store.GetCluster(tx, request.ClusterID)
  76. })
  77. if cluster == nil {
  78. return nil, status.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID)
  79. }
  80. redactedClusters := redactClusters([]*api.Cluster{cluster})
  81. // WARN: we should never return cluster here. We need to redact the private fields first.
  82. return &api.GetClusterResponse{
  83. Cluster: redactedClusters[0],
  84. }, nil
  85. }
  86. // UpdateCluster updates a Cluster referenced by ClusterID with the given ClusterSpec.
  87. // - Returns `NotFound` if the Cluster is not found.
  88. // - Returns `InvalidArgument` if the ClusterSpec is malformed.
  89. // - Returns `Unimplemented` if the ClusterSpec references unimplemented features.
  90. // - Returns an error if the update fails.
  91. func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRequest) (*api.UpdateClusterResponse, error) {
  92. if request.ClusterID == "" || request.ClusterVersion == nil {
  93. return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
  94. }
  95. if err := validateClusterSpec(request.Spec); err != nil {
  96. return nil, err
  97. }
  98. var cluster *api.Cluster
  99. err := s.store.Update(func(tx store.Tx) error {
  100. cluster = store.GetCluster(tx, request.ClusterID)
  101. if cluster == nil {
  102. return status.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID)
  103. }
  104. // This ensures that we have the current rootCA with which to generate tokens (expiration doesn't matter
  105. // for generating the tokens)
  106. rootCA, err := ca.RootCAFromAPI(ctx, &cluster.RootCA, ca.DefaultNodeCertExpiration)
  107. if err != nil {
  108. log.G(ctx).WithField(
  109. "method", "(*controlapi.Server).UpdateCluster").WithError(err).Error("invalid cluster root CA")
  110. return status.Errorf(codes.Internal, "error loading cluster rootCA for update")
  111. }
  112. cluster.Meta.Version = *request.ClusterVersion
  113. cluster.Spec = *request.Spec.Copy()
  114. expireBlacklistedCerts(cluster)
  115. if request.Rotation.WorkerJoinToken {
  116. cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(&rootCA, cluster.FIPS)
  117. }
  118. if request.Rotation.ManagerJoinToken {
  119. cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(&rootCA, cluster.FIPS)
  120. }
  121. updatedRootCA, err := validateCAConfig(ctx, s.securityConfig, cluster)
  122. if err != nil {
  123. return err
  124. }
  125. cluster.RootCA = *updatedRootCA
  126. var unlockKeys []*api.EncryptionKey
  127. var managerKey *api.EncryptionKey
  128. for _, eKey := range cluster.UnlockKeys {
  129. if eKey.Subsystem == ca.ManagerRole {
  130. if !cluster.Spec.EncryptionConfig.AutoLockManagers {
  131. continue
  132. }
  133. managerKey = eKey
  134. }
  135. unlockKeys = append(unlockKeys, eKey)
  136. }
  137. switch {
  138. case !cluster.Spec.EncryptionConfig.AutoLockManagers:
  139. break
  140. case managerKey == nil:
  141. unlockKeys = append(unlockKeys, &api.EncryptionKey{
  142. Subsystem: ca.ManagerRole,
  143. Key: encryption.GenerateSecretKey(),
  144. })
  145. case request.Rotation.ManagerUnlockKey:
  146. managerKey.Key = encryption.GenerateSecretKey()
  147. }
  148. cluster.UnlockKeys = unlockKeys
  149. return store.UpdateCluster(tx, cluster)
  150. })
  151. if err != nil {
  152. return nil, err
  153. }
  154. redactedClusters := redactClusters([]*api.Cluster{cluster})
  155. // WARN: we should never return cluster here. We need to redact the private fields first.
  156. return &api.UpdateClusterResponse{
  157. Cluster: redactedClusters[0],
  158. }, nil
  159. }
  160. func filterClusters(candidates []*api.Cluster, filters ...func(*api.Cluster) bool) []*api.Cluster {
  161. result := []*api.Cluster{}
  162. for _, c := range candidates {
  163. match := true
  164. for _, f := range filters {
  165. if !f(c) {
  166. match = false
  167. break
  168. }
  169. }
  170. if match {
  171. result = append(result, c)
  172. }
  173. }
  174. return result
  175. }
  176. // ListClusters returns a list of all clusters.
  177. func (s *Server) ListClusters(ctx context.Context, request *api.ListClustersRequest) (*api.ListClustersResponse, error) {
  178. var (
  179. clusters []*api.Cluster
  180. err error
  181. )
  182. s.store.View(func(tx store.ReadTx) {
  183. switch {
  184. case request.Filters != nil && len(request.Filters.Names) > 0:
  185. clusters, err = store.FindClusters(tx, buildFilters(store.ByName, request.Filters.Names))
  186. case request.Filters != nil && len(request.Filters.NamePrefixes) > 0:
  187. clusters, err = store.FindClusters(tx, buildFilters(store.ByNamePrefix, request.Filters.NamePrefixes))
  188. case request.Filters != nil && len(request.Filters.IDPrefixes) > 0:
  189. clusters, err = store.FindClusters(tx, buildFilters(store.ByIDPrefix, request.Filters.IDPrefixes))
  190. default:
  191. clusters, err = store.FindClusters(tx, store.All)
  192. }
  193. })
  194. if err != nil {
  195. return nil, err
  196. }
  197. if request.Filters != nil {
  198. clusters = filterClusters(clusters,
  199. func(e *api.Cluster) bool {
  200. return filterContains(e.Spec.Annotations.Name, request.Filters.Names)
  201. },
  202. func(e *api.Cluster) bool {
  203. return filterContainsPrefix(e.Spec.Annotations.Name, request.Filters.NamePrefixes)
  204. },
  205. func(e *api.Cluster) bool {
  206. return filterContainsPrefix(e.ID, request.Filters.IDPrefixes)
  207. },
  208. func(e *api.Cluster) bool {
  209. return filterMatchLabels(e.Spec.Annotations.Labels, request.Filters.Labels)
  210. },
  211. )
  212. }
  213. // WARN: we should never return cluster here. We need to redact the private fields first.
  214. return &api.ListClustersResponse{
  215. Clusters: redactClusters(clusters),
  216. }, nil
  217. }
  218. // redactClusters is a method that enforces a whitelist of fields that are ok to be
  219. // returned in the Cluster object. It should filter out all sensitive information.
  220. func redactClusters(clusters []*api.Cluster) []*api.Cluster {
  221. var redactedClusters []*api.Cluster
  222. // Only add public fields to the new clusters
  223. for _, cluster := range clusters {
  224. // Copy all the mandatory fields
  225. // Do not copy secret keys
  226. redactedSpec := cluster.Spec.Copy()
  227. redactedSpec.CAConfig.SigningCAKey = nil
  228. // the cert is not a secret, but if API users get the cluster spec and then update,
  229. // then because the cert is included but not the key, the user can get update errors
  230. // or unintended consequences (such as telling swarm to forget about the key so long
  231. // as there is a corresponding external CA)
  232. redactedSpec.CAConfig.SigningCACert = nil
  233. redactedRootCA := cluster.RootCA.Copy()
  234. redactedRootCA.CAKey = nil
  235. if r := redactedRootCA.RootRotation; r != nil {
  236. r.CAKey = nil
  237. }
  238. newCluster := &api.Cluster{
  239. ID: cluster.ID,
  240. Meta: cluster.Meta,
  241. Spec: *redactedSpec,
  242. RootCA: *redactedRootCA,
  243. BlacklistedCertificates: cluster.BlacklistedCertificates,
  244. DefaultAddressPool: cluster.DefaultAddressPool,
  245. SubnetSize: cluster.SubnetSize,
  246. VXLANUDPPort: cluster.VXLANUDPPort,
  247. }
  248. if newCluster.DefaultAddressPool == nil {
  249. // This is just for CLI display. Set the inbuilt default pool for
  250. // user reference.
  251. newCluster.DefaultAddressPool = inbuiltDefaultAddressPool
  252. newCluster.SubnetSize = inbuiltSubnetSize
  253. }
  254. if newCluster.VXLANUDPPort == 0 {
  255. newCluster.VXLANUDPPort = defaultVXLANPort
  256. }
  257. redactedClusters = append(redactedClusters, newCluster)
  258. }
  259. return redactedClusters
  260. }
  261. func expireBlacklistedCerts(cluster *api.Cluster) {
  262. nowMinusGrace := time.Now().Add(-expiredCertGrace)
  263. for cn, blacklistedCert := range cluster.BlacklistedCertificates {
  264. if blacklistedCert.Expiry == nil {
  265. continue
  266. }
  267. expiry, err := gogotypes.TimestampFromProto(blacklistedCert.Expiry)
  268. if err == nil && nowMinusGrace.After(expiry) {
  269. delete(cluster.BlacklistedCertificates, cn)
  270. }
  271. }
  272. }