bootstrap.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. /*
  14. Package bootstrap provides a token authenticator for TLS bootstrap secrets.
  15. */
  16. package bootstrap
  17. import (
  18. "context"
  19. "crypto/subtle"
  20. "fmt"
  21. "regexp"
  22. "strings"
  23. "time"
  24. "k8s.io/klog"
  25. corev1 "k8s.io/api/core/v1"
  26. "k8s.io/apimachinery/pkg/api/errors"
  27. "k8s.io/apimachinery/pkg/util/sets"
  28. "k8s.io/apiserver/pkg/authentication/authenticator"
  29. "k8s.io/apiserver/pkg/authentication/user"
  30. corev1listers "k8s.io/client-go/listers/core/v1"
  31. bootstrapapi "k8s.io/cluster-bootstrap/token/api"
  32. bootstraputil "k8s.io/cluster-bootstrap/token/util"
  33. )
  34. // TODO: A few methods in this package is copied from other sources. Either
  35. // because the existing functionality isn't exported or because it is in a
  36. // package that shouldn't be directly imported by this packages.
  37. // NewTokenAuthenticator initializes a bootstrap token authenticator.
  38. //
  39. // Lister is expected to be for the "kube-system" namespace.
  40. func NewTokenAuthenticator(lister corev1listers.SecretNamespaceLister) *TokenAuthenticator {
  41. return &TokenAuthenticator{lister}
  42. }
  43. // TokenAuthenticator authenticates bootstrap tokens from secrets in the API server.
  44. type TokenAuthenticator struct {
  45. lister corev1listers.SecretNamespaceLister
  46. }
  47. // tokenErrorf prints a error message for a secret that has matched a bearer
  48. // token but fails to meet some other criteria.
  49. //
  50. // tokenErrorf(secret, "has invalid value for key %s", key)
  51. //
  52. func tokenErrorf(s *corev1.Secret, format string, i ...interface{}) {
  53. format = fmt.Sprintf("Bootstrap secret %s/%s matching bearer token ", s.Namespace, s.Name) + format
  54. klog.V(3).Infof(format, i...)
  55. }
  56. // AuthenticateToken tries to match the provided token to a bootstrap token secret
  57. // in a given namespace. If found, it authenticates the token in the
  58. // "system:bootstrappers" group and with the "system:bootstrap:(token-id)" username.
  59. //
  60. // All secrets must be of type "bootstrap.kubernetes.io/token". An example secret:
  61. //
  62. // apiVersion: v1
  63. // kind: Secret
  64. // metadata:
  65. // # Name MUST be of form "bootstrap-token-( token id )".
  66. // name: bootstrap-token-( token id )
  67. // namespace: kube-system
  68. // # Only secrets of this type will be evaluated.
  69. // type: bootstrap.kubernetes.io/token
  70. // data:
  71. // token-secret: ( private part of token )
  72. // token-id: ( token id )
  73. // # Required key usage.
  74. // usage-bootstrap-authentication: true
  75. // auth-extra-groups: "system:bootstrappers:custom-group1,system:bootstrappers:custom-group2"
  76. // # May also contain an expiry.
  77. //
  78. // Tokens are expected to be of the form:
  79. //
  80. // ( token-id ).( token-secret )
  81. //
  82. func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
  83. tokenID, tokenSecret, err := parseToken(token)
  84. if err != nil {
  85. // Token isn't of the correct form, ignore it.
  86. return nil, false, nil
  87. }
  88. secretName := bootstrapapi.BootstrapTokenSecretPrefix + tokenID
  89. secret, err := t.lister.Get(secretName)
  90. if err != nil {
  91. if errors.IsNotFound(err) {
  92. klog.V(3).Infof("No secret of name %s to match bootstrap bearer token", secretName)
  93. return nil, false, nil
  94. }
  95. return nil, false, err
  96. }
  97. if secret.DeletionTimestamp != nil {
  98. tokenErrorf(secret, "is deleted and awaiting removal")
  99. return nil, false, nil
  100. }
  101. if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
  102. tokenErrorf(secret, "has invalid type, expected %s.", bootstrapapi.SecretTypeBootstrapToken)
  103. return nil, false, nil
  104. }
  105. ts := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
  106. if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
  107. tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenSecretKey, tokenSecret)
  108. return nil, false, nil
  109. }
  110. id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
  111. if id != tokenID {
  112. tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenIDKey, tokenID)
  113. return nil, false, nil
  114. }
  115. if isSecretExpired(secret) {
  116. // logging done in isSecretExpired method.
  117. return nil, false, nil
  118. }
  119. if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
  120. tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
  121. return nil, false, nil
  122. }
  123. groups, err := getGroups(secret)
  124. if err != nil {
  125. tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
  126. return nil, false, nil
  127. }
  128. return &authenticator.Response{
  129. User: &user.DefaultInfo{
  130. Name: bootstrapapi.BootstrapUserPrefix + string(id),
  131. Groups: groups,
  132. },
  133. }, true, nil
  134. }
  135. // Copied from k8s.io/cluster-bootstrap/token/api
  136. func getSecretString(secret *corev1.Secret, key string) string {
  137. data, ok := secret.Data[key]
  138. if !ok {
  139. return ""
  140. }
  141. return string(data)
  142. }
  143. // Copied from k8s.io/cluster-bootstrap/token/api
  144. func isSecretExpired(secret *corev1.Secret) bool {
  145. expiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
  146. if len(expiration) > 0 {
  147. expTime, err2 := time.Parse(time.RFC3339, expiration)
  148. if err2 != nil {
  149. klog.V(3).Infof("Unparseable expiration time (%s) in %s/%s Secret: %v. Treating as expired.",
  150. expiration, secret.Namespace, secret.Name, err2)
  151. return true
  152. }
  153. if time.Now().After(expTime) {
  154. klog.V(3).Infof("Expired bootstrap token in %s/%s Secret: %v",
  155. secret.Namespace, secret.Name, expiration)
  156. return true
  157. }
  158. }
  159. return false
  160. }
  161. // Copied from kubernetes/cmd/kubeadm/app/util/token
  162. var (
  163. // tokenRegexpString defines id.secret regular expression pattern
  164. tokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$"
  165. // tokenRegexp is a compiled regular expression of TokenRegexpString
  166. tokenRegexp = regexp.MustCompile(tokenRegexpString)
  167. )
  168. // parseToken tries and parse a valid token from a string.
  169. // A token ID and token secret are returned in case of success, an error otherwise.
  170. func parseToken(s string) (string, string, error) {
  171. split := tokenRegexp.FindStringSubmatch(s)
  172. if len(split) != 3 {
  173. return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString)
  174. }
  175. return split[1], split[2], nil
  176. }
  177. // getGroups loads and validates the bootstrapapi.BootstrapTokenExtraGroupsKey
  178. // key from the bootstrap token secret, returning a list of group names or an
  179. // error if any of the group names are invalid.
  180. func getGroups(secret *corev1.Secret) ([]string, error) {
  181. // always include the default group
  182. groups := sets.NewString(bootstrapapi.BootstrapDefaultGroup)
  183. // grab any extra groups and if there are none, return just the default
  184. extraGroupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
  185. if extraGroupsString == "" {
  186. return groups.List(), nil
  187. }
  188. // validate the names of the extra groups
  189. for _, group := range strings.Split(extraGroupsString, ",") {
  190. if err := bootstraputil.ValidateBootstrapGroupName(group); err != nil {
  191. return nil, err
  192. }
  193. groups.Insert(group)
  194. }
  195. // return the result as a deduplicated, sorted list
  196. return groups.List(), nil
  197. }