Files
teleport/lib/auth/middleware.go
T
Alexander Klizhentas 73ecb48232 Adds support for kubernetes_users, extend interpolation (#3404) (#3418)
This commit fixes #3369, refs #3374

It adds support for kuberenetes_users section in roles,
allowing Teleport proxy to impersonate user identities.

It also extends variable interpolation syntax by adding
suffix and prefix to variables and function `email.local`:

Example:

```yaml
kind: role
version: v3
metadata:
  name: admin
spec:
  allow:
    # extract email local part from the email claim
    logins: ['{{email.local(external.email)}}']

    # impersonate a kubernetes user with IAM prefix
    kubernetes_users: ['IAM#{{external.email}}']

  # the deny section uses the identical format as the 'allow' section.
  # the deny rules always override allow rules.
  deny: {}
```

Some notes on email.local behavior:

* This is the only function supported in the template variables for now
* In case if the email.local will encounter invalid email address,
it will interpolate to empty value, will be removed from resulting
output.

Changes in impersonation behavior:

* By default, if no kubernetes_users is set, which is a majority of cases,
  user will impersonate themselves, which is the backwards-compatible behavior.

* As long as at least one `kubernetes_users` is set, the forwarder will start
  limiting the list of users allowed by the client to impersonate.

* If the users' role set does not include actual user name, it will be rejected,
  otherwise there will be no way to exclude the user from the list).

