1
16
17 package auth
18
19 import (
20 "bytes"
21 "fmt"
22 "io"
23 "net/http"
24 "testing"
25 "time"
26
27 corev1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/labels"
30 "k8s.io/apiserver/pkg/authentication/group"
31 "k8s.io/apiserver/pkg/authentication/request/bearertoken"
32 "k8s.io/client-go/rest"
33 bootstrapapi "k8s.io/cluster-bootstrap/token/api"
34 "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
35 "k8s.io/kubernetes/pkg/controlplane"
36 "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
37 "k8s.io/kubernetes/test/integration"
38 "k8s.io/kubernetes/test/integration/framework"
39 "k8s.io/kubernetes/test/utils/ktesting"
40 )
41
42 type bootstrapSecrets []*corev1.Secret
43
44 func (b bootstrapSecrets) List(selector labels.Selector) (ret []*corev1.Secret, err error) {
45 return b, nil
46 }
47
48 func (b bootstrapSecrets) Get(name string) (*corev1.Secret, error) {
49 return b[0], nil
50 }
51
52
53 func TestBootstrapTokenAuth(t *testing.T) {
54 validTokenID := "token1"
55 validSecret := "validtokensecret"
56 var bootstrapSecretValid = &corev1.Secret{
57 ObjectMeta: metav1.ObjectMeta{
58 Namespace: metav1.NamespaceSystem,
59 Name: bootstrapapi.BootstrapTokenSecretPrefix,
60 },
61 Type: corev1.SecretTypeBootstrapToken,
62 Data: map[string][]byte{
63 bootstrapapi.BootstrapTokenIDKey: []byte(validTokenID),
64 bootstrapapi.BootstrapTokenSecretKey: []byte(validSecret),
65 bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
66 },
67 }
68 var bootstrapSecretInvalid = &corev1.Secret{
69 ObjectMeta: metav1.ObjectMeta{
70 Namespace: metav1.NamespaceSystem,
71 Name: bootstrapapi.BootstrapTokenSecretPrefix,
72 },
73 Type: corev1.SecretTypeBootstrapToken,
74 Data: map[string][]byte{
75 bootstrapapi.BootstrapTokenIDKey: []byte(validTokenID),
76 bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"),
77 bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
78 },
79 }
80 tokenExpiredTime := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339)
81 var expiredBootstrapToken = &corev1.Secret{
82 ObjectMeta: metav1.ObjectMeta{
83 Namespace: metav1.NamespaceSystem,
84 Name: bootstrapapi.BootstrapTokenSecretPrefix,
85 },
86 Type: corev1.SecretTypeBootstrapToken,
87 Data: map[string][]byte{
88 bootstrapapi.BootstrapTokenIDKey: []byte(validTokenID),
89 bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"),
90 bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
91 bootstrapapi.BootstrapTokenExpirationKey: []byte(tokenExpiredTime),
92 },
93 }
94 type request struct {
95 verb string
96 URL string
97 body string
98 statusCodes map[int]bool
99 }
100 tests := []struct {
101 name string
102 request request
103 secret *corev1.Secret
104 }{
105 {
106 name: "valid token",
107 request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code200},
108 secret: bootstrapSecretValid,
109 },
110 {
111 name: "invalid token format",
112 request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401},
113 secret: bootstrapSecretInvalid,
114 },
115 {
116 name: "invalid token expired",
117 request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401},
118 secret: expiredBootstrapToken,
119 },
120 }
121 for _, test := range tests {
122 t.Run(test.name, func(t *testing.T) {
123 tCtx := ktesting.Init(t)
124 authenticator := group.NewAuthenticatedGroupAdder(bearertoken.New(bootstrap.NewTokenAuthenticator(bootstrapSecrets{test.secret})))
125
126 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
127 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
128 opts.Authorization.Modes = []string{"AlwaysAllow"}
129 },
130 ModifyServerConfig: func(config *controlplane.Config) {
131 config.GenericConfig.Authentication.Authenticator = authenticator
132 },
133 })
134 defer tearDownFn()
135
136 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-bootstrap-token", t)
137 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
138
139 previousResourceVersion := make(map[string]float64)
140 transport, err := rest.TransportFor(kubeConfig)
141 if err != nil {
142 t.Fatal(err)
143 }
144
145 token := validTokenID + "." + validSecret
146 var bodyStr string
147 if test.request.body != "" {
148 sub := ""
149 if test.request.verb == "PUT" {
150
151 if resVersion := previousResourceVersion[getPreviousResourceVersionKey(test.request.URL, "")]; resVersion != 0 {
152 sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
153 }
154 sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
155 }
156 bodyStr = fmt.Sprintf(test.request.body, sub)
157 }
158 test.request.body = bodyStr
159 bodyBytes := bytes.NewReader([]byte(bodyStr))
160 req, err := http.NewRequest(test.request.verb, kubeConfig.Host+test.request.URL, bodyBytes)
161 if err != nil {
162 t.Fatalf("unexpected error: %v", err)
163 }
164 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
165 if test.request.verb == "PATCH" {
166 req.Header.Set("Content-Type", "application/merge-patch+json")
167 }
168
169 func() {
170 resp, err := transport.RoundTrip(req)
171 if err != nil {
172 t.Logf("case %v", test.name)
173 t.Fatalf("unexpected error: %v", err)
174 }
175 defer resp.Body.Close()
176 b, _ := io.ReadAll(resp.Body)
177 if _, ok := test.request.statusCodes[resp.StatusCode]; !ok {
178 t.Logf("case %v", test.name)
179 t.Errorf("Expected status one of %v, but got %v", test.request.statusCodes, resp.StatusCode)
180 t.Errorf("Body: %v", string(b))
181 } else {
182 if test.request.verb == "POST" {
183
184 id, currentResourceVersion, err := parseResourceVersion(b)
185 if err == nil {
186 key := getPreviousResourceVersionKey(test.request.URL, id)
187 previousResourceVersion[key] = currentResourceVersion
188 }
189 }
190 }
191
192 }()
193 })
194 }
195 }
196
View as plain text