forked from jetstack/kube-oidc-proxy
/
proxy.go
190 lines (153 loc) · 4.84 KB
/
proxy.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
// Copyright Jetstack Ltd. See LICENSE for details.
package proxy
import (
"errors"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/rest"
"k8s.io/client-go/transport"
"k8s.io/klog"
authtypes "k8s.io/kubernetes/pkg/apis/authentication"
)
var (
errUnauthorized = errors.New("Unauthorized")
errImpersonateHeader = errors.New("Impersonate-User in header")
errNoName = errors.New("No name in OIDC info")
// http headers are case-insensitive
impersonateUserHeader = strings.ToLower(authtypes.ImpersonateUserHeader)
impersonateGroupHeader = strings.ToLower(authtypes.ImpersonateGroupHeader)
impersonateExtraHeader = strings.ToLower(authtypes.ImpersonateUserExtraHeaderPrefix)
)
type Proxy struct {
reqAuther *bearertoken.Authenticator
secureServingInfo *server.SecureServingInfo
restConfig *rest.Config
clientTransport http.RoundTripper
}
func New(restConfig *rest.Config, auther *bearertoken.Authenticator,
ssinfo *server.SecureServingInfo) *Proxy {
return &Proxy{
restConfig: restConfig,
reqAuther: auther,
secureServingInfo: ssinfo,
}
}
func (p *Proxy) Run(stopCh <-chan struct{}) error {
klog.Infof("waiting for oidc provider to become ready...")
// get golang tls config to the API server
tlsConfig, err := rest.TLSConfigFor(p.restConfig)
if err != nil {
return err
}
// create tls transport to request
tlsTransport := &http.Transport{
TLSClientConfig: tlsConfig,
}
// get kube transport config form rest client config
restTransportConfig, err := p.restConfig.TransportConfig()
if err != nil {
return err
}
// wrap golang tls config with kube transport round tripper
clientRT, err := transport.HTTPWrappersForConfig(restTransportConfig, tlsTransport)
if err != nil {
return err
}
p.clientTransport = clientRT
// get API server url
url, err := url.Parse(p.restConfig.Host)
if err != nil {
return fmt.Errorf("failed to parse url: %s", err)
}
// set up proxy handler using proxy
proxyHandler := httputil.NewSingleHostReverseProxy(url)
proxyHandler.Transport = p
proxyHandler.ErrorHandler = p.Error
// wait for oidc auther to become ready
time.Sleep(10 * time.Second)
if err := p.serve(proxyHandler, stopCh); err != nil {
return err
}
klog.Infof("proxy ready")
return nil
}
func (p *Proxy) serve(proxyHandler *httputil.ReverseProxy, stopCh <-chan struct{}) error {
// securely serve using serving config
err := p.secureServingInfo.Serve(proxyHandler, time.Second*60, stopCh)
if err != nil {
return err
}
return nil
}
func (p *Proxy) RoundTrip(req *http.Request) (*http.Response, error) {
// auth request and handle unauthed
info, ok, err := p.reqAuther.AuthenticateRequest(req)
if err != nil {
klog.Errorf("unable to authenticate the request due to an error: %v", err)
return nil, errUnauthorized
}
if !ok {
return nil, errUnauthorized
}
// check for incoming impersonation headers and reject if any exists
if p.hasImpersonation(req.Header) {
return nil, errImpersonateHeader
}
user := info.User
// no name available so reject request
if user.GetName() == "" {
return nil, errNoName
}
// set impersonation header using authenticated user identity
conf := transport.ImpersonationConfig{
UserName: user.GetName(),
Groups: user.GetGroups(),
Extra: user.GetExtra(),
}
rt := transport.NewImpersonatingRoundTripper(conf, p.clientTransport)
// push request through round trippers to the API server
return rt.RoundTrip(req)
}
func (p *Proxy) hasImpersonation(header http.Header) bool {
for h := range header {
if strings.ToLower(h) == impersonateUserHeader ||
strings.ToLower(h) == impersonateGroupHeader ||
strings.HasPrefix(strings.ToLower(h), impersonateExtraHeader) {
return true
}
}
return false
}
func (p *Proxy) Error(rw http.ResponseWriter, r *http.Request, err error) {
if err == nil {
klog.Error("error was called with no error")
http.Error(rw, "", http.StatusInternalServerError)
return
}
switch err {
// failed auth
case errUnauthorized:
klog.V(2).Infof("unauthenticated user request %s", r.RemoteAddr)
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
return
// user request with impersonation
case errImpersonateHeader:
klog.V(2).Infof("impersonation user request %s", r.RemoteAddr)
http.Error(rw, "Impersonation requests are disabled when using kube-oidc-proxy", http.StatusForbidden)
return
// no name given or available in oidc response
case errNoName:
klog.V(2).Infof("no name available in oidc info %s", r.RemoteAddr)
http.Error(rw, "Username claim not available in OIDC Issuer response", http.StatusForbidden)
// server or unknown error
default:
klog.Errorf("unknown error (%s): %s", r.RemoteAddr, err)
http.Error(rw, "", http.StatusInternalServerError)
}
}