* If the `kuberentes_users` role set includes only one user
  (quite frequently that's the real intent), teleport will default to it,
  otherwise it will refuse to select.

  This will enable the use case when `kubernetes_users` has just one field to
  link the user identity with the IAM role, for example `IAM#{{external.email}}`

* Previous versions of the forwarding proxy were denying all external
impersonation headers, this commit allows 'Impesrsonate-User' and
'Impersonate-Group' header values that are allowed by role set.

* Previous versions of the forwarding proxy ignored 'Deny' section of the roles
when applied to impersonation, this commit fixes that - roles with deny
kubernetes_users and kubernetes_groups section will not allow
impersonation of those users and groups.
2020-03-07 16:32:37 -08:00

364 lines
12 KiB
Go

/*
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"
"crypto/x509"
"net"
"net/http"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
"golang.org/x/net/http2"
)
// 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
// AccessPoint is a caching access point
AccessPoint AccessCache
// Component is used for debugging purposes
Component string
// AcceptedUsage restricts authentication
// to a subset of certificates based on the metadata
AcceptedUsage []string
}
// 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.AccessPoint == nil {
return trace.BadParameter("missing parameter AccessPoint")
}
if c.Component == "" {
c.Component = teleport.ComponentAuth
}
return nil
}
// TLSServer is TLS auth server
type TLSServer struct {
*http.Server
// TLSServerConfig is TLS server configuration used for auth server
TLSServerConfig
// Entry is TLS server logging entry
*logrus.Entry
}
// 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 information to the context
// and passes it to the API server
authMiddleware := &AuthMiddleware{
AccessPoint: cfg.AccessPoint,
AcceptedUsage: cfg.AcceptedUsage,
}
authMiddleware.Wrap(NewGRPCServer(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
cfg.TLS.NextProtos = []string{http2.NextProtoTLS}
server := &TLSServer{
TLSServerConfig: cfg,
Server: &http.Server{
Handler: limiter,
ReadHeaderTimeout: defaults.DefaultDialTimeout,
},
Entry: logrus.WithFields(logrus.Fields{
trace.Component: cfg.Component,
}),
}
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) {
var clusterName string
var err error
if info.ServerName != "" {
clusterName, err = DecodeClusterName(info.ServerName)
if err != nil {
if !trace.IsNotFound(err) {
t.Warningf("Client sent unsupported cluster name %q, what resulted in error %v.", info.ServerName, err)
return nil, trace.AccessDenied("access is denied")
}
}
}
// update client certificate pool based on currently trusted TLS
// certificate authorities.
// TODO(klizhentas) drop connections of the TLS cert authorities
// that are not trusted
pool, err := ClientCertPool(t.AccessPoint, clusterName)
if err != nil {
var ourClusterName string
if clusterName, err := t.AccessPoint.GetClusterName(); err == nil {
ourClusterName = clusterName.GetClusterName()
}
t.Errorf("Failed to retrieve client pool. Client cluster %v, target cluster %v, error: %v.", clusterName, ourClusterName, trace.DebugReport(err))
// this falls back to the default config
return nil, nil
}
tlsCopy := t.TLS.Clone()
tlsCopy.ClientCAs = pool
for _, cert := range tlsCopy.Certificates {
t.Debugf("Server certificate %v.", TLSCertInfo(&cert))
}
return tlsCopy, nil
}
// AuthMiddleware is authentication middleware checking every request
type AuthMiddleware struct {
// AccessPoint is a caching access point for auth server
AccessPoint AccessCache
// Handler is HTTP handler called after the middleware checks requests
Handler http.Handler
// AcceptedUsage restricts authentication
// to a subset of certificates based on certificate metadata,
// for example middleware can reject certificates with mismatching usage.
// If empty, will only accept certificates with non-limited usage,
// if set, will accept certificates with non-limited usage,
// and usage exactly matching the specified values.
AcceptedUsage []string
}
// 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) (IdentityGetter, 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")
}
localClusterName, err := a.AccessPoint.GetClusterName()
if err != nil {
return nil, trace.Wrap(err)
}
// 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.AccessPoint.GetClusterConfig,
Role: teleport.RoleNop,
Username: string(teleport.RoleNop),
ClusterName: localClusterName.GetClusterName(),
Identity: tlsca.Identity{},
}, nil
}
clientCert := peers[0]
certClusterName, err := tlsca.ClusterName(clientCert.Issuer)
if err != nil {
log.Warnf("Failed to parse client certificate %v.", err)
return nil, trace.AccessDenied("access denied: invalid client certificate")
}
identity, err := tlsca.FromSubject(clientCert.Subject, clientCert.NotAfter)
if err != nil {
return nil, trace.Wrap(err)
}
// If there is any restriction on the certificate usage
// reject the API server request. This is done so some classes
// of certificates issued for kubernetes usage by proxy, can not be used
// against auth server. Later on we can extend more
// advanced cert usage, but for now this is the safest option.
if len(identity.Usage) != 0 && !utils.StringSlicesEqual(a.AcceptedUsage, identity.Usage) {
log.Warningf("Restricted certificate of user %q with usage %v rejected while accessing the auth endpoint with acceptable usage %v.",
identity.Username, identity.Usage, a.AcceptedUsage)
return nil, trace.AccessDenied("access denied: invalid client certificate")
}
// 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.GetClusterName() {
// 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,
Identity: *identity,
}, nil
}
return RemoteUser{
ClusterName: certClusterName,
Username: identity.Username,
Principals: identity.Principals,
KubernetesGroups: identity.KubernetesGroups,
KubernetesUsers: identity.KubernetesUsers,
RemoteRoles: identity.Groups,
Identity: *identity,
}, 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 presence 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.AccessPoint.GetClusterConfig,
Role: *systemRole,
Username: identity.Username,
ClusterName: localClusterName.GetClusterName(),
Identity: *identity,
}, 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,
Identity: *identity,
}, 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)
}
// ClientCertPool returns trusted x509 cerificate authority pool
func ClientCertPool(client AccessCache, clusterName string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
var authorities []services.CertAuthority
if clusterName == "" {
hostCAs, err := client.GetCertAuthorities(services.HostCA, false, services.SkipValidation())
if err != nil {
return nil, trace.Wrap(err)
}
userCAs, err := client.GetCertAuthorities(services.UserCA, false, services.SkipValidation())
if err != nil {
return nil, trace.Wrap(err)
}
authorities = append(authorities, hostCAs...)
authorities = append(authorities, userCAs...)
} else {
hostCA, err := client.GetCertAuthority(
services.CertAuthID{Type: services.HostCA, DomainName: clusterName},
false, services.SkipValidation())
if err != nil {
return nil, trace.Wrap(err)
}
userCA, err := client.GetCertAuthority(
services.CertAuthID{Type: services.UserCA, DomainName: clusterName},
false, services.SkipValidation())
if err != nil {
return nil, trace.Wrap(err)
}
authorities = append(authorities, hostCA)
authorities = append(authorities, userCA)
}
for _, auth := range authorities {
for _, keyPair := range auth.GetTLSKeyPairs() {
cert, err := tlsca.ParseCertificatePEM(keyPair.Cert)
if err != nil {
return nil, trace.Wrap(err)
}
log.Debugf("ClientCertPool -> %v", CertInfo(cert))
pool.AddCert(cert)
}
}
return pool, nil
}