mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-03 07:40:28 +08:00
* Move the authorizer into its own package. The Authorizer and associated functions and constants have been moved into their own package `authz`. This will allow us to more easily create separate services for gRPC functionality so that we're not pushing as much into the `lib/auth` package. Though this PR is large, the only actual code change here is making `ContextForLocalUser` exported and changing the `permissions_test.go` so that it doesn't rely on the `lib/auth` package. * Use context functions. * Test and linting fixes. * Add in a temporary alias file to ensure e still compiles. * Use functions instead of var aliases for performance reasons. * Adding in godocs, cleaning up tests. * Fix tests. * Add a few more aliases.
254 lines
9.2 KiB
Go
254 lines
9.2 KiB
Go
// Copyright 2023 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"
|
|
"io"
|
|
"net"
|
|
|
|
"github.com/gravitational/trace"
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
apievents "github.com/gravitational/teleport/api/types/events"
|
|
"github.com/gravitational/teleport/lib/authz"
|
|
)
|
|
|
|
// UserGetter is responsible for building an authenticated user based on TLS metadata
|
|
type UserGetter interface {
|
|
GetUser(connState tls.ConnectionState) (authz.IdentityGetter, error)
|
|
}
|
|
|
|
// ConnectionIdentity contains the identifying properties of a
|
|
// client connection required to enforce connection limits.
|
|
type ConnectionIdentity struct {
|
|
// Username is the name of the user
|
|
Username string
|
|
// MaxConnections the upper limit to number of open connections for a user
|
|
MaxConnections int64
|
|
// LocalAddr is the local address for the connection
|
|
LocalAddr string
|
|
// RemoteAddr is the remote address for the connection
|
|
RemoteAddr string
|
|
// UserMetadata contains metadata for a user
|
|
UserMetadata apievents.UserMetadata
|
|
}
|
|
|
|
// ConnectionEnforcer limits incoming connections based on
|
|
// max connection settings.
|
|
type ConnectionEnforcer interface {
|
|
EnforceConnectionLimits(ctx context.Context, identity ConnectionIdentity, closers ...io.Closer) (context.Context, error)
|
|
}
|
|
|
|
// TransportCredentialsConfig configures the behavior that occurs
|
|
// during the server handshake by the TransportCredentials
|
|
type TransportCredentialsConfig struct {
|
|
// TransportCredentials provide the credentials that are used to perform the TLS
|
|
// server and client handshakes as well as the [credentials.ProtocolInfo]. This
|
|
// **MUST** not be nil, and it must have its [credentials.ProtocolInfo.SecurityProtocol]
|
|
// equal to "tls".
|
|
TransportCredentials credentials.TransportCredentials
|
|
// UserGetter constructs the clients' [tlsca.Identity] from the [tls.ConnectionState]
|
|
// that is received from the TLS handshake. This
|
|
UserGetter UserGetter
|
|
// Authorizer prevents any connections from being established if the user is not
|
|
// authorized due to locks, private key policy, device trust, etc. If not set
|
|
// then no authorization is performed.
|
|
Authorizer authz.Authorizer
|
|
// Enforcer prevents any connections from being established if the user would
|
|
// exceed their configured max connection limit. Any connections that are
|
|
// permitted may be terminated if there is an issue determining if the number
|
|
// of active connections is within the limit. If not set then no connection
|
|
// limits are enforced.
|
|
Enforcer ConnectionEnforcer
|
|
}
|
|
|
|
// Check validates that the configuration is valid for use and
|
|
// that all supplied parameters are set accordingly.
|
|
func (c *TransportCredentialsConfig) Check() error {
|
|
switch {
|
|
case c.TransportCredentials == nil:
|
|
return trace.BadParameter("parameter TransportCredentials required")
|
|
case c.TransportCredentials.Info().SecurityProtocol != "tls":
|
|
return trace.BadParameter("the TransportCredentials must be a tls security protocol, got %s", c.TransportCredentials.Info().SecurityProtocol)
|
|
case c.UserGetter == nil:
|
|
return trace.BadParameter("parameter UserGetter required")
|
|
case c.Authorizer != nil && c.UserGetter == nil:
|
|
return trace.BadParameter("a UserGetter is required to use validate identities with an Authorizer")
|
|
case c.Enforcer != nil && c.Authorizer == nil:
|
|
return trace.BadParameter("both a UserGetter and an Authorizer are required to enforce connection limits with an Enforcer")
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// TransportCredentials is a [credentials.TransportCredentials] that
|
|
// enforces mTLS and retrieves the [IdentityGetter] for use by middleware
|
|
// to perform authorization.
|
|
type TransportCredentials struct {
|
|
credentials.TransportCredentials
|
|
|
|
userGetter UserGetter
|
|
authorizer authz.Authorizer
|
|
enforcer ConnectionEnforcer
|
|
}
|
|
|
|
// NewTransportCredentials returns a new TransportCredentials
|
|
func NewTransportCredentials(cfg TransportCredentialsConfig) (*TransportCredentials, error) {
|
|
if err := cfg.Check(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &TransportCredentials{
|
|
TransportCredentials: cfg.TransportCredentials,
|
|
userGetter: cfg.UserGetter,
|
|
authorizer: cfg.Authorizer,
|
|
enforcer: cfg.Enforcer,
|
|
}, nil
|
|
}
|
|
|
|
// IdentityInfo contains the auth information and identity
|
|
// for an authenticated TLS connection. It implements the
|
|
// [credentials.AuthInfo] interface and is returned from
|
|
// [TransportCredentials.ServerHandshake].
|
|
type IdentityInfo struct {
|
|
// TLSInfo contains TLS connection information.
|
|
*credentials.TLSInfo
|
|
// IdentityGetter provides a mechanism to retrieve the
|
|
// identity of the client.
|
|
IdentityGetter authz.IdentityGetter
|
|
// AuthContext contains information about the traits and roles
|
|
// that an identity may have. This will be unset if the
|
|
// [TransportCredentialsConfig.Authorizer] provided to [NewTransportCredentials]
|
|
// was nil.
|
|
AuthContext *authz.Context
|
|
}
|
|
|
|
// ServerHandshake does the authentication handshake for servers. It returns
|
|
// the authenticated connection and the corresponding auth information about
|
|
// the connection.
|
|
// At minimum the TLS handshake is performed and the identity is built from
|
|
// the [tls.ConnectionState]. If the TransportCredentials is configured with
|
|
// and Authorizer and ConnectionEnforcer then additional session controls are
|
|
// applied before the handshake completes.
|
|
func (c *TransportCredentials) ServerHandshake(rawConn net.Conn) (_ net.Conn, _ credentials.AuthInfo, err error) {
|
|
conn, tlsInfo, err := c.performTLSHandshake(rawConn)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
conn.Close()
|
|
}
|
|
}()
|
|
|
|
identityGetter, err := c.userGetter.GetUser(tlsInfo.State)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
authCtx, err := c.authorize(ctx, conn.RemoteAddr(), identityGetter, &tlsInfo.State)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := c.enforceConnectionLimits(ctx, authCtx, conn); err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
return conn, IdentityInfo{
|
|
TLSInfo: tlsInfo,
|
|
IdentityGetter: identityGetter,
|
|
AuthContext: authCtx,
|
|
}, nil
|
|
}
|
|
|
|
// performTLSHandshake does the TLS handshake and validates the
|
|
// returned [credentials.AuthInfo] are of type [credentials.TLSInfo].
|
|
func (c *TransportCredentials) performTLSHandshake(rawConn net.Conn) (net.Conn, *credentials.TLSInfo, error) {
|
|
conn, info, err := c.TransportCredentials.ServerHandshake(rawConn)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
tlsInfo, ok := info.(credentials.TLSInfo)
|
|
if !ok {
|
|
conn.Close()
|
|
return nil, nil, trace.BadParameter("unexpected type in tls auth info %T", info)
|
|
}
|
|
|
|
return conn, &tlsInfo, nil
|
|
}
|
|
|
|
// authorize enforces that the identity is not restricted from connecting due
|
|
// to things like locks, private key policy, device trust, etc. If the TransportCredentials
|
|
// was not configured to do authorization then this is a noop and will return nil, nil.
|
|
func (c *TransportCredentials) authorize(ctx context.Context, remoteAddr net.Addr, identityGetter authz.IdentityGetter, connState *tls.ConnectionState) (*authz.Context, error) {
|
|
if c.authorizer == nil {
|
|
return &authz.Context{
|
|
Identity: identityGetter,
|
|
}, nil
|
|
}
|
|
|
|
// construct a context with the keys expected by the Authorizer
|
|
ctx = authz.ContextWithUserCertificate(ctx, certFromConnState(connState))
|
|
ctx = authz.ContextWithClientAddr(ctx, remoteAddr)
|
|
ctx = authz.ContextWithUser(ctx, identityGetter)
|
|
|
|
authCtx, err := c.authorizer.Authorize(ctx)
|
|
return authCtx, trace.Wrap(err)
|
|
}
|
|
|
|
// enforceConnectionLimits prevents the identity from exceeding any configured
|
|
// connection limits. The provided connection will be closed by the enforcer
|
|
// if connectivity to Auth is interrupted. If the TransportCredentials
|
|
// was not configured to do connection limiting then this is a noop and will return nil.
|
|
func (c *TransportCredentials) enforceConnectionLimits(ctx context.Context, authCtx *authz.Context, conn net.Conn) error {
|
|
if c.enforcer == nil {
|
|
return nil
|
|
}
|
|
|
|
if authCtx == nil || authCtx.Checker == nil {
|
|
return trace.BadParameter("unable to determine connection limits without a valid auth context")
|
|
}
|
|
|
|
_, err := c.enforcer.EnforceConnectionLimits(
|
|
ctx,
|
|
ConnectionIdentity{
|
|
Username: authCtx.User.GetName(),
|
|
MaxConnections: authCtx.Checker.MaxConnections(),
|
|
LocalAddr: conn.LocalAddr().String(),
|
|
RemoteAddr: conn.RemoteAddr().String(),
|
|
UserMetadata: authz.ClientUserMetadata(ctx),
|
|
},
|
|
conn,
|
|
)
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// Clone makes a copy of this TransportCredentials.
|
|
func (c *TransportCredentials) Clone() credentials.TransportCredentials {
|
|
return &TransportCredentials{
|
|
userGetter: c.userGetter,
|
|
authorizer: c.authorizer,
|
|
enforcer: c.enforcer,
|
|
TransportCredentials: c.TransportCredentials.Clone(),
|
|
}
|
|
}
|