forked from gravitational/teleport
-
Notifications
You must be signed in to change notification settings - Fork 0
/
middleware.go
245 lines (226 loc) · 8.03 KB
/
middleware.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
Copyright 2017 Gravitational, Inc.
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 auth
import (
"context"
"crypto/tls"
"net"
"net/http"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/trace"
)
// TLSServerConfig is a configuration for TLS server
type TLSServerConfig struct {
// TLS is a base TLS configuration
TLS *tls.Config
// API is API server configuration
APIConfig
// LimiterConfig is limiter config
LimiterConfig limiter.LimiterConfig
}
// CheckAndSetDefaults checks and sets default values
func (c *TLSServerConfig) CheckAndSetDefaults() error {
if c.TLS == nil {
return trace.BadParameter("missing parameter TLS")
}
c.TLS.ClientAuth = tls.VerifyClientCertIfGiven
if c.TLS.ClientCAs == nil {
return trace.BadParameter("missing parameter TLS.ClientCAs")
}
if c.TLS.RootCAs == nil {
return trace.BadParameter("missing parameter TLS.RootCAs")
}
if len(c.TLS.Certificates) == 0 {
return trace.BadParameter("missing parameter TLS.Certificates")
}
if c.AuthServer == nil {
return trace.BadParameter("missing parameter AuthServer")
}
return nil
}
// TLSServer is TLS auth server
type TLSServer struct {
*http.Server
// TLSServerConfig is TLS server configuration used for auth server
TLSServerConfig
}
// NewTLSServer returns new unstarted TLS server
func NewTLSServer(cfg TLSServerConfig) (*TLSServer, error) {
if err := cfg.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
// limiter limits requests by frequency and amount of simultaneous
// connections per client
limiter, err := limiter.NewLimiter(cfg.LimiterConfig)
if err != nil {
return nil, trace.Wrap(err)
}
// authMiddleware authenticates request assuming TLS client authentication
// adds authentication infromation to the context
// and passes it to the API server
authMiddleware := &AuthMiddleware{AuthServer: cfg.AuthServer}
authMiddleware.Wrap(NewAPIServer(&cfg.APIConfig))
// Wrap sets the next middleware in chain to the authMiddleware
limiter.WrapHandle(authMiddleware)
// force client auth if given
cfg.TLS.ClientAuth = tls.VerifyClientCertIfGiven
server := &TLSServer{
TLSServerConfig: cfg,
Server: &http.Server{
Handler: limiter,
},
}
server.TLS.GetConfigForClient = server.GetConfigForClient
return server, nil
}
// Serve takes TCP listener, upgrades to TLS using config and starts serving
func (t *TLSServer) Serve(listener net.Listener) error {
return t.Server.Serve(tls.NewListener(listener, t.TLS))
}
// GetConfigForClient is getting called on every connection
// and server's GetConfigForClient reloads the list of trusted
// local and remote certificate authorities
func (t *TLSServer) GetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) {
// update client certificate pool based on currently trusted TLS
// certificate authorities.
// TODO(klizhentas) drop connections of the TLS cert authorities
// that are not trusted
// TODO(klizhentas) what are performance implications of returning new config
// per connections? E.g. what happens to session tickets. Benchmark this.
pool, err := t.AuthServer.ClientCertPool()
if err != nil {
log.Errorf("failed to retrieve client pool: %v", trace.DebugReport(err))
// this falls back to the default config
return nil, nil
}
tlsCopy := t.TLS.Clone()
tlsCopy.ClientCAs = pool
return tlsCopy, nil
}
// AuthMiddleware is authentication middleware checking every request
type AuthMiddleware struct {
// AuthServer is underlying auth server used to authenticate requests
AuthServer *AuthServer
// Handler is HTTP handler called after the middleware checks requests
Handler http.Handler
}
// Wrap sets next handler in chain
func (a *AuthMiddleware) Wrap(h http.Handler) {
a.Handler = h
}
// GetUser returns authenticated user based on request metadata set by HTTP server
func (a *AuthMiddleware) GetUser(r *http.Request) (interface{}, error) {
peers := r.TLS.PeerCertificates
if len(peers) > 1 {
// when turning intermediaries on, don't forget to verify
// https://github.com/kubernetes/kubernetes/pull/34524/files#diff-2b283dde198c92424df5355f39544aa4R59
return nil, trace.AccessDenied("access denied: intermediaries are not supported")
}
// with no client authentication in place, middleware
// assumes not-privileged Nop role.
// it theoretically possible to use bearer token auth even
// for connections without auth, but this is not active use-case
// therefore it is not allowed to reduce scope
if len(peers) == 0 {
return BuiltinRole{
GetClusterConfig: a.AuthServer.getCachedClusterConfig,
Role: teleport.RoleNop,
Username: string(teleport.RoleNop),
}, nil
}
clientCert := peers[0]
certClusterName, err := tlsca.ClusterName(clientCert.Issuer)
if err != nil {
log.Warning("Failed to parse client certificate %v.", err)
return nil, trace.AccessDenied("access denied: invalid client certificate")
}
localClusterName, err := a.AuthServer.GetDomainName()
if err != nil {
return nil, trace.Wrap(err)
}
identity, err := tlsca.FromSubject(clientCert.Subject)
if err != nil {
return nil, trace.Wrap(err)
}
// this block assumes interactive user from remote cluster
// based on the remote certificate authority cluster name encoded in
// x509 organization name. This is a safe check because:
// 1. Trust and verification is established during TLS handshake
// by creating a cert pool constructed of trusted certificate authorities
// 2. Remote CAs are not allowed to have the same cluster name
// as the local certificate authority
if certClusterName != localClusterName {
// make sure that this user does not have system role
// the local auth server can not truste remote servers
// to issue certificates with system roles (e.g. Admin),
// to get unrestricted access to the local cluster
systemRole := findSystemRole(identity.Groups)
if systemRole != nil {
return RemoteBuiltinRole{
Role: *systemRole,
Username: identity.Username,
ClusterName: certClusterName,
}, nil
}
return RemoteUser{
ClusterName: certClusterName,
Username: identity.Username,
RemoteRoles: identity.Groups,
}, nil
}
// code below expects user or service from local cluster, to distinguish between
// interactive users and services (e.g. proxies), the code below
// checks for presense of system roles issued in certificate identity
systemRole := findSystemRole(identity.Groups)
// in case if the system role is present, assume this is a service
// agent, e.g. Proxy, connecting to the cluster
if systemRole != nil {
return BuiltinRole{
GetClusterConfig: a.AuthServer.getCachedClusterConfig,
Role: *systemRole,
Username: identity.Username,
}, nil
}
// otherwise assume that is a local role, no need to pass the roles
// as it will be fetched from the local database
return LocalUser{
Username: identity.Username,
}, nil
}
func findSystemRole(roles []string) *teleport.Role {
for _, role := range roles {
systemRole := teleport.Role(role)
err := systemRole.Check()
if err == nil {
return &systemRole
}
}
return nil
}
// ServeHTTP serves HTTP requests
func (a *AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
baseContext := r.Context()
if baseContext == nil {
baseContext = context.TODO()
}
user, err := a.GetUser(r)
if err != nil {
trace.WriteError(w, err)
return
}
// determine authenticated user based on the request parameters
requestWithContext := r.WithContext(context.WithValue(baseContext, ContextUser, user))
a.Handler.ServeHTTP(w, requestWithContext)
}