1
16
17 package v1
18
19 import (
20 "fmt"
21 "sort"
22 "strings"
23 "time"
24
25 "github.com/pkg/errors"
26
27 v1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 bootstrapapi "k8s.io/cluster-bootstrap/token/api"
30 bootstraputil "k8s.io/cluster-bootstrap/token/util"
31 bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
32 )
33
34 const (
35
36
37
38
39
40
41
42
43 validatedSubstringsSize = 3
44 )
45
46
47 func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) {
48 return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil
49 }
50
51
52 func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error {
53
54 if len(b) == 0 {
55 return nil
56 }
57
58
59 token := strings.Replace(string(b), `"`, ``, -1)
60
61 newbts, err := NewBootstrapTokenString(token)
62 if err != nil {
63 return err
64 }
65 bts.ID = newbts.ID
66 bts.Secret = newbts.Secret
67 return nil
68 }
69
70
71 func (bts BootstrapTokenString) String() string {
72 if len(bts.ID) > 0 && len(bts.Secret) > 0 {
73 return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret)
74 }
75 return ""
76 }
77
78
79
80
81
82 func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) {
83 substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
84 if len(substrs) != validatedSubstringsSize {
85 return nil, errors.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
86 }
87
88 return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
89 }
90
91
92
93 func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
94 return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
95 }
96
97
98
99 func BootstrapTokenToSecret(bt *BootstrapToken) *v1.Secret {
100 return &v1.Secret{
101 ObjectMeta: metav1.ObjectMeta{
102 Name: bootstraputil.BootstrapTokenSecretName(bt.Token.ID),
103 Namespace: metav1.NamespaceSystem,
104 },
105 Type: bootstrapapi.SecretTypeBootstrapToken,
106 Data: encodeTokenSecretData(bt, time.Now()),
107 }
108 }
109
110
111
112 func encodeTokenSecretData(token *BootstrapToken, now time.Time) map[string][]byte {
113 data := map[string][]byte{
114 bootstrapapi.BootstrapTokenIDKey: []byte(token.Token.ID),
115 bootstrapapi.BootstrapTokenSecretKey: []byte(token.Token.Secret),
116 }
117
118 if len(token.Description) > 0 {
119 data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(token.Description)
120 }
121
122
123
124
125 if token.Expires != nil {
126
127
128 expirationString := token.Expires.Time.UTC().Format(time.RFC3339)
129 data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString)
130
131 } else if token.TTL != nil && token.TTL.Duration > 0 {
132
133
134 expirationString := now.Add(token.TTL.Duration).UTC().Format(time.RFC3339)
135 data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString)
136 }
137
138 for _, usage := range token.Usages {
139 data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true")
140 }
141
142 if len(token.Groups) > 0 {
143 data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(token.Groups, ","))
144 }
145 return data
146 }
147
148
149 func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
150
151 tokenID := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey)
152 if len(tokenID) == 0 {
153 return nil, errors.Errorf("bootstrap Token Secret has no token-id data: %s", secret.Name)
154 }
155
156
157 if secret.Name != bootstraputil.BootstrapTokenSecretName(tokenID) {
158 return nil, errors.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q",
159 bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID))
160 }
161
162 tokenSecret := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
163 if len(tokenSecret) == 0 {
164 return nil, errors.Errorf("bootstrap Token Secret has no token-secret data: %s", secret.Name)
165 }
166
167
168 bts, err := NewBootstrapTokenStringFromIDAndSecret(tokenID, tokenSecret)
169 if err != nil {
170 return nil, errors.Wrap(err, "bootstrap Token Secret is invalid and couldn't be parsed")
171 }
172
173
174 description := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenDescriptionKey)
175
176
177
178 secretExpiration := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenExpirationKey)
179 var expires *metav1.Time
180 if len(secretExpiration) > 0 {
181 expTime, err := time.Parse(time.RFC3339, secretExpiration)
182 if err != nil {
183 return nil, errors.Wrapf(err, "can't parse expiration time of bootstrap token %q", secret.Name)
184 }
185 expires = &metav1.Time{Time: expTime}
186 }
187
188
189 var usages []string
190 for k, v := range secret.Data {
191
192 if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) {
193 continue
194 }
195
196 if string(v) != "true" {
197 continue
198 }
199 usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix))
200 }
201
202 if usages != nil {
203 sort.Strings(usages)
204 }
205
206
207
208
209 var groups []string
210 groupsString := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
211 g := strings.Split(groupsString, ",")
212 if len(g) > 0 && len(g[0]) > 0 {
213 groups = g
214 }
215
216 return &BootstrapToken{
217 Token: bts,
218 Description: description,
219 Expires: expires,
220 Usages: usages,
221 Groups: groups,
222 }, nil
223 }
224
View as plain text