/
auth-config.go
178 lines (151 loc) · 4.9 KB
/
auth-config.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
/*
* This file is part of the CDI project
*
* 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.
*
* Copyright 2019 Red Hat, Inc.
*
*/
package apiserver
import (
"context"
"crypto/x509"
"encoding/json"
"sync"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"kubevirt.io/containerized-data-importer/pkg/common"
)
const (
configMapName = "extension-apiserver-authentication"
)
// AuthConfig contains extension-apiserver-authentication data
type AuthConfig struct {
AllowedNames []string
UserHeaders []string
GroupHeaders []string
ExtraPrefixHeaders []string
ClientCABytes []byte
RequestheaderClientCABytes []byte
CertPool *x509.CertPool
}
// AuthConfigWatcher is the interface of authConfigWatcher
type AuthConfigWatcher interface {
GetAuthConfig() *AuthConfig
}
type authConfigWatcher struct {
// keep this around for tests
informer cache.SharedIndexInformer
config *AuthConfig
mutex sync.RWMutex
}
// ValidateName checks if name is allowed
func (ac *AuthConfig) ValidateName(name string) bool {
klog.V(3).Infof("Validating CN: %s", name)
for _, n := range ac.AllowedNames {
if n == name {
return true
}
}
// no allowed names means anyone is allowed
// https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/#kubernetes-apiserver-client-authentication
return len(ac.AllowedNames) == 0
}
// NewAuthConfigWatcher crates a new authConfigWatcher
func NewAuthConfigWatcher(ctx context.Context, client kubernetes.Interface) (AuthConfigWatcher, error) {
informerFactory := informers.NewSharedInformerFactoryWithOptions(client,
common.DefaultResyncPeriod,
informers.WithNamespace(metav1.NamespaceSystem),
informers.WithTweakListOptions(
func(options *metav1.ListOptions) {
options.FieldSelector = "metadata.name=" + configMapName
},
),
)
configMapInformer := informerFactory.Core().V1().ConfigMaps().Informer()
acw := &authConfigWatcher{
informer: configMapInformer,
}
_, err := configMapInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
klog.V(3).Infof("configMapInformer add callback: %+v", obj)
acw.updateConfig(obj.(*corev1.ConfigMap))
},
UpdateFunc: func(_, obj interface{}) {
klog.V(3).Infof("configMapInformer update callback: %+v", obj)
acw.updateConfig(obj.(*corev1.ConfigMap))
},
DeleteFunc: func(obj interface{}) {
cm := obj.(*corev1.ConfigMap)
klog.Errorf("Configmap %s deleted", cm.Name)
},
})
if err != nil {
return nil, err
}
go informerFactory.Start(ctx.Done())
klog.V(3).Infoln("Waiting for cache sync")
cache.WaitForCacheSync(ctx.Done(), configMapInformer.HasSynced)
klog.V(3).Infoln("Cache sync complete")
return acw, nil
}
func (acw *authConfigWatcher) GetAuthConfig() *AuthConfig {
acw.mutex.RLock()
defer acw.mutex.RUnlock()
return acw.config
}
func deserializeStringSlice(in string) []string {
if len(in) == 0 {
return nil
}
var ret []string
if err := json.Unmarshal([]byte(in), &ret); err != nil {
klog.Errorf("Error decoding %q", in)
return nil
}
return ret
}
func (acw *authConfigWatcher) updateConfig(cm *corev1.ConfigMap) {
newConfig := &AuthConfig{}
pool := x509.NewCertPool()
s, ok := cm.Data["client-ca-file"]
if ok {
newConfig.ClientCABytes = []byte(s)
// TODO don't think we've done enough testing to support this path (direct access to the apiserver)
// Have to write code to get user/groups/etc from cert
/*
if ok = pool.AppendCertsFromPEM(newConfig.ClientCABytes); !ok {
klog.Errorf("Error adding ClientCABytes to client cert pool")
}
*/
}
s, ok = cm.Data["requestheader-client-ca-file"]
if ok {
newConfig.RequestheaderClientCABytes = []byte(s)
if ok = pool.AppendCertsFromPEM(newConfig.RequestheaderClientCABytes); !ok {
klog.Errorf("Error adding RequestheaderClientCABytes to client cert pool")
}
}
newConfig.CertPool = pool
newConfig.AllowedNames = deserializeStringSlice(cm.Data["requestheader-allowed-names"])
newConfig.UserHeaders = deserializeStringSlice(cm.Data["requestheader-username-headers"])
newConfig.GroupHeaders = deserializeStringSlice(cm.Data["requestheader-group-headers"])
newConfig.ExtraPrefixHeaders = deserializeStringSlice(cm.Data["requestheader-extra-headers-prefix"])
acw.mutex.Lock()
defer acw.mutex.Unlock()
acw.config = newConfig
}