forked from kubernetes/kops
-
Notifications
You must be signed in to change notification settings - Fork 0
/
admission.go
217 lines (174 loc) · 6 KB
/
admission.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"context"
"fmt"
"time"
"k8s.io/kops/node-authorizer/pkg/utils"
"go.uber.org/zap"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
// CheckRegistration indicates we should validate the node is not regestered
CheckRegistration = "verify-registration"
)
// authorizeNodeRequest is responsible for handling the incoming authorization request
func (n *NodeAuthorizer) authorizeNodeRequest(ctx context.Context, request *NodeRegistration) error {
doneCh := make(chan error, 0)
// @step: create a context to run under
ctx, cancel := context.WithTimeout(ctx, n.config.AuthorizationTimeout)
defer cancel()
// @step: background the request and wait for either a timeout or a token
go func() {
doneCh <- func() error {
// @step: check if the node request is authorized
if err := n.safelyAuthorizeNode(ctx, request); err != nil {
return err
}
if request.IsAllowed() {
return n.safelyProvisionBootstrapToken(ctx, request)
}
return nil
}()
}()
// @step: we either wait for the context to timeout or cancel, or we receive a done signal
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil {
utils.Logger.Error("operation has either timed out or been cancelled",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName),
zap.Error(err))
}
return nil
case err := <-doneCh:
if err != nil {
utils.Logger.Error("failed to provision a bootstrap token",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName),
zap.Error(err))
}
}
if !request.IsAllowed() {
utils.Logger.Error("the node has been denied authorization",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName),
zap.String("reason", request.Status.Reason))
nodeAuthorizationMetric.WithLabelValues("denied").Inc()
return nil
}
utils.Logger.Info("node has been authorized access",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName))
nodeAuthorizationMetric.WithLabelValues("allowed").Inc()
return nil
}
// safelyAuthorizeNode checks if the request is permitted
func (n *NodeAuthorizer) safelyAuthorizeNode(ctx context.Context, request *NodeRegistration) error {
// @step: attempt to authorize the request
now := time.Now()
if err := n.authorizer.Authorize(ctx, request); err != nil {
authorizerErrorMetric.Inc()
return err
}
authorizerLatencyMetric.Observe(time.Since(now).Seconds())
// @check if the node is registered already
if n.config.UseFeature(CheckRegistration) {
if found, err := isNodeRegistered(ctx, n.client, request.Spec.NodeName); err != nil {
return fmt.Errorf("unable to check node registration status: %s", err)
} else if found {
request.Deny(fmt.Sprintf("node %s already registered", request.Spec.NodeName))
}
}
return nil
}
// safelyProvisionBootstrapToken is responsible for generating a bootstrap token for us
func (n *NodeAuthorizer) safelyProvisionBootstrapToken(ctx context.Context, request *NodeRegistration) error {
maxInterval := 500 * time.Millisecond
maxTime := 10 * time.Second
usages := []string{"authentication", "signing"}
now := time.Now()
if err := utils.Retry(ctx, maxInterval, maxTime, func() error {
token, err := n.createToken(n.config.TokenDuration, usages)
if err != nil {
return err
}
request.Status.Token = token.String()
return err
}); err != nil {
return err
}
tokenLatencyMetric.Observe(time.Since(now).Seconds())
return nil
}
// createToken generates a token for the instance
func (n *NodeAuthorizer) createToken(expiration time.Duration, usages []string) (*Token, error) {
var err error
var token *Token
err = utils.Retry(context.TODO(), 2000*time.Millisecond, 10*time.Second, func() error {
// @step: generate a random token for them
if token, err = NewToken(); err != nil {
return err
}
// @step: check if the token already exist, remote but a possibility
if found, err := n.hasToken(token); err != nil {
return err
} else if found {
return fmt.Errorf("duplicate token found: %s, skipping", token.ID)
}
// @step: add the secret to the namespace
v1secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: token.Name(),
Labels: map[string]string{
"name": token.Name(),
},
},
Type: v1.SecretType(secretTypeBootstrapToken),
Data: encodeTokenSecretData(token, usages, expiration),
}
if _, err := n.client.CoreV1().Secrets(tokenNamespace).Create(v1secret); err != nil {
return err
}
return nil
})
return token, err
}
// hasToken checks if the tokens already exists
func (n *NodeAuthorizer) hasToken(token *Token) (bool, error) {
resp, err := n.client.CoreV1().Secrets(tokenNamespace).List(metav1.ListOptions{
LabelSelector: "name=" + token.Name(),
Limit: 1,
})
if err != nil {
return false, err
}
return len(resp.Items) > 0, nil
}
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
func encodeTokenSecretData(token *Token, usages []string, ttl time.Duration) map[string][]byte {
data := map[string][]byte{
bootstrapTokenIDKey: []byte(token.ID),
bootstrapTokenSecretKey: []byte(token.Secret),
}
if ttl > 0 {
expire := time.Now().Add(ttl).Format(time.RFC3339)
data[bootstrapTokenExpirationKey] = []byte(expire)
}
for _, usage := range usages {
data[bootstrapTokenUsagePrefix+usage] = []byte("true")
}
return data
}