Files
teleport/lib/auth/middleware.go
T
rosstimothy c2fc9a9375 Add idle connection timeouts to http clients and servers (#22885)
Sets `http.Server.IdleTimeout` and `http.Client.IdleConnTimeout`
on clients and servers which didn't have them set. A default of
360s was chosen to be on par with the default of an NLB without
being identical.

This was added as another safety measure to prevent leaking any
idle connections indefinitely as seen in #22757.

`apidefaults.DefaultDialTimeout` was also renamed to
`apidefaults.DefaultIOTimeout` to better reflect its usage.
2023-03-10 16:47:06 +00:00

781 lines
26 KiB
Go

/*
Copyright 2017-2021 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"
"math"
"net"
"net/http"
"time"
"github.com/gravitational/oxy/ratelimit"
"github.com/gravitational/trace"
om "github.com/grpc-ecosystem/go-grpc-middleware/providers/openmetrics/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"golang.org/x/exp/slices"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
"github.com/gravitational/teleport"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/multiplexer"
"github.com/gravitational/teleport/lib/observability/metrics"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
)
// TLSServerConfig is a configuration for TLS server
type TLSServerConfig struct {
// Listener is a listener to bind to
Listener net.Listener
// TLS is a base TLS configuration
TLS *tls.Config
// API is API server configuration
APIConfig
// LimiterConfig is limiter config
LimiterConfig limiter.Config
// 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
// ID is an optional debugging ID
ID string
// Metrics are optional TLSServer metrics
Metrics *Metrics
}
// CheckAndSetDefaults checks and sets default values
func (c *TLSServerConfig) CheckAndSetDefaults() error {
if err := c.APIConfig.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
if c.Listener == nil {
return trace.BadParameter("missing parameter Listener")
}
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
}
if c.Metrics == nil {
c.Metrics = &Metrics{}
}
return nil
}
// Metrics handles optional metrics for TLSServerConfig
type Metrics struct {
GRPCServerLatency bool
}
// TLSServer is TLS auth server
type TLSServer struct {
// httpServer is HTTP/1.1 part of the server
httpServer *http.Server
// grpcServer is GRPC server
grpcServer *GRPCServer
// cfg is TLS server configuration used for auth server
cfg TLSServerConfig
// log is TLS server logging entry
log *logrus.Entry
// mux is a listener that multiplexes HTTP/2 and HTTP/1.1
// on different listeners
mux *multiplexer.TLSListener
}
// 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)
}
// sets up grpc metrics interceptor
grpcMetrics := metrics.CreateGRPCServerMetrics(cfg.Metrics.GRPCServerLatency, prometheus.Labels{teleport.TagServer: "teleport-auth"})
err = metrics.RegisterPrometheusCollectors(grpcMetrics)
if err != nil {
return nil, trace.Wrap(err)
}
localClusterName, err := cfg.AccessPoint.GetClusterName()
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 := &Middleware{
ClusterName: localClusterName.GetClusterName(),
AcceptedUsage: cfg.AcceptedUsage,
Limiter: limiter,
GRPCMetrics: grpcMetrics,
}
apiServer, err := NewAPIServer(&cfg.APIConfig)
if err != nil {
return nil, trace.Wrap(err)
}
authMiddleware.Wrap(apiServer)
// 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}
securityHeaderHandler := httplib.MakeSecurityHeaderHandler(limiter)
tracingHandler := httplib.MakeTracingHandler(securityHeaderHandler, teleport.ComponentAuth)
server := &TLSServer{
cfg: cfg,
httpServer: &http.Server{
Handler: tracingHandler,
ReadHeaderTimeout: apidefaults.DefaultIOTimeout,
IdleTimeout: apidefaults.DefaultIdleTimeout,
},
log: logrus.WithFields(logrus.Fields{
trace.Component: cfg.Component,
}),
}
server.cfg.TLS.GetConfigForClient = server.GetConfigForClient
server.grpcServer, err = NewGRPCServer(GRPCServerConfig{
TLS: server.cfg.TLS,
Middleware: authMiddleware,
APIConfig: cfg.APIConfig,
UnaryInterceptor: authMiddleware.UnaryInterceptor(),
StreamInterceptor: authMiddleware.StreamInterceptor(),
})
if err != nil {
return nil, trace.Wrap(err)
}
server.mux, err = multiplexer.NewTLSListener(multiplexer.TLSListenerConfig{
Listener: tls.NewListener(cfg.Listener, server.cfg.TLS),
ID: cfg.ID,
})
if err != nil {
return nil, trace.Wrap(err)
}
if cfg.PluginRegistry != nil {
if err := cfg.PluginRegistry.RegisterAuthServices(server.grpcServer); err != nil {
return nil, trace.Wrap(err)
}
}
return server, nil
}
// Close closes TLS server non-gracefully - terminates in flight connections
func (t *TLSServer) Close() error {
errC := make(chan error, 2)
go func() {
errC <- t.httpServer.Close()
}()
go func() {
t.grpcServer.server.Stop()
errC <- nil
}()
errors := []error{}
for i := 0; i < 2; i++ {
errors = append(errors, <-errC)
}
errors = append(errors, t.mux.Close())
return trace.NewAggregate(errors...)
}
// Shutdown shuts down TLS server
func (t *TLSServer) Shutdown(ctx context.Context) error {
errC := make(chan error, 2)
go func() {
errC <- t.httpServer.Shutdown(ctx)
}()
go func() {
t.grpcServer.server.GracefulStop()
errC <- nil
}()
errors := []error{}
for i := 0; i < 2; i++ {
errors = append(errors, <-errC)
}
return trace.NewAggregate(errors...)
}
// Serve starts GRPC and HTTP1.1 services on the mux listener
func (t *TLSServer) Serve() error {
errC := make(chan error, 2)
go func() {
err := t.mux.Serve()
t.log.WithError(err).Warningf("Mux serve failed.")
}()
go func() {
errC <- t.httpServer.Serve(t.mux.HTTP())
}()
go func() {
errC <- t.grpcServer.server.Serve(t.mux.HTTP2())
}()
errors := []error{}
for i := 0; i < 2; i++ {
errors = append(errors, <-errC)
}
return trace.NewAggregate(errors...)
}
// 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
switch info.ServerName {
case "":
// Client does not use SNI, will validate against all known CAs.
default:
clusterName, err = apiutils.DecodeClusterName(info.ServerName)
if err != nil {
if !trace.IsNotFound(err) {
t.log.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, totalSubjectsLen, err := DefaultClientCertPool(t.cfg.AccessPoint, clusterName)
if err != nil {
var ourClusterName string
if clusterName, err := t.cfg.AccessPoint.GetClusterName(); err == nil {
ourClusterName = clusterName.GetClusterName()
}
t.log.Errorf("Failed to retrieve client pool for client %v, client cluster %v, target cluster %v, error: %v.",
info.Conn.RemoteAddr().String(), clusterName, ourClusterName, trace.DebugReport(err))
// this falls back to the default config
return nil, nil
}
// Per https://tools.ietf.org/html/rfc5246#section-7.4.4 the total size of
// the known CA subjects sent to the client can't exceed 2^16-1 (due to
// 2-byte length encoding). The crypto/tls stack will panic if this
// happens. To make the error less cryptic, catch this condition and return
// a better error.
//
// This may happen with a very large (>500) number of trusted clusters, if
// the client doesn't send the correct ServerName in its ClientHelloInfo
// (see the switch at the top of this func).
if totalSubjectsLen >= int64(math.MaxUint16) {
return nil, trace.BadParameter("number of CAs in client cert pool is too large and cannot be encoded in a TLS handshake; this is due to a large number of trusted clusters; try updating tsh to the latest version; if that doesn't help, remove some trusted clusters")
}
tlsCopy := t.cfg.TLS.Clone()
tlsCopy.ClientCAs = pool
for _, cert := range tlsCopy.Certificates {
t.log.Debugf("Server certificate %v.", TLSCertInfo(&cert))
}
return tlsCopy, nil
}
// Middleware is authentication middleware checking every request
type Middleware struct {
ClusterName string
// 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
// Limiter is a rate and connection limiter
Limiter *limiter.Limiter
// GRPCMetrics is the configured grpc metrics for the interceptors
GRPCMetrics *om.ServerMetrics
}
// Wrap sets next handler in chain
func (a *Middleware) Wrap(h http.Handler) {
a.Handler = h
}
func getCustomRate(endpoint string) *ratelimit.RateSet {
switch endpoint {
// Account recovery RPCs.
case
"/proto.AuthService/ChangeUserAuthentication",
"/proto.AuthService/ChangePassword",
"/proto.AuthService/GetAccountRecoveryToken",
"/proto.AuthService/StartAccountRecovery",
"/proto.AuthService/VerifyAccountRecovery":
rates := ratelimit.NewRateSet()
// This limit means: 1 request per minute with bursts up to 10 requests.
if err := rates.Add(time.Minute, 1, 10); err != nil {
log.WithError(err).Debugf("Failed to define a custom rate for rpc method %q, using default rate", endpoint)
return nil
}
return rates
// Passwordless RPCs (potential unauthenticated challenge generation).
case "/proto.AuthService/CreateAuthenticateChallenge":
const period = defaults.LimiterPasswordlessPeriod
const average = defaults.LimiterPasswordlessAverage
const burst = defaults.LimiterPasswordlessBurst
rates := ratelimit.NewRateSet()
if err := rates.Add(period, average, burst); err != nil {
log.WithError(err).Debugf("Failed to define a custom rate for rpc method %q, using default rate", endpoint)
return nil
}
return rates
}
return nil
}
// withAuthenticatedUser returns a new context with the ContextUser field set to
// the caller's user identity as authenticated by their client TLS certificate.
func (a *Middleware) withAuthenticatedUser(ctx context.Context) (context.Context, error) {
peerInfo, ok := peer.FromContext(ctx)
if !ok {
return nil, trace.AccessDenied("missing authentication")
}
var (
connState *tls.ConnectionState
identityGetter authz.IdentityGetter
)
switch info := peerInfo.AuthInfo.(type) {
// IdentityInfo is provided if the grpc server is configured with the
// TransportCredentials provided in this package.
case IdentityInfo:
connState = &info.TLSInfo.State
identityGetter = info.IdentityGetter
// credentials.TLSInfo is provided if the grpc server is configured with
// credentials.NewTLS.
case credentials.TLSInfo:
user, err := a.GetUser(info.State)
if err != nil {
return nil, trace.Wrap(err)
}
connState = &info.State
identityGetter = user
default:
return nil, trace.AccessDenied("missing authentication")
}
ctx = authz.ContextWithUserCertificate(ctx, certFromConnState(connState))
ctx = authz.ContextWithClientAddr(ctx, peerInfo.Addr)
ctx = authz.ContextWithUser(ctx, identityGetter)
return ctx, nil
}
func certFromConnState(state *tls.ConnectionState) *x509.Certificate {
if state == nil || len(state.PeerCertificates) != 1 {
return nil
}
return state.PeerCertificates[0]
}
// withAuthenticatedUserUnaryInterceptor is a gRPC unary server interceptor
// which sets the ContextUser field on the request context to the caller's user
// identity as authenticated by their client TLS certificate.
func (a *Middleware) withAuthenticatedUserUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx, err := a.withAuthenticatedUser(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return handler(ctx, req)
}
// withAuthenticatedUserUnaryInterceptor is a gRPC stream server interceptor
// which sets the ContextUser field on the request context to the caller's user
// identity as authenticated by their client TLS certificate.
func (a *Middleware) withAuthenticatedUserStreamInterceptor(srv interface{}, serverStream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx, err := a.withAuthenticatedUser(serverStream.Context())
if err != nil {
return trace.Wrap(err)
}
return handler(srv, &authenticatedStream{ctx: ctx, ServerStream: serverStream})
}
// UnaryInterceptor returns a gPRC unary interceptor which performs rate
// limiting, authenticates requests, and passes the user information as context
// metadata.
func (a *Middleware) UnaryInterceptor() grpc.UnaryServerInterceptor {
if a.GRPCMetrics != nil {
return utils.ChainUnaryServerInterceptors(
otelgrpc.UnaryServerInterceptor(),
om.UnaryServerInterceptor(a.GRPCMetrics),
utils.GRPCServerUnaryErrorInterceptor,
a.Limiter.UnaryServerInterceptorWithCustomRate(getCustomRate),
a.withAuthenticatedUserUnaryInterceptor,
)
}
return utils.ChainUnaryServerInterceptors(
otelgrpc.UnaryServerInterceptor(),
utils.GRPCServerUnaryErrorInterceptor,
a.Limiter.UnaryServerInterceptorWithCustomRate(getCustomRate),
a.withAuthenticatedUserUnaryInterceptor,
)
}
// StreamInterceptor returns a gPRC stream interceptor which performs rate
// limiting, authenticates requests, and passes the user information as context
// metadata.
func (a *Middleware) StreamInterceptor() grpc.StreamServerInterceptor {
if a.GRPCMetrics != nil {
return utils.ChainStreamServerInterceptors(
otelgrpc.StreamServerInterceptor(),
om.StreamServerInterceptor(a.GRPCMetrics),
utils.GRPCServerStreamErrorInterceptor,
a.Limiter.StreamServerInterceptor,
a.withAuthenticatedUserStreamInterceptor,
)
}
return utils.ChainStreamServerInterceptors(
otelgrpc.StreamServerInterceptor(),
utils.GRPCServerStreamErrorInterceptor,
a.Limiter.StreamServerInterceptor,
a.withAuthenticatedUserStreamInterceptor,
)
}
// authenticatedStream wraps around the embedded grpc.ServerStream
// provides new context with additional metadata
type authenticatedStream struct {
ctx context.Context
grpc.ServerStream
}
// Context specifies stream context with authentication metadata
func (a *authenticatedStream) Context() context.Context {
return a.ctx
}
// GetUser returns authenticated user based on request TLS metadata
func (a *Middleware) GetUser(connState tls.ConnectionState) (authz.IdentityGetter, error) {
peers := connState.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 authz.BuiltinRole{
Role: types.RoleNop,
Username: string(types.RoleNop),
ClusterName: a.ClusterName,
Identity: tlsca.Identity{},
}, nil
}
clientCert := peers[0]
identity, err := tlsca.FromSubject(clientCert.Subject, clientCert.NotAfter)
if err != nil {
return nil, trace.Wrap(err)
}
// Since 5.0, teleport TLS certs include the origin teleport cluster in the
// subject (identity). Before 5.0, origin teleport cluster was inferred
// from the cert issuer.
certClusterName := identity.TeleportCluster
if certClusterName == "" {
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.TeleportCluster = certClusterName
}
// 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 && !slices.Equal(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 != a.ClusterName {
// 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 := findPrimarySystemRole(identity.Groups)
if systemRole != nil {
return authz.RemoteBuiltinRole{
Role: *systemRole,
Username: identity.Username,
ClusterName: certClusterName,
Identity: *identity,
}, nil
}
return authz.RemoteUser{
ClusterName: certClusterName,
Username: identity.Username,
Principals: identity.Principals,
KubernetesGroups: identity.KubernetesGroups,
KubernetesUsers: identity.KubernetesUsers,
DatabaseNames: identity.DatabaseNames,
DatabaseUsers: identity.DatabaseUsers,
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 := findPrimarySystemRole(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 authz.BuiltinRole{
Role: *systemRole,
AdditionalSystemRoles: extractAdditionalSystemRoles(identity.SystemRoles),
Username: identity.Username,
ClusterName: a.ClusterName,
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 authz.LocalUser{
Username: identity.Username,
Identity: *identity,
}, nil
}
func findPrimarySystemRole(roles []string) *types.SystemRole {
for _, role := range roles {
systemRole := types.SystemRole(role)
err := systemRole.Check()
if err == nil {
return &systemRole
}
}
return nil
}
func extractAdditionalSystemRoles(roles []string) types.SystemRoles {
var systemRoles types.SystemRoles
for _, role := range roles {
systemRole := types.SystemRole(role)
err := systemRole.Check()
if err != nil {
// ignore unknown system roles rather than rejecting them, since new unknown system
// roles may be present on certs if we rolled back from a newer version.
log.Warnf("Ignoring unknown system role: %q", role)
continue
}
systemRoles = append(systemRoles, systemRole)
}
return systemRoles
}
// ServeHTTP serves HTTP requests
func (a *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil {
trace.WriteError(w, trace.AccessDenied("missing authentication"))
return
}
user, err := a.GetUser(*r.TLS)
if err != nil {
trace.WriteError(w, err)
return
}
// determine authenticated user based on the request parameters
ctx := r.Context()
ctx = authz.ContextWithUserCertificate(ctx, certFromConnState(r.TLS))
clientSrcAddr, err := utils.ParseAddr(r.RemoteAddr)
if err == nil {
ctx = authz.ContextWithClientAddr(ctx, clientSrcAddr)
}
ctx = authz.ContextWithUser(ctx, user)
a.Handler.ServeHTTP(w, r.WithContext(ctx))
}
// WrapContextWithUser enriches the provided context with the identity information
// extracted from the provided TLS connection.
func (a *Middleware) WrapContextWithUser(ctx context.Context, conn utils.TLSConn) (context.Context, error) {
// Perform the handshake if it hasn't been already. Before the handshake we
// won't have client certs available.
if !conn.ConnectionState().HandshakeComplete {
if err := conn.HandshakeContext(ctx); err != nil {
return nil, trace.ConvertSystemError(err)
}
}
return a.WrapContextWithUserFromTLSConnState(ctx, conn.ConnectionState(), conn.RemoteAddr())
}
// WrapContextWithUserFromTLSConnState enriches the provided context with the identity information
// extracted from the provided TLS connection state.
func (a *Middleware) WrapContextWithUserFromTLSConnState(ctx context.Context, tlsState tls.ConnectionState, remoteAddr net.Addr) (context.Context, error) {
user, err := a.GetUser(tlsState)
if err != nil {
return nil, trace.Wrap(err)
}
ctx = authz.ContextWithUserCertificate(ctx, certFromConnState(&tlsState))
ctx = authz.ContextWithClientAddr(ctx, remoteAddr)
ctx = authz.ContextWithUser(ctx, user)
return ctx, nil
}
// CheckIPPinning verifies IP pinning for the identity, using the client ip taken from context.
// Check is considered successful if no error is returned.
func CheckIPPinning(ctx context.Context, identity tlsca.Identity, pinSourceIP bool) error {
if identity.PinnedIP == "" {
if pinSourceIP {
return trace.AccessDenied("pinned IP is required for the user, but is not present on identity")
}
return nil
}
clientSrcAddr, err := authz.ClientAddrFromContext(ctx)
if err != nil {
return trace.Wrap(err)
}
clientIP, _, err := net.SplitHostPort(clientSrcAddr.String())
if err != nil {
return trace.Wrap(err)
}
if clientIP != identity.PinnedIP {
log.WithFields(logrus.Fields{
"client_ip": clientIP,
"pinned_ip": identity.PinnedIP,
}).Debug("Pinned IP and client IP mismatch")
return trace.AccessDenied("pinned IP doesn't match observed client IP")
}
return nil
}
// ClientCertPool returns trusted x509 certificate authority pool with CAs provided as caTypes.
// In addition, it returns the total length of all subjects added to the cert pool, allowing
// the caller to validate that the pool doesn't exceed the maximum 2-byte length prefix before
// using it.
func ClientCertPool(client AccessCache, clusterName string, caTypes ...types.CertAuthType) (*x509.CertPool, int64, error) {
if len(caTypes) == 0 {
return nil, 0, trace.BadParameter("at least one CA type is required")
}
ctx := context.TODO()
pool := x509.NewCertPool()
var authorities []types.CertAuthority
if clusterName == "" {
for _, caType := range caTypes {
cas, err := client.GetCertAuthorities(ctx, caType, false)
if err != nil {
return nil, 0, trace.Wrap(err)
}
authorities = append(authorities, cas...)
}
} else {
for _, caType := range caTypes {
ca, err := client.GetCertAuthority(
ctx,
types.CertAuthID{Type: caType, DomainName: clusterName},
false)
if err != nil {
return nil, 0, trace.Wrap(err)
}
authorities = append(authorities, ca)
}
}
var totalSubjectsLen int64
for _, auth := range authorities {
for _, keyPair := range auth.GetTrustedTLSKeyPairs() {
cert, err := tlsca.ParseCertificatePEM(keyPair.Cert)
if err != nil {
return nil, 0, trace.Wrap(err)
}
log.Debugf("ClientCertPool -> %v", CertInfo(cert))
pool.AddCert(cert)
// Each subject in the list gets a separate 2-byte length prefix.
totalSubjectsLen += 2
totalSubjectsLen += int64(len(cert.RawSubject))
}
}
return pool, totalSubjectsLen, nil
}
// DefaultClientCertPool returns default trusted x509 certificate authority pool.
func DefaultClientCertPool(client AccessCache, clusterName string) (*x509.CertPool, int64, error) {
return ClientCertPool(client, clusterName, types.HostCA, types.UserCA)
}