1/*
2Copyright 2019 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package auth
18
19import (
20	"context"
21	"io/ioutil"
22	"os"
23	"testing"
24	"time"
25
26	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27	"k8s.io/apiserver/pkg/authentication/authenticator"
28	"k8s.io/apiserver/pkg/authorization/authorizerfactory"
29	clientset "k8s.io/client-go/kubernetes"
30	restclient "k8s.io/client-go/rest"
31	"k8s.io/controller-manager/pkg/clientbuilder"
32	"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
33	"k8s.io/kubernetes/pkg/controlplane"
34	kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
35	"k8s.io/kubernetes/test/integration/framework"
36)
37
38func TestDynamicClientBuilder(t *testing.T) {
39	tmpfile, err := ioutil.TempFile("/tmp", "key")
40	if err != nil {
41		t.Fatalf("create temp file failed: %v", err)
42	}
43	defer os.RemoveAll(tmpfile.Name())
44
45	if err = ioutil.WriteFile(tmpfile.Name(), []byte(ecdsaPrivateKey), 0666); err != nil {
46		t.Fatalf("write file %s failed: %v", tmpfile.Name(), err)
47	}
48
49	const iss = "https://foo.bar.example.com"
50	aud := authenticator.Audiences{"api"}
51
52	maxExpirationDuration := time.Second * 60 * 60
53	if err != nil {
54		t.Fatalf("parse duration failed: %v", err)
55	}
56
57	stopCh := make(chan struct{})
58	defer close(stopCh)
59
60	baseClient, baseConfig := framework.StartTestServer(t, stopCh, framework.TestServerSetup{
61		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
62			opts.ServiceAccountSigningKeyFile = tmpfile.Name()
63			opts.ServiceAccountTokenMaxExpiration = maxExpirationDuration
64			if opts.Authentication == nil {
65				opts.Authentication = &kubeoptions.BuiltInAuthenticationOptions{}
66			}
67
68			opts.Authentication.APIAudiences = aud
69			if opts.Authentication.ServiceAccounts == nil {
70				opts.Authentication.ServiceAccounts = &kubeoptions.ServiceAccountAuthenticationOptions{}
71			}
72			opts.Authentication.ServiceAccounts.Issuers = []string{iss}
73			opts.Authentication.ServiceAccounts.KeyFiles = []string{tmpfile.Name()}
74		},
75		ModifyServerConfig: func(config *controlplane.Config) {
76			config.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
77		},
78	})
79
80	// We want to test if the token rotation works fine here.
81	// To minimize the time this test would consume, we use the minimial token expiration.
82	// The minimial token expiration is defined in:
83	// pkg/apis/authentication/validation/validation.go
84	exp := int64(600)
85	leeway := 99
86	ns := "default"
87	clientBuilder := clientbuilder.NewTestDynamicClientBuilder(
88		restclient.AnonymousClientConfig(baseConfig),
89		baseClient.CoreV1(),
90		ns, exp, leeway)
91
92	saName := "dt"
93	dymClient, err := clientBuilder.Client(saName)
94
95	if err != nil {
96		t.Fatalf("build client via dynamic client builder failed: %v", err)
97	}
98
99	if err = testClientBuilder(dymClient, ns, saName); err != nil {
100		t.Fatalf("dynamic client get resources failed befroe deleting sa: %v", err)
101	}
102
103	// We want to trigger token rotation here by deleting service account
104	// the dynamic client was using.
105	if err = dymClient.CoreV1().ServiceAccounts(ns).Delete(context.TODO(), saName, metav1.DeleteOptions{}); err != nil {
106		t.Fatalf("delete service account %s failed: %v", saName, err)
107	}
108	time.Sleep(time.Second * 10)
109
110	if err = testClientBuilder(dymClient, ns, saName); err != nil {
111		t.Fatalf("dynamic client get resources failed after deleting sa: %v", err)
112	}
113}
114
115func testClientBuilder(dymClient clientset.Interface, ns, saName string) error {
116	_, err := dymClient.CoreV1().Namespaces().Get(context.TODO(), ns, metav1.GetOptions{})
117	if err != nil {
118		return err
119	}
120
121	_, err = dymClient.CoreV1().ServiceAccounts(ns).Get(context.TODO(), saName, metav1.GetOptions{})
122	if err != nil {
123		return err
124	}
125	return nil
126}
127