config.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. package ca
  2. import (
  3. cryptorand "crypto/rand"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "fmt"
  7. "math/big"
  8. "math/rand"
  9. "path/filepath"
  10. "strings"
  11. "sync"
  12. "time"
  13. "github.com/Sirupsen/logrus"
  14. cfconfig "github.com/cloudflare/cfssl/config"
  15. "github.com/docker/swarmkit/api"
  16. "github.com/docker/swarmkit/connectionbroker"
  17. "github.com/docker/swarmkit/identity"
  18. "github.com/docker/swarmkit/log"
  19. "github.com/docker/swarmkit/watch"
  20. "github.com/opencontainers/go-digest"
  21. "github.com/pkg/errors"
  22. "google.golang.org/grpc/credentials"
  23. "golang.org/x/net/context"
  24. )
  25. const (
  26. rootCACertFilename = "swarm-root-ca.crt"
  27. rootCAKeyFilename = "swarm-root-ca.key"
  28. nodeTLSCertFilename = "swarm-node.crt"
  29. nodeTLSKeyFilename = "swarm-node.key"
  30. nodeCSRFilename = "swarm-node.csr"
  31. // DefaultRootCN represents the root CN that we should create roots CAs with by default
  32. DefaultRootCN = "swarm-ca"
  33. // ManagerRole represents the Manager node type, and is used for authorization to endpoints
  34. ManagerRole = "swarm-manager"
  35. // WorkerRole represents the Worker node type, and is used for authorization to endpoints
  36. WorkerRole = "swarm-worker"
  37. // CARole represents the CA node type, and is used for clients attempting to get new certificates issued
  38. CARole = "swarm-ca"
  39. generatedSecretEntropyBytes = 16
  40. joinTokenBase = 36
  41. // ceil(log(2^128-1, 36))
  42. maxGeneratedSecretLength = 25
  43. // ceil(log(2^256-1, 36))
  44. base36DigestLen = 50
  45. )
  46. var (
  47. // GetCertRetryInterval is how long to wait before retrying a node
  48. // certificate or root certificate request.
  49. GetCertRetryInterval = 2 * time.Second
  50. )
  51. // SecurityConfig is used to represent a node's security configuration. It includes information about
  52. // the RootCA and ServerTLSCreds/ClientTLSCreds transport authenticators to be used for MTLS
  53. type SecurityConfig struct {
  54. // mu protects against concurrent access to fields inside the structure.
  55. mu sync.Mutex
  56. // renewalMu makes sure only one certificate renewal attempt happens at
  57. // a time. It should never be locked after mu is already locked.
  58. renewalMu sync.Mutex
  59. rootCA *RootCA
  60. externalCA *ExternalCA
  61. keyReadWriter *KeyReadWriter
  62. certificate *tls.Certificate
  63. issuerInfo *IssuerInfo
  64. externalCAClientRootPool *x509.CertPool
  65. ServerTLSCreds *MutableTLSCreds
  66. ClientTLSCreds *MutableTLSCreds
  67. // An optional queue for anyone interested in subscribing to SecurityConfig updates
  68. queue *watch.Queue
  69. }
  70. // CertificateUpdate represents a change in the underlying TLS configuration being returned by
  71. // a certificate renewal event.
  72. type CertificateUpdate struct {
  73. Role string
  74. Err error
  75. }
  76. // NewSecurityConfig initializes and returns a new SecurityConfig.
  77. func NewSecurityConfig(rootCA *RootCA, krw *KeyReadWriter, tlsKeyPair *tls.Certificate, issuerInfo *IssuerInfo) (*SecurityConfig, error) {
  78. // Create the Server TLS Credentials for this node. These will not be used by workers.
  79. serverTLSCreds, err := rootCA.NewServerTLSCredentials(tlsKeyPair)
  80. if err != nil {
  81. return nil, err
  82. }
  83. // Create a TLSConfig to be used when this node connects as a client to another remote node.
  84. // We're using ManagerRole as remote serverName for TLS host verification because both workers
  85. // and managers always connect to remote managers.
  86. clientTLSCreds, err := rootCA.NewClientTLSCredentials(tlsKeyPair, ManagerRole)
  87. if err != nil {
  88. return nil, err
  89. }
  90. // Make a new TLS config for the external CA client without a
  91. // ServerName value set.
  92. externalCATLSConfig := &tls.Config{
  93. Certificates: []tls.Certificate{*tlsKeyPair},
  94. RootCAs: rootCA.Pool,
  95. MinVersion: tls.VersionTLS12,
  96. }
  97. return &SecurityConfig{
  98. rootCA: rootCA,
  99. keyReadWriter: krw,
  100. certificate: tlsKeyPair,
  101. issuerInfo: issuerInfo,
  102. externalCA: NewExternalCA(rootCA, externalCATLSConfig),
  103. ClientTLSCreds: clientTLSCreds,
  104. ServerTLSCreds: serverTLSCreds,
  105. externalCAClientRootPool: rootCA.Pool,
  106. }, nil
  107. }
  108. // RootCA returns the root CA.
  109. func (s *SecurityConfig) RootCA() *RootCA {
  110. s.mu.Lock()
  111. defer s.mu.Unlock()
  112. return s.rootCA
  113. }
  114. // ExternalCA returns the external CA.
  115. func (s *SecurityConfig) ExternalCA() *ExternalCA {
  116. return s.externalCA
  117. }
  118. // KeyWriter returns the object that can write keys to disk
  119. func (s *SecurityConfig) KeyWriter() KeyWriter {
  120. return s.keyReadWriter
  121. }
  122. // KeyReader returns the object that can read keys from disk
  123. func (s *SecurityConfig) KeyReader() KeyReader {
  124. return s.keyReadWriter
  125. }
  126. // UpdateRootCA replaces the root CA with a new root CA
  127. func (s *SecurityConfig) UpdateRootCA(rootCA *RootCA, externalCARootPool *x509.CertPool) error {
  128. s.mu.Lock()
  129. defer s.mu.Unlock()
  130. s.rootCA = rootCA
  131. s.externalCAClientRootPool = externalCARootPool
  132. return s.updateTLSCredentials(s.certificate, s.issuerInfo)
  133. }
  134. // SetWatch allows you to set a watch on the security config, in order to be notified of any changes
  135. func (s *SecurityConfig) SetWatch(q *watch.Queue) {
  136. s.mu.Lock()
  137. defer s.mu.Unlock()
  138. s.queue = q
  139. }
  140. // IssuerInfo returns the issuer subject and issuer public key
  141. func (s *SecurityConfig) IssuerInfo() *IssuerInfo {
  142. s.mu.Lock()
  143. defer s.mu.Unlock()
  144. return s.issuerInfo
  145. }
  146. // This function expects something else to have taken out a lock on the SecurityConfig.
  147. func (s *SecurityConfig) updateTLSCredentials(certificate *tls.Certificate, issuerInfo *IssuerInfo) error {
  148. certs := []tls.Certificate{*certificate}
  149. clientConfig, err := NewClientTLSConfig(certs, s.rootCA.Pool, ManagerRole)
  150. if err != nil {
  151. return errors.Wrap(err, "failed to create a new client config using the new root CA")
  152. }
  153. serverConfig, err := NewServerTLSConfig(certs, s.rootCA.Pool)
  154. if err != nil {
  155. return errors.Wrap(err, "failed to create a new server config using the new root CA")
  156. }
  157. if err := s.ClientTLSCreds.loadNewTLSConfig(clientConfig); err != nil {
  158. return errors.Wrap(err, "failed to update the client credentials")
  159. }
  160. // Update the external CA to use the new client TLS
  161. // config using a copy without a serverName specified.
  162. s.externalCA.UpdateTLSConfig(&tls.Config{
  163. Certificates: certs,
  164. RootCAs: s.externalCAClientRootPool,
  165. MinVersion: tls.VersionTLS12,
  166. })
  167. if err := s.ServerTLSCreds.loadNewTLSConfig(serverConfig); err != nil {
  168. return errors.Wrap(err, "failed to update the server TLS credentials")
  169. }
  170. s.certificate = certificate
  171. s.issuerInfo = issuerInfo
  172. if s.queue != nil {
  173. s.queue.Publish(&api.NodeTLSInfo{
  174. TrustRoot: s.rootCA.Certs,
  175. CertIssuerPublicKey: s.issuerInfo.PublicKey,
  176. CertIssuerSubject: s.issuerInfo.Subject,
  177. })
  178. }
  179. return nil
  180. }
  181. // SigningPolicy creates a policy used by the signer to ensure that the only fields
  182. // from the remote CSRs we trust are: PublicKey, PublicKeyAlgorithm and SignatureAlgorithm.
  183. // It receives the duration a certificate will be valid for
  184. func SigningPolicy(certExpiry time.Duration) *cfconfig.Signing {
  185. // Force the minimum Certificate expiration to be fifteen minutes
  186. if certExpiry < MinNodeCertExpiration {
  187. certExpiry = DefaultNodeCertExpiration
  188. }
  189. // Add the backdate
  190. certExpiry = certExpiry + CertBackdate
  191. return &cfconfig.Signing{
  192. Default: &cfconfig.SigningProfile{
  193. Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
  194. Expiry: certExpiry,
  195. Backdate: CertBackdate,
  196. // Only trust the key components from the CSR. Everything else should
  197. // come directly from API call params.
  198. CSRWhitelist: &cfconfig.CSRWhitelist{
  199. PublicKey: true,
  200. PublicKeyAlgorithm: true,
  201. SignatureAlgorithm: true,
  202. },
  203. },
  204. }
  205. }
  206. // SecurityConfigPaths is used as a helper to hold all the paths of security relevant files
  207. type SecurityConfigPaths struct {
  208. Node, RootCA CertPaths
  209. }
  210. // NewConfigPaths returns the absolute paths to all of the different types of files
  211. func NewConfigPaths(baseCertDir string) *SecurityConfigPaths {
  212. return &SecurityConfigPaths{
  213. Node: CertPaths{
  214. Cert: filepath.Join(baseCertDir, nodeTLSCertFilename),
  215. Key: filepath.Join(baseCertDir, nodeTLSKeyFilename)},
  216. RootCA: CertPaths{
  217. Cert: filepath.Join(baseCertDir, rootCACertFilename),
  218. Key: filepath.Join(baseCertDir, rootCAKeyFilename)},
  219. }
  220. }
  221. // GenerateJoinToken creates a new join token.
  222. func GenerateJoinToken(rootCA *RootCA) string {
  223. var secretBytes [generatedSecretEntropyBytes]byte
  224. if _, err := cryptorand.Read(secretBytes[:]); err != nil {
  225. panic(fmt.Errorf("failed to read random bytes: %v", err))
  226. }
  227. var nn, digest big.Int
  228. nn.SetBytes(secretBytes[:])
  229. digest.SetString(rootCA.Digest.Hex(), 16)
  230. return fmt.Sprintf("SWMTKN-1-%0[1]*s-%0[3]*s", base36DigestLen, digest.Text(joinTokenBase), maxGeneratedSecretLength, nn.Text(joinTokenBase))
  231. }
  232. func getCAHashFromToken(token string) (digest.Digest, error) {
  233. split := strings.Split(token, "-")
  234. if len(split) != 4 || split[0] != "SWMTKN" || split[1] != "1" || len(split[2]) != base36DigestLen || len(split[3]) != maxGeneratedSecretLength {
  235. return "", errors.New("invalid join token")
  236. }
  237. var digestInt big.Int
  238. digestInt.SetString(split[2], joinTokenBase)
  239. return digest.Parse(fmt.Sprintf("sha256:%0[1]*s", 64, digestInt.Text(16)))
  240. }
  241. // DownloadRootCA tries to retrieve a remote root CA and matches the digest against the provided token.
  242. func DownloadRootCA(ctx context.Context, paths CertPaths, token string, connBroker *connectionbroker.Broker) (RootCA, error) {
  243. var rootCA RootCA
  244. // Get a digest for the optional CA hash string that we've been provided
  245. // If we were provided a non-empty string, and it is an invalid hash, return
  246. // otherwise, allow the invalid digest through.
  247. var (
  248. d digest.Digest
  249. err error
  250. )
  251. if token != "" {
  252. d, err = getCAHashFromToken(token)
  253. if err != nil {
  254. return RootCA{}, err
  255. }
  256. }
  257. // Get the remote CA certificate, verify integrity with the
  258. // hash provided. Retry up to 5 times, in case the manager we
  259. // first try to contact is not responding properly (it may have
  260. // just been demoted, for example).
  261. for i := 0; i != 5; i++ {
  262. rootCA, err = GetRemoteCA(ctx, d, connBroker)
  263. if err == nil {
  264. break
  265. }
  266. log.G(ctx).WithError(err).Errorf("failed to retrieve remote root CA certificate")
  267. select {
  268. case <-time.After(GetCertRetryInterval):
  269. case <-ctx.Done():
  270. return RootCA{}, ctx.Err()
  271. }
  272. }
  273. if err != nil {
  274. return RootCA{}, err
  275. }
  276. // Save root CA certificate to disk
  277. if err = SaveRootCA(rootCA, paths); err != nil {
  278. return RootCA{}, err
  279. }
  280. log.G(ctx).Debugf("retrieved remote CA certificate: %s", paths.Cert)
  281. return rootCA, nil
  282. }
  283. // LoadSecurityConfig loads TLS credentials from disk, or returns an error if
  284. // these credentials do not exist or are unusable.
  285. func LoadSecurityConfig(ctx context.Context, rootCA RootCA, krw *KeyReadWriter, allowExpired bool) (*SecurityConfig, error) {
  286. ctx = log.WithModule(ctx, "tls")
  287. // At this point we've successfully loaded the CA details from disk, or
  288. // successfully downloaded them remotely. The next step is to try to
  289. // load our certificates.
  290. // Read both the Cert and Key from disk
  291. cert, key, err := krw.Read()
  292. if err != nil {
  293. return nil, err
  294. }
  295. // Check to see if this certificate was signed by our CA, and isn't expired
  296. _, chains, err := ValidateCertChain(rootCA.Pool, cert, allowExpired)
  297. if err != nil {
  298. return nil, err
  299. }
  300. // ValidateChain, if successful, will always return at least 1 chain containing
  301. // at least 2 certificates: the leaf and the root.
  302. issuer := chains[0][1]
  303. // Now that we know this certificate is valid, create a TLS Certificate for our
  304. // credentials
  305. keyPair, err := tls.X509KeyPair(cert, key)
  306. if err != nil {
  307. return nil, err
  308. }
  309. secConfig, err := NewSecurityConfig(&rootCA, krw, &keyPair, &IssuerInfo{
  310. Subject: issuer.RawSubject,
  311. PublicKey: issuer.RawSubjectPublicKeyInfo,
  312. })
  313. if err == nil {
  314. log.G(ctx).WithFields(logrus.Fields{
  315. "node.id": secConfig.ClientTLSCreds.NodeID(),
  316. "node.role": secConfig.ClientTLSCreds.Role(),
  317. }).Debug("loaded node credentials")
  318. }
  319. return secConfig, err
  320. }
  321. // CertificateRequestConfig contains the information needed to request a
  322. // certificate from a remote CA.
  323. type CertificateRequestConfig struct {
  324. // Token is the join token that authenticates us with the CA.
  325. Token string
  326. // Availability allows a user to control the current scheduling status of a node
  327. Availability api.NodeSpec_Availability
  328. // ConnBroker provides connections to CAs.
  329. ConnBroker *connectionbroker.Broker
  330. // Credentials provides transport credentials for communicating with the
  331. // remote server.
  332. Credentials credentials.TransportCredentials
  333. // ForceRemote specifies that only a remote (TCP) connection should
  334. // be used to request the certificate. This may be necessary in cases
  335. // where the local node is running a manager, but is in the process of
  336. // being demoted.
  337. ForceRemote bool
  338. // NodeCertificateStatusRequestTimeout determines how long to wait for a node
  339. // status RPC result. If not provided (zero value), will default to 5 seconds.
  340. NodeCertificateStatusRequestTimeout time.Duration
  341. // RetryInterval specifies how long to delay between retries, if non-zero.
  342. RetryInterval time.Duration
  343. }
  344. // CreateSecurityConfig creates a new key and cert for this node, either locally
  345. // or via a remote CA.
  346. func (rootCA RootCA) CreateSecurityConfig(ctx context.Context, krw *KeyReadWriter, config CertificateRequestConfig) (*SecurityConfig, error) {
  347. ctx = log.WithModule(ctx, "tls")
  348. // Create a new random ID for this certificate
  349. cn := identity.NewID()
  350. org := identity.NewID()
  351. proposedRole := ManagerRole
  352. tlsKeyPair, issuerInfo, err := rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org)
  353. switch errors.Cause(err) {
  354. case ErrNoValidSigner:
  355. config.RetryInterval = GetCertRetryInterval
  356. // Request certificate issuance from a remote CA.
  357. // Last argument is nil because at this point we don't have any valid TLS creds
  358. tlsKeyPair, issuerInfo, err = rootCA.RequestAndSaveNewCertificates(ctx, krw, config)
  359. if err != nil {
  360. log.G(ctx).WithError(err).Error("failed to request and save new certificate")
  361. return nil, err
  362. }
  363. case nil:
  364. log.G(ctx).WithFields(logrus.Fields{
  365. "node.id": cn,
  366. "node.role": proposedRole,
  367. }).Debug("issued new TLS certificate")
  368. default:
  369. log.G(ctx).WithFields(logrus.Fields{
  370. "node.id": cn,
  371. "node.role": proposedRole,
  372. }).WithError(err).Errorf("failed to issue and save new certificate")
  373. return nil, err
  374. }
  375. secConfig, err := NewSecurityConfig(&rootCA, krw, tlsKeyPair, issuerInfo)
  376. if err == nil {
  377. log.G(ctx).WithFields(logrus.Fields{
  378. "node.id": secConfig.ClientTLSCreds.NodeID(),
  379. "node.role": secConfig.ClientTLSCreds.Role(),
  380. }).Debugf("new node credentials generated: %s", krw.Target())
  381. }
  382. return secConfig, err
  383. }
  384. // RenewTLSConfigNow gets a new TLS cert and key, and updates the security config if provided. This is similar to
  385. // RenewTLSConfig, except while that monitors for expiry, and periodically renews, this renews once and is blocking
  386. func RenewTLSConfigNow(ctx context.Context, s *SecurityConfig, connBroker *connectionbroker.Broker) error {
  387. s.renewalMu.Lock()
  388. defer s.renewalMu.Unlock()
  389. ctx = log.WithModule(ctx, "tls")
  390. log := log.G(ctx).WithFields(logrus.Fields{
  391. "node.id": s.ClientTLSCreds.NodeID(),
  392. "node.role": s.ClientTLSCreds.Role(),
  393. })
  394. // Let's request new certs. Renewals don't require a token.
  395. rootCA := s.RootCA()
  396. tlsKeyPair, issuerInfo, err := rootCA.RequestAndSaveNewCertificates(ctx,
  397. s.KeyWriter(),
  398. CertificateRequestConfig{
  399. ConnBroker: connBroker,
  400. Credentials: s.ClientTLSCreds,
  401. })
  402. if err != nil {
  403. log.WithError(err).Errorf("failed to renew the certificate")
  404. return err
  405. }
  406. s.mu.Lock()
  407. defer s.mu.Unlock()
  408. return s.updateTLSCredentials(tlsKeyPair, issuerInfo)
  409. }
  410. // calculateRandomExpiry returns a random duration between 50% and 80% of the
  411. // original validity period
  412. func calculateRandomExpiry(validFrom, validUntil time.Time) time.Duration {
  413. duration := validUntil.Sub(validFrom)
  414. var randomExpiry int
  415. // Our lower bound of renewal will be half of the total expiration time
  416. minValidity := int(duration.Minutes() * CertLowerRotationRange)
  417. // Our upper bound of renewal will be 80% of the total expiration time
  418. maxValidity := int(duration.Minutes() * CertUpperRotationRange)
  419. // Let's select a random number of minutes between min and max, and set our retry for that
  420. // Using randomly selected rotation allows us to avoid certificate thundering herds.
  421. if maxValidity-minValidity < 1 {
  422. randomExpiry = minValidity
  423. } else {
  424. randomExpiry = rand.Intn(maxValidity-minValidity) + int(minValidity)
  425. }
  426. expiry := validFrom.Add(time.Duration(randomExpiry) * time.Minute).Sub(time.Now())
  427. if expiry < 0 {
  428. return 0
  429. }
  430. return expiry
  431. }
  432. // NewServerTLSConfig returns a tls.Config configured for a TLS Server, given a tls.Certificate
  433. // and the PEM-encoded root CA Certificate
  434. func NewServerTLSConfig(certs []tls.Certificate, rootCAPool *x509.CertPool) (*tls.Config, error) {
  435. if rootCAPool == nil {
  436. return nil, errors.New("valid root CA pool required")
  437. }
  438. return &tls.Config{
  439. Certificates: certs,
  440. // Since we're using the same CA server to issue Certificates to new nodes, we can't
  441. // use tls.RequireAndVerifyClientCert
  442. ClientAuth: tls.VerifyClientCertIfGiven,
  443. RootCAs: rootCAPool,
  444. ClientCAs: rootCAPool,
  445. PreferServerCipherSuites: true,
  446. MinVersion: tls.VersionTLS12,
  447. }, nil
  448. }
  449. // NewClientTLSConfig returns a tls.Config configured for a TLS Client, given a tls.Certificate
  450. // the PEM-encoded root CA Certificate, and the name of the remote server the client wants to connect to.
  451. func NewClientTLSConfig(certs []tls.Certificate, rootCAPool *x509.CertPool, serverName string) (*tls.Config, error) {
  452. if rootCAPool == nil {
  453. return nil, errors.New("valid root CA pool required")
  454. }
  455. return &tls.Config{
  456. ServerName: serverName,
  457. Certificates: certs,
  458. RootCAs: rootCAPool,
  459. MinVersion: tls.VersionTLS12,
  460. }, nil
  461. }
  462. // NewClientTLSCredentials returns GRPC credentials for a TLS GRPC client, given a tls.Certificate
  463. // a PEM-Encoded root CA Certificate, and the name of the remote server the client wants to connect to.
  464. func (rootCA *RootCA) NewClientTLSCredentials(cert *tls.Certificate, serverName string) (*MutableTLSCreds, error) {
  465. tlsConfig, err := NewClientTLSConfig([]tls.Certificate{*cert}, rootCA.Pool, serverName)
  466. if err != nil {
  467. return nil, err
  468. }
  469. mtls, err := NewMutableTLS(tlsConfig)
  470. return mtls, err
  471. }
  472. // NewServerTLSCredentials returns GRPC credentials for a TLS GRPC client, given a tls.Certificate
  473. // a PEM-Encoded root CA Certificate, and the name of the remote server the client wants to connect to.
  474. func (rootCA *RootCA) NewServerTLSCredentials(cert *tls.Certificate) (*MutableTLSCreds, error) {
  475. tlsConfig, err := NewServerTLSConfig([]tls.Certificate{*cert}, rootCA.Pool)
  476. if err != nil {
  477. return nil, err
  478. }
  479. mtls, err := NewMutableTLS(tlsConfig)
  480. return mtls, err
  481. }
  482. // ParseRole parses an apiRole into an internal role string
  483. func ParseRole(apiRole api.NodeRole) (string, error) {
  484. switch apiRole {
  485. case api.NodeRoleManager:
  486. return ManagerRole, nil
  487. case api.NodeRoleWorker:
  488. return WorkerRole, nil
  489. default:
  490. return "", errors.Errorf("failed to parse api role: %v", apiRole)
  491. }
  492. }
  493. // FormatRole parses an internal role string into an apiRole
  494. func FormatRole(role string) (api.NodeRole, error) {
  495. switch strings.ToLower(role) {
  496. case strings.ToLower(ManagerRole):
  497. return api.NodeRoleManager, nil
  498. case strings.ToLower(WorkerRole):
  499. return api.NodeRoleWorker, nil
  500. default:
  501. return 0, errors.Errorf("failed to parse role: %s", role)
  502. }
  503. }