mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
* Add UpdateUser rpc to proto * Differentiate between create and update in github,oidc,saml * Edit updated_by event field to be more generic (used with contexts to capture user modifying records) * Update security issue by removing secrets from user when update/upsert/create (forrest) * Update createUser in resource_command and require force for updates
1690 lines
54 KiB
Go
1690 lines
54 KiB
Go
/*
|
|
Copyright 2015-2019 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 implements certificate signing authority and access control server
|
|
// Authority server is composed of several parts:
|
|
//
|
|
// * Authority server itself that implements signing and acl logic
|
|
// * HTTP server wrapper for authority server
|
|
// * HTTP client wrapper
|
|
//
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/subtle"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/events"
|
|
"github.com/gravitational/teleport/lib/limiter"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/services/local"
|
|
"github.com/gravitational/teleport/lib/sshca"
|
|
"github.com/gravitational/teleport/lib/sshutils"
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
"github.com/gravitational/teleport/lib/wrappers"
|
|
|
|
"github.com/coreos/go-oidc/oauth2"
|
|
"github.com/coreos/go-oidc/oidc"
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
saml2 "github.com/russellhaering/gosaml2"
|
|
"github.com/tstranex/u2f"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// AuthServerOption allows setting options as functional arguments to AuthServer
|
|
type AuthServerOption func(*AuthServer)
|
|
|
|
// NewAuthServer creates and configures a new AuthServer instance
|
|
func NewAuthServer(cfg *InitConfig, opts ...AuthServerOption) (*AuthServer, error) {
|
|
if cfg.Trust == nil {
|
|
cfg.Trust = local.NewCAService(cfg.Backend)
|
|
}
|
|
if cfg.Presence == nil {
|
|
cfg.Presence = local.NewPresenceService(cfg.Backend)
|
|
}
|
|
if cfg.Provisioner == nil {
|
|
cfg.Provisioner = local.NewProvisioningService(cfg.Backend)
|
|
}
|
|
if cfg.Identity == nil {
|
|
cfg.Identity = local.NewIdentityService(cfg.Backend)
|
|
}
|
|
if cfg.Access == nil {
|
|
cfg.Access = local.NewAccessService(cfg.Backend)
|
|
}
|
|
if cfg.DynamicAccess == nil {
|
|
cfg.DynamicAccess = local.NewDynamicAccessService(cfg.Backend)
|
|
}
|
|
if cfg.ClusterConfiguration == nil {
|
|
cfg.ClusterConfiguration = local.NewClusterConfigurationService(cfg.Backend)
|
|
}
|
|
if cfg.Events == nil {
|
|
cfg.Events = local.NewEventsService(cfg.Backend)
|
|
}
|
|
if cfg.AuditLog == nil {
|
|
cfg.AuditLog = events.NewDiscardAuditLog()
|
|
}
|
|
|
|
limiter, err := limiter.NewConnectionsLimiter(limiter.LimiterConfig{
|
|
MaxConnections: defaults.LimiterMaxConcurrentSignatures,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
closeCtx, cancelFunc := context.WithCancel(context.TODO())
|
|
as := AuthServer{
|
|
bk: cfg.Backend,
|
|
limiter: limiter,
|
|
Authority: cfg.Authority,
|
|
AuthServiceName: cfg.AuthServiceName,
|
|
oidcClients: make(map[string]*oidcClient),
|
|
samlProviders: make(map[string]*samlProvider),
|
|
githubClients: make(map[string]*githubClient),
|
|
cancelFunc: cancelFunc,
|
|
closeCtx: closeCtx,
|
|
AuthServices: AuthServices{
|
|
Trust: cfg.Trust,
|
|
Presence: cfg.Presence,
|
|
Provisioner: cfg.Provisioner,
|
|
Identity: cfg.Identity,
|
|
Access: cfg.Access,
|
|
DynamicAccess: cfg.DynamicAccess,
|
|
ClusterConfiguration: cfg.ClusterConfiguration,
|
|
IAuditLog: cfg.AuditLog,
|
|
Events: cfg.Events,
|
|
},
|
|
}
|
|
for _, o := range opts {
|
|
o(&as)
|
|
}
|
|
if as.clock == nil {
|
|
as.clock = clockwork.NewRealClock()
|
|
}
|
|
|
|
return &as, nil
|
|
}
|
|
|
|
type AuthServices struct {
|
|
services.Trust
|
|
services.Presence
|
|
services.Provisioner
|
|
services.Identity
|
|
services.Access
|
|
services.DynamicAccess
|
|
services.ClusterConfiguration
|
|
services.Events
|
|
events.IAuditLog
|
|
}
|
|
|
|
var (
|
|
generateRequestsCount = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Name: teleport.MetricGenerateRequests,
|
|
Help: "Number of requests to generate new server keys",
|
|
},
|
|
)
|
|
generateThrottledRequestsCount = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Name: teleport.MetricGenerateRequestsThrottled,
|
|
Help: "Number of throttled requests to generate new server keys",
|
|
},
|
|
)
|
|
generateRequestsCurrent = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Name: teleport.MetricGenerateRequestsCurrent,
|
|
Help: "Number of current generate requests",
|
|
},
|
|
)
|
|
generateRequestsLatencies = prometheus.NewHistogram(
|
|
prometheus.HistogramOpts{
|
|
Name: teleport.MetricGenerateRequestsHistogram,
|
|
Help: "Latency for generate requests",
|
|
// lowest bucket start of upper bound 0.001 sec (1 ms) with factor 2
|
|
// highest bucket start of 0.001 sec * 2^15 == 32.768 sec
|
|
Buckets: prometheus.ExponentialBuckets(0.001, 2, 16),
|
|
},
|
|
)
|
|
)
|
|
|
|
// AuthServer keeps the cluster together. It acts as a certificate authority (CA) for
|
|
// a cluster and:
|
|
// - generates the keypair for the node it's running on
|
|
// - invites other SSH nodes to a cluster, by issuing invite tokens
|
|
// - adds other SSH nodes to a cluster, by checking their token and signing their keys
|
|
// - same for users and their sessions
|
|
// - checks public keys to see if they're signed by it (can be trusted or not)
|
|
type AuthServer struct {
|
|
lock sync.RWMutex
|
|
oidcClients map[string]*oidcClient
|
|
samlProviders map[string]*samlProvider
|
|
githubClients map[string]*githubClient
|
|
clock clockwork.Clock
|
|
bk backend.Backend
|
|
|
|
closeCtx context.Context
|
|
cancelFunc context.CancelFunc
|
|
|
|
sshca.Authority
|
|
|
|
// AuthServiceName is a human-readable name of this CA. If several Auth services are running
|
|
// (managing multiple teleport clusters) this field is used to tell them apart in UIs
|
|
// It usually defaults to the hostname of the machine the Auth service runs on.
|
|
AuthServiceName string
|
|
|
|
// AuthServices encapsulate services - provisioner, trust, etc
|
|
// used by the auth server in a separate structure
|
|
AuthServices
|
|
|
|
// privateKey is used in tests to use pre-generated private keys
|
|
privateKey []byte
|
|
|
|
// cipherSuites is a list of ciphersuites that the auth server supports.
|
|
cipherSuites []uint16
|
|
|
|
// cache is a fast cache that allows auth server
|
|
// to use cache for most frequent operations,
|
|
// if not set, cache uses itself
|
|
cache AuthCache
|
|
|
|
limiter *limiter.ConnectionsLimiter
|
|
}
|
|
|
|
// SetCache sets cache used by auth server
|
|
func (a *AuthServer) SetCache(clt AuthCache) {
|
|
a.lock.Lock()
|
|
defer a.lock.Unlock()
|
|
a.cache = clt
|
|
}
|
|
|
|
// GetCache returns cache used by auth server
|
|
func (a *AuthServer) GetCache() AuthCache {
|
|
a.lock.RLock()
|
|
defer a.lock.RUnlock()
|
|
if a.cache == nil {
|
|
return &a.AuthServices
|
|
}
|
|
return a.cache
|
|
}
|
|
|
|
// runPeriodicOperations runs some periodic bookkeeping operations
|
|
// performed by auth server
|
|
func (a *AuthServer) runPeriodicOperations() {
|
|
// run periodic functions with a semi-random period
|
|
// to avoid contention on the database in case if there are multiple
|
|
// auth servers running - so they don't compete trying
|
|
// to update the same resources.
|
|
r := rand.New(rand.NewSource(a.GetClock().Now().UnixNano()))
|
|
period := defaults.HighResPollingPeriod + time.Duration(r.Intn(int(defaults.HighResPollingPeriod/time.Second)))*time.Second
|
|
log.Debugf("Ticking with period: %v.", period)
|
|
ticker := time.NewTicker(period)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-a.closeCtx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
err := a.autoRotateCertAuthorities()
|
|
if err != nil {
|
|
if trace.IsCompareFailed(err) {
|
|
log.Debugf("Cert authority has been updated concurrently: %v.", err)
|
|
} else {
|
|
log.Errorf("Failed to perform cert rotation check: %v.", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *AuthServer) Close() error {
|
|
a.cancelFunc()
|
|
if a.bk != nil {
|
|
return trace.Wrap(a.bk.Close())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *AuthServer) GetClock() clockwork.Clock {
|
|
a.lock.RLock()
|
|
defer a.lock.RUnlock()
|
|
return a.clock
|
|
}
|
|
|
|
// SetClock sets clock, used in tests
|
|
func (a *AuthServer) SetClock(clock clockwork.Clock) {
|
|
a.lock.Lock()
|
|
defer a.lock.Unlock()
|
|
a.clock = clock
|
|
}
|
|
|
|
// SetAuditLog sets the server's audit log
|
|
func (a *AuthServer) SetAuditLog(auditLog events.IAuditLog) {
|
|
a.IAuditLog = auditLog
|
|
}
|
|
|
|
// GetClusterConfig gets ClusterConfig from the backend.
|
|
func (a *AuthServer) GetClusterConfig(opts ...services.MarshalOption) (services.ClusterConfig, error) {
|
|
return a.GetCache().GetClusterConfig(opts...)
|
|
}
|
|
|
|
// GetClusterName returns the domain name that identifies this authority server.
|
|
// Also known as "cluster name"
|
|
func (a *AuthServer) GetClusterName(opts ...services.MarshalOption) (services.ClusterName, error) {
|
|
return a.GetCache().GetClusterName(opts...)
|
|
}
|
|
|
|
// GetDomainName returns the domain name that identifies this authority server.
|
|
// Also known as "cluster name"
|
|
func (a *AuthServer) GetDomainName() (string, error) {
|
|
clusterName, err := a.GetClusterName()
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
return clusterName.GetClusterName(), nil
|
|
}
|
|
|
|
// LocalCAResponse contains PEM-encoded local CAs.
|
|
type LocalCAResponse struct {
|
|
// TLSCA is the PEM-encoded TLS certificate authority.
|
|
TLSCA []byte `json:"tls_ca"`
|
|
}
|
|
|
|
// GetClusterCACert returns the CAs for the local cluster without signing keys.
|
|
func (a *AuthServer) GetClusterCACert() (*LocalCAResponse, error) {
|
|
clusterName, err := a.GetClusterName()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// Extract the TLS CA for this cluster.
|
|
hostCA, err := a.GetCache().GetCertAuthority(services.CertAuthID{
|
|
Type: services.HostCA,
|
|
DomainName: clusterName.GetClusterName(),
|
|
}, false)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tlsCA, err := hostCA.TLSCA()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Marshal to PEM bytes to send the CA over the wire.
|
|
pemBytes, err := tlsca.MarshalCertificatePEM(tlsCA.Cert)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &LocalCAResponse{
|
|
TLSCA: pemBytes,
|
|
}, nil
|
|
}
|
|
|
|
// GenerateHostCert uses the private key of the CA to sign the public key of the host
|
|
// (along with meta data like host ID, node name, roles, and ttl) to generate a host certificate.
|
|
func (s *AuthServer) GenerateHostCert(hostPublicKey []byte, hostID, nodeName string, principals []string, clusterName string, roles teleport.Roles, ttl time.Duration) ([]byte, error) {
|
|
domainName, err := s.GetDomainName()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// get the certificate authority that will be signing the public key of the host
|
|
ca, err := s.Trust.GetCertAuthority(services.CertAuthID{
|
|
Type: services.HostCA,
|
|
DomainName: domainName,
|
|
}, true)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("failed to load host CA for '%s': %v", domainName, err)
|
|
}
|
|
|
|
// get the private key of the certificate authority
|
|
caPrivateKey, err := ca.FirstSigningKey()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// create and sign!
|
|
return s.Authority.GenerateHostCert(services.HostCertParams{
|
|
PrivateCASigningKey: caPrivateKey,
|
|
PublicHostKey: hostPublicKey,
|
|
HostID: hostID,
|
|
NodeName: nodeName,
|
|
Principals: principals,
|
|
ClusterName: clusterName,
|
|
Roles: roles,
|
|
TTL: ttl,
|
|
})
|
|
}
|
|
|
|
// certs is a pair of SSH and TLS certificates
|
|
type certs struct {
|
|
// ssh is PEM encoded SSH certificate
|
|
ssh []byte
|
|
// tls is PEM encoded TLS certificate
|
|
tls []byte
|
|
}
|
|
|
|
type certRequest struct {
|
|
// user is a user to generate certificate for
|
|
user services.User
|
|
// checker is used to perform RBAC checks.
|
|
checker services.AccessChecker
|
|
// ttl is Duration of the certificate
|
|
ttl time.Duration
|
|
// publicKey is RSA public key in authorized_keys format
|
|
publicKey []byte
|
|
// compatibility is compatibility mode
|
|
compatibility string
|
|
// overrideRoleTTL is used for requests when the requested TTL should not be
|
|
// adjusted based off the role of the user. This is used by tctl to allow
|
|
// creating long lived user certs.
|
|
overrideRoleTTL bool
|
|
// usage is a list of acceptable usages to be encoded in X509 certificate,
|
|
// is used to limit ways the certificate can be used, for example
|
|
// the cert can be only used against kubernetes endpoint, and not auth endpoint,
|
|
// no usage means unrestricted (to keep backwards compatibility)
|
|
usage []string
|
|
// routeToCluster is an optional cluster name to route the certificate requests to,
|
|
// this cluster name will be used to route the requests to in case of kubernetes
|
|
routeToCluster string
|
|
// traits hold claim data used to populate a role at runtime.
|
|
traits wrappers.Traits
|
|
// activeRequests tracks privilege escalation requests applied
|
|
// during the construction of the certificate.
|
|
activeRequests services.RequestIDs
|
|
}
|
|
|
|
// GenerateUserTestCerts is used to generate user certificate, used internally for tests
|
|
func (a *AuthServer) GenerateUserTestCerts(key []byte, username string, ttl time.Duration, compatibility, routeToCluster string) ([]byte, []byte, error) {
|
|
user, err := a.Identity.GetUser(username, false)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
checker, err := services.FetchRoles(user.GetRoles(), a.Access, user.GetTraits())
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
certs, err := a.generateUserCert(certRequest{
|
|
user: user,
|
|
ttl: ttl,
|
|
compatibility: compatibility,
|
|
publicKey: key,
|
|
routeToCluster: routeToCluster,
|
|
checker: checker,
|
|
traits: user.GetTraits(),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
return certs.ssh, certs.tls, nil
|
|
}
|
|
|
|
// generateUserCert generates user certificates
|
|
func (s *AuthServer) generateUserCert(req certRequest) (*certs, error) {
|
|
// reuse the same RSA keys for SSH and TLS keys
|
|
cryptoPubKey, err := sshutils.CryptoPublicKey(req.publicKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// extract the passed in certificate format. if nothing was passed in, fetch
|
|
// the certificate format from the role.
|
|
certificateFormat, err := utils.CheckCertificateFormatFlag(req.compatibility)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if certificateFormat == teleport.CertificateFormatUnspecified {
|
|
certificateFormat = req.checker.CertificateFormat()
|
|
}
|
|
|
|
var sessionTTL time.Duration
|
|
var allowedLogins []string
|
|
|
|
// If the role TTL is ignored, do not restrict session TTL and allowed logins.
|
|
// The only caller setting this parameter should be "tctl auth sign".
|
|
// Otherwise set the session TTL to the smallest of all roles and
|
|
// then only grant access to allowed logins based on that.
|
|
if req.overrideRoleTTL {
|
|
// Take whatever was passed in. Pass in 0 to CheckLoginDuration so all
|
|
// logins are returned for the role set.
|
|
sessionTTL = req.ttl
|
|
allowedLogins, err = req.checker.CheckLoginDuration(0)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
} else {
|
|
// Adjust session TTL to the smaller of two values: the session TTL
|
|
// requested in tsh or the session TTL for the role.
|
|
sessionTTL = req.checker.AdjustSessionTTL(req.ttl)
|
|
|
|
// Return a list of logins that meet the session TTL limit. This means if
|
|
// the requested session TTL is larger than the max session TTL for a login,
|
|
// that login will not be included in the list of allowed logins.
|
|
allowedLogins, err = req.checker.CheckLoginDuration(sessionTTL)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
clusterName, err := s.GetDomainName()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ca, err := s.Trust.GetCertAuthority(services.CertAuthID{
|
|
Type: services.UserCA,
|
|
DomainName: clusterName,
|
|
}, true)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
privateKey, err := ca.FirstSigningKey()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sshCert, err := s.Authority.GenerateUserCert(services.UserCertParams{
|
|
PrivateCASigningKey: privateKey,
|
|
PublicUserKey: req.publicKey,
|
|
Username: req.user.GetName(),
|
|
AllowedLogins: allowedLogins,
|
|
TTL: sessionTTL,
|
|
Roles: req.checker.RoleNames(),
|
|
CertificateFormat: certificateFormat,
|
|
PermitPortForwarding: req.checker.CanPortForward(),
|
|
PermitAgentForwarding: req.checker.CanForwardAgents(),
|
|
RouteToCluster: req.routeToCluster,
|
|
Traits: req.traits,
|
|
ActiveRequests: req.activeRequests,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
userCA, err := s.Trust.GetCertAuthority(services.CertAuthID{
|
|
Type: services.UserCA,
|
|
DomainName: clusterName,
|
|
}, true)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// generate TLS certificate
|
|
tlsAuthority, err := userCA.TLSCA()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
identity := tlsca.Identity{
|
|
Username: req.user.GetName(),
|
|
Groups: req.checker.RoleNames(),
|
|
Principals: allowedLogins,
|
|
Usage: req.usage,
|
|
RouteToCluster: req.routeToCluster,
|
|
Traits: req.traits,
|
|
}
|
|
subject, err := identity.Subject()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
certRequest := tlsca.CertificateRequest{
|
|
Clock: s.clock,
|
|
PublicKey: cryptoPubKey,
|
|
Subject: subject,
|
|
NotAfter: s.clock.Now().UTC().Add(sessionTTL),
|
|
}
|
|
tlsCert, err := tlsAuthority.GenerateCertificate(certRequest)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &certs{ssh: sshCert, tls: tlsCert}, nil
|
|
}
|
|
|
|
// WithUserLock executes function authenticateFn that performs user authentication
|
|
// if authenticateFn returns non nil error, the login attempt will be logged in as failed.
|
|
// The only exception to this rule is ConnectionProblemError, in case if it occurs
|
|
// access will be denied, but login attempt will not be recorded
|
|
// this is done to avoid potential user lockouts due to backend failures
|
|
// In case if user exceeds defaults.MaxLoginAttempts
|
|
// the user account will be locked for defaults.AccountLockInterval
|
|
func (s *AuthServer) WithUserLock(username string, authenticateFn func() error) error {
|
|
user, err := s.Identity.GetUser(username, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
status := user.GetStatus()
|
|
if status.IsLocked && status.LockExpires.After(s.clock.Now().UTC()) {
|
|
return trace.AccessDenied("%v exceeds %v failed login attempts, locked until %v",
|
|
user.GetName(), defaults.MaxLoginAttempts, utils.HumanTimeFormat(status.LockExpires))
|
|
}
|
|
fnErr := authenticateFn()
|
|
if fnErr == nil {
|
|
// upon successful login, reset the failed attempt counter
|
|
err = s.DeleteUserLoginAttempts(username)
|
|
if !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
// do not lock user in case if DB is flaky or down
|
|
if trace.IsConnectionProblem(err) {
|
|
return trace.Wrap(fnErr)
|
|
}
|
|
// log failed attempt and possibly lock user
|
|
attempt := services.LoginAttempt{Time: s.clock.Now().UTC(), Success: false}
|
|
err = s.AddUserLoginAttempt(username, attempt, defaults.AttemptTTL)
|
|
if err != nil {
|
|
log.Error(trace.DebugReport(err))
|
|
return trace.Wrap(fnErr)
|
|
}
|
|
loginAttempts, err := s.Identity.GetUserLoginAttempts(username)
|
|
if err != nil {
|
|
log.Error(trace.DebugReport(err))
|
|
return trace.Wrap(fnErr)
|
|
}
|
|
if !services.LastFailed(defaults.MaxLoginAttempts, loginAttempts) {
|
|
log.Debugf("%v user has less than %v failed login attempts", username, defaults.MaxLoginAttempts)
|
|
return trace.Wrap(fnErr)
|
|
}
|
|
lockUntil := s.clock.Now().UTC().Add(defaults.AccountLockInterval)
|
|
message := fmt.Sprintf("%v exceeds %v failed login attempts, locked until %v",
|
|
username, defaults.MaxLoginAttempts, utils.HumanTimeFormat(status.LockExpires))
|
|
log.Debug(message)
|
|
user.SetLocked(lockUntil, "user has exceeded maximum failed login attempts")
|
|
err = s.Identity.UpsertUser(user)
|
|
if err != nil {
|
|
log.Error(trace.DebugReport(err))
|
|
return trace.Wrap(fnErr)
|
|
}
|
|
return trace.AccessDenied(message)
|
|
}
|
|
|
|
// PreAuthenticatedSignIn is for 2-way authentication methods like U2F where the password is
|
|
// already checked before issuing the second factor challenge
|
|
func (s *AuthServer) PreAuthenticatedSignIn(user string, identity *tlsca.Identity) (services.WebSession, error) {
|
|
roles, traits, err := services.ExtractFromIdentity(s, identity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, err := s.NewWebSession(user, roles, traits)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := s.UpsertWebSession(user, sess); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return sess.WithoutSecrets(), nil
|
|
}
|
|
|
|
func (s *AuthServer) U2FSignRequest(user string, password []byte) (*u2f.SignRequest, error) {
|
|
cap, err := s.GetAuthPreference()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
universalSecondFactor, err := cap.GetU2F()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = s.WithUserLock(user, func() error {
|
|
return s.CheckPasswordWOToken(user, password)
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
registration, err := s.GetU2FRegistration(user)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
challenge, err := u2f.NewChallenge(universalSecondFactor.AppID, universalSecondFactor.Facets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = s.UpsertU2FSignChallenge(user, challenge)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
u2fSignReq := challenge.SignRequest(*registration)
|
|
|
|
return u2fSignReq, nil
|
|
}
|
|
|
|
func (s *AuthServer) CheckU2FSignResponse(user string, response *u2f.SignResponse) error {
|
|
// before trying to register a user, see U2F is actually setup on the backend
|
|
cap, err := s.GetAuthPreference()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
_, err = cap.GetU2F()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
reg, err := s.GetU2FRegistration(user)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
counter, err := s.GetU2FRegistrationCounter(user)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
challenge, err := s.GetU2FSignChallenge(user)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
newCounter, err := reg.Authenticate(*response, *challenge, counter)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = s.UpsertU2FRegistrationCounter(user, newCounter)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExtendWebSession creates a new web session for a user based on a valid previous sessionID,
|
|
// method is used to renew the web session for a user
|
|
func (s *AuthServer) ExtendWebSession(user string, prevSessionID string, identity *tlsca.Identity) (services.WebSession, error) {
|
|
prevSession, err := s.GetWebSession(user, prevSessionID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// consider absolute expiry time that may be set for this session
|
|
// by some external identity serivce, so we can not renew this session
|
|
// any more without extra logic for renewal with external OIDC provider
|
|
expiresAt := prevSession.GetExpiryTime()
|
|
if !expiresAt.IsZero() && expiresAt.Before(s.clock.Now().UTC()) {
|
|
return nil, trace.NotFound("web session has expired")
|
|
}
|
|
|
|
roles, traits, err := services.ExtractFromIdentity(s, identity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, err := s.NewWebSession(user, roles, traits)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess.SetExpiryTime(expiresAt)
|
|
bearerTokenTTL := utils.MinTTL(utils.ToTTL(s.clock, expiresAt), BearerTokenTTL)
|
|
sess.SetBearerTokenExpiryTime(s.clock.Now().UTC().Add(bearerTokenTTL))
|
|
if err := s.UpsertWebSession(user, sess); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, err = services.GetWebSessionMarshaler().ExtendWebSession(sess)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return sess, nil
|
|
}
|
|
|
|
// CreateWebSession creates a new web session for user without any
|
|
// checks, is used by admins
|
|
func (s *AuthServer) CreateWebSession(user string) (services.WebSession, error) {
|
|
u, err := s.GetUser(user, false)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, err := s.NewWebSession(user, u.GetRoles(), u.GetTraits())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := s.UpsertWebSession(user, sess); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, err = services.GetWebSessionMarshaler().GenerateWebSession(sess)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return sess, nil
|
|
}
|
|
|
|
// GenerateTokenRequest is a request to generate auth token
|
|
type GenerateTokenRequest struct {
|
|
// Token if provided sets the token value, otherwise will be auto generated
|
|
Token string `json:"token"`
|
|
// Roles is a list of roles this token authenticates as
|
|
Roles teleport.Roles `json:"roles"`
|
|
// TTL is a time to live for token
|
|
TTL time.Duration `json:"ttl"`
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default values of request
|
|
func (req *GenerateTokenRequest) CheckAndSetDefaults() error {
|
|
for _, role := range req.Roles {
|
|
if err := role.Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
if req.TTL == 0 {
|
|
req.TTL = defaults.ProvisioningTokenTTL
|
|
}
|
|
if req.Token == "" {
|
|
token, err := utils.CryptoRandomHex(TokenLenBytes)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
req.Token = token
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GenerateToken generates multi-purpose authentication token
|
|
func (s *AuthServer) GenerateToken(req GenerateTokenRequest) (string, error) {
|
|
if err := req.CheckAndSetDefaults(); err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
token, err := services.NewProvisionToken(req.Token, req.Roles, s.clock.Now().UTC().Add(req.TTL))
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
if err := s.Provisioner.UpsertToken(token); err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
return req.Token, nil
|
|
}
|
|
|
|
// ExtractHostID returns host id based on the hostname
|
|
func ExtractHostID(hostName string, clusterName string) (string, error) {
|
|
suffix := "." + clusterName
|
|
if !strings.HasSuffix(hostName, suffix) {
|
|
return "", trace.BadParameter("expected suffix %q in %q", suffix, hostName)
|
|
}
|
|
return strings.TrimSuffix(hostName, suffix), nil
|
|
}
|
|
|
|
// HostFQDN consits of host UUID and cluster name joined via .
|
|
func HostFQDN(hostUUID, clusterName string) string {
|
|
return fmt.Sprintf("%v.%v", hostUUID, clusterName)
|
|
}
|
|
|
|
// GenerateServerKeysRequest is a request to generate server keys
|
|
type GenerateServerKeysRequest struct {
|
|
// HostID is a unique ID of the host
|
|
HostID string `json:"host_id"`
|
|
// NodeName is a user friendly host name
|
|
NodeName string `json:"node_name"`
|
|
// Roles is a list of roles assigned to node
|
|
Roles teleport.Roles `json:"roles"`
|
|
// AdditionalPrincipals is a list of additional principals
|
|
// to include in OpenSSH and X509 certificates
|
|
AdditionalPrincipals []string `json:"additional_principals"`
|
|
// DNSNames is a list of DNS names
|
|
// to include in the x509 client certificate
|
|
DNSNames []string `json:"dns_names"`
|
|
// PublicTLSKey is a PEM encoded public key
|
|
// used for TLS setup
|
|
PublicTLSKey []byte `json:"public_tls_key"`
|
|
// PublicSSHKey is a SSH encoded public key,
|
|
// if present will be signed as a return value
|
|
// otherwise, new public/private key pair will be generated
|
|
PublicSSHKey []byte `json:"public_ssh_key"`
|
|
// RemoteAddr is the IP address of the remote host requesting a host
|
|
// certificate. RemoteAddr is used to replace 0.0.0.0 in the list of
|
|
// additional principals.
|
|
RemoteAddr string `json:"remote_addr"`
|
|
// Rotation allows clients to send the certificate authority rotation state
|
|
// expected by client of the certificate authority backends, so auth servers
|
|
// can avoid situation when clients request certs assuming one
|
|
// state, and auth servers issue another
|
|
Rotation *services.Rotation `json:"rotation,omitempty"`
|
|
// NoCache is argument that only local callers can supply to bypass cache
|
|
NoCache bool `json:"-"`
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default values
|
|
func (req *GenerateServerKeysRequest) CheckAndSetDefaults() error {
|
|
if req.HostID == "" {
|
|
return trace.BadParameter("missing parameter HostID")
|
|
}
|
|
if len(req.Roles) != 1 {
|
|
return trace.BadParameter("expected only one system role, got %v", len(req.Roles))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GenerateServerKeys generates new host private keys and certificates (signed
|
|
// by the host certificate authority) for a node.
|
|
func (s *AuthServer) GenerateServerKeys(req GenerateServerKeysRequest) (*PackedKeys, error) {
|
|
if err := req.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := s.limiter.AcquireConnection(req.Roles.String()); err != nil {
|
|
generateThrottledRequestsCount.Inc()
|
|
log.Debugf("Node %q [%v] is rate limited: %v.", req.NodeName, req.HostID, req.Roles)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
defer s.limiter.ReleaseConnection(req.Roles.String())
|
|
|
|
// only observe latencies for non-throttled requests
|
|
start := s.clock.Now()
|
|
defer generateRequestsLatencies.Observe(time.Since(start).Seconds())
|
|
|
|
generateRequestsCount.Inc()
|
|
generateRequestsCurrent.Inc()
|
|
defer generateRequestsCurrent.Dec()
|
|
|
|
clusterName, err := s.GetClusterName()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// If the request contains 0.0.0.0, this implies an advertise IP was not
|
|
// specified on the node. Try and guess what the address by replacing 0.0.0.0
|
|
// with the RemoteAddr as known to the Auth Server.
|
|
if utils.SliceContainsStr(req.AdditionalPrincipals, defaults.AnyAddress) {
|
|
remoteHost, err := utils.Host(req.RemoteAddr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
req.AdditionalPrincipals = utils.ReplaceInSlice(
|
|
req.AdditionalPrincipals,
|
|
defaults.AnyAddress,
|
|
remoteHost)
|
|
}
|
|
|
|
var cryptoPubKey crypto.PublicKey
|
|
var privateKeyPEM, pubSSHKey []byte
|
|
if req.PublicSSHKey != nil || req.PublicTLSKey != nil {
|
|
_, _, _, _, err := ssh.ParseAuthorizedKey(req.PublicSSHKey)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("failed to parse SSH public key")
|
|
}
|
|
pubSSHKey = req.PublicSSHKey
|
|
cryptoPubKey, err = tlsca.ParsePublicKeyPEM(req.PublicTLSKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
} else {
|
|
// generate private key
|
|
privateKeyPEM, pubSSHKey, err = s.GenerateKeyPair("")
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// reuse the same RSA keys for SSH and TLS keys
|
|
cryptoPubKey, err = sshutils.CryptoPublicKey(pubSSHKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
}
|
|
|
|
// get the certificate authority that will be signing the public key of the host,
|
|
client := s.GetCache()
|
|
if req.NoCache {
|
|
client = &s.AuthServices
|
|
}
|
|
ca, err := client.GetCertAuthority(services.CertAuthID{
|
|
Type: services.HostCA,
|
|
DomainName: clusterName.GetClusterName(),
|
|
}, true)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("failed to load host CA for %q: %v", clusterName.GetClusterName(), err)
|
|
}
|
|
|
|
// could be a couple of scenarios, either client data is out of sync,
|
|
// or auth server is out of sync, either way, for now check that
|
|
// cache is out of sync, this will result in higher read rate
|
|
// to the backend, which is a fine tradeoff
|
|
if !req.NoCache && req.Rotation != nil && !req.Rotation.Matches(ca.GetRotation()) {
|
|
log.Debugf("Client sent rotation state %v, cache state is %v, using state from the DB.", req.Rotation, ca.GetRotation())
|
|
ca, err = s.GetCertAuthority(services.CertAuthID{
|
|
Type: services.HostCA,
|
|
DomainName: clusterName.GetClusterName(),
|
|
}, true)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("failed to load host CA for %q: %v", clusterName.GetClusterName(), err)
|
|
}
|
|
if !req.Rotation.Matches(ca.GetRotation()) {
|
|
return nil, trace.BadParameter("the client expected state is out of sync, server rotation state: %v, client rotation state: %v, re-register the client from scratch to fix the issue.", ca.GetRotation(), req.Rotation)
|
|
}
|
|
}
|
|
|
|
tlsAuthority, err := ca.TLSCA()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// get the private key of the certificate authority
|
|
caPrivateKey, err := ca.FirstSigningKey()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// generate hostSSH certificate
|
|
hostSSHCert, err := s.Authority.GenerateHostCert(services.HostCertParams{
|
|
PrivateCASigningKey: caPrivateKey,
|
|
PublicHostKey: pubSSHKey,
|
|
HostID: req.HostID,
|
|
NodeName: req.NodeName,
|
|
ClusterName: clusterName.GetClusterName(),
|
|
Roles: req.Roles,
|
|
Principals: req.AdditionalPrincipals,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// generate host TLS certificate
|
|
identity := tlsca.Identity{
|
|
Username: HostFQDN(req.HostID, clusterName.GetClusterName()),
|
|
Groups: req.Roles.StringSlice(),
|
|
}
|
|
subject, err := identity.Subject()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
certRequest := tlsca.CertificateRequest{
|
|
Clock: s.clock,
|
|
PublicKey: cryptoPubKey,
|
|
Subject: subject,
|
|
NotAfter: s.clock.Now().UTC().Add(defaults.CATTL),
|
|
DNSNames: append([]string{}, req.AdditionalPrincipals...),
|
|
}
|
|
// HTTPS requests need to specify DNS name that should be present in the
|
|
// certificate as one of the DNS Names. It is not known in advance,
|
|
// that is why there is a default one for all certificates
|
|
if req.Roles.Include(teleport.RoleAuth) || req.Roles.Include(teleport.RoleAdmin) {
|
|
certRequest.DNSNames = append(certRequest.DNSNames, "*."+teleport.APIDomain, teleport.APIDomain)
|
|
}
|
|
// Unlike additional pricinpals, DNS Names is x509 specific
|
|
// and is limited to auth servers and proxies
|
|
if req.Roles.Include(teleport.RoleAuth) || req.Roles.Include(teleport.RoleAdmin) || req.Roles.Include(teleport.RoleProxy) {
|
|
certRequest.DNSNames = append(certRequest.DNSNames, req.DNSNames...)
|
|
}
|
|
hostTLSCert, err := tlsAuthority.GenerateCertificate(certRequest)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &PackedKeys{
|
|
Key: privateKeyPEM,
|
|
Cert: hostSSHCert,
|
|
TLSCert: hostTLSCert,
|
|
TLSCACerts: services.TLSCerts(ca),
|
|
SSHCACerts: ca.GetCheckingKeys(),
|
|
}, nil
|
|
}
|
|
|
|
// ValidateToken takes a provisioning token value and finds if it's valid. Returns
|
|
// a list of roles this token allows its owner to assume, or an error if the token
|
|
// cannot be found.
|
|
func (s *AuthServer) ValidateToken(token string) (roles teleport.Roles, e error) {
|
|
tkns, err := s.GetCache().GetStaticTokens()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// First check if the token is a static token. If it is, return right away.
|
|
// Static tokens have no expiration.
|
|
for _, st := range tkns.GetStaticTokens() {
|
|
if subtle.ConstantTimeCompare([]byte(st.GetName()), []byte(token)) == 1 {
|
|
return st.GetRoles(), nil
|
|
}
|
|
}
|
|
|
|
// If it's not a static token, check if it's a ephemeral token in the backend.
|
|
// If a ephemeral token is found, make sure it's still valid.
|
|
tok, err := s.GetCache().GetToken(token)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if !s.checkTokenTTL(tok) {
|
|
return nil, trace.AccessDenied("token expired")
|
|
}
|
|
|
|
return tok.GetRoles(), nil
|
|
}
|
|
|
|
// checkTokenTTL checks if the token is still valid. If it is not, the token
|
|
// is removed from the backend and returns false. Otherwise returns true.
|
|
func (s *AuthServer) checkTokenTTL(tok services.ProvisionToken) bool {
|
|
now := s.clock.Now().UTC()
|
|
if tok.Expiry().Before(now) {
|
|
err := s.DeleteToken(tok.GetName())
|
|
if err != nil {
|
|
if !trace.IsNotFound(err) {
|
|
log.Warnf("Unable to delete token from backend: %v.", err)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// RegisterUsingTokenRequest is a request to register with
|
|
// auth server using authentication token
|
|
type RegisterUsingTokenRequest struct {
|
|
// HostID is a unique host ID, usually a UUID
|
|
HostID string `json:"hostID"`
|
|
// NodeName is a node name
|
|
NodeName string `json:"node_name"`
|
|
// Role is a system role, e.g. Proxy
|
|
Role teleport.Role `json:"role"`
|
|
// Token is an authentication token
|
|
Token string `json:"token"`
|
|
// AdditionalPrincipals is a list of additional principals
|
|
AdditionalPrincipals []string `json:"additional_principals"`
|
|
// DNSNames is a list of DNS names to include in the x509 client certificate
|
|
DNSNames []string `json:"dns_names"`
|
|
// PublicTLSKey is a PEM encoded public key
|
|
// used for TLS setup
|
|
PublicTLSKey []byte `json:"public_tls_key"`
|
|
// PublicSSHKey is a SSH encoded public key,
|
|
// if present will be signed as a return value
|
|
// otherwise, new public/private key pair will be generated
|
|
PublicSSHKey []byte `json:"public_ssh_key"`
|
|
// RemoteAddr is the remote address of the host requesting a host certificate.
|
|
// It is used to replace 0.0.0.0 in the list of additional principals.
|
|
RemoteAddr string `json:"remote_addr"`
|
|
}
|
|
|
|
// CheckAndSetDefaults checks for errors and sets defaults
|
|
func (r *RegisterUsingTokenRequest) CheckAndSetDefaults() error {
|
|
if r.HostID == "" {
|
|
return trace.BadParameter("missing parameter HostID")
|
|
}
|
|
if r.Token == "" {
|
|
return trace.BadParameter("missing parameter Token")
|
|
}
|
|
if err := r.Role.Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RegisterUsingToken adds a new node to the Teleport cluster using previously issued token.
|
|
// A node must also request a specific role (and the role must match one of the roles
|
|
// the token was generated for).
|
|
//
|
|
// If a token was generated with a TTL, it gets enforced (can't register new nodes after TTL expires)
|
|
// If a token was generated with a TTL=0, it means it's a single-use token and it gets destroyed
|
|
// after a successful registration.
|
|
func (s *AuthServer) RegisterUsingToken(req RegisterUsingTokenRequest) (*PackedKeys, error) {
|
|
log.Infof("Node %q [%v] is trying to join with role: %v.", req.NodeName, req.HostID, req.Role)
|
|
|
|
if err := req.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// make sure the token is valid
|
|
roles, err := s.ValidateToken(req.Token)
|
|
if err != nil {
|
|
log.Warningf("%q [%v] can not join the cluster with role %s, token error: %v", req.NodeName, req.HostID, req.Role, err)
|
|
return nil, trace.AccessDenied(fmt.Sprintf("%q [%v] can not join the cluster with role %s, the token is not valid", req.NodeName, req.HostID, req.Role))
|
|
}
|
|
|
|
// make sure the caller is requested the role allowed by the token
|
|
if !roles.Include(req.Role) {
|
|
msg := fmt.Sprintf("node %q [%v] can not join the cluster, the token does not allow %q role", req.NodeName, req.HostID, req.Role)
|
|
log.Warn(msg)
|
|
return nil, trace.BadParameter(msg)
|
|
}
|
|
|
|
// generate and return host certificate and keys
|
|
keys, err := s.GenerateServerKeys(GenerateServerKeysRequest{
|
|
HostID: req.HostID,
|
|
NodeName: req.NodeName,
|
|
Roles: teleport.Roles{req.Role},
|
|
AdditionalPrincipals: req.AdditionalPrincipals,
|
|
PublicTLSKey: req.PublicTLSKey,
|
|
PublicSSHKey: req.PublicSSHKey,
|
|
RemoteAddr: req.RemoteAddr,
|
|
DNSNames: req.DNSNames,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
log.Infof("Node %q [%v] has joined the cluster.", req.NodeName, req.HostID)
|
|
return keys, nil
|
|
}
|
|
|
|
func (s *AuthServer) RegisterNewAuthServer(token string) error {
|
|
tok, err := s.Provisioner.GetToken(token)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if !tok.GetRoles().Include(teleport.RoleAuth) {
|
|
return trace.AccessDenied("role does not match")
|
|
}
|
|
if err := s.DeleteToken(token); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *AuthServer) DeleteToken(token string) (err error) {
|
|
tkns, err := s.GetStaticTokens()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// is this a static token?
|
|
for _, st := range tkns.GetStaticTokens() {
|
|
if subtle.ConstantTimeCompare([]byte(st.GetName()), []byte(token)) == 1 {
|
|
return trace.BadParameter("token %s is statically configured and cannot be removed", token)
|
|
}
|
|
}
|
|
// delete reset password token:
|
|
if err = s.Identity.DeleteResetPasswordToken(context.TODO(), token); err == nil {
|
|
return nil
|
|
}
|
|
// delete node token:
|
|
if err = s.Provisioner.DeleteToken(token); err == nil {
|
|
return nil
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// GetTokens returns all tokens (machine provisioning ones and user invitation tokens). Machine
|
|
// tokens usually have "node roles", like auth,proxy,node and user invitation tokens have 'signup' role
|
|
func (s *AuthServer) GetTokens(opts ...services.MarshalOption) (tokens []services.ProvisionToken, err error) {
|
|
// get node tokens:
|
|
tokens, err = s.Provisioner.GetTokens()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// get static tokens:
|
|
tkns, err := s.GetStaticTokens()
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err == nil {
|
|
tokens = append(tokens, tkns.GetStaticTokens()...)
|
|
}
|
|
// get reset password tokens:
|
|
resetPasswordTokens, err := s.Identity.GetResetPasswordTokens(context.TODO())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// convert reset password tokens to machine tokens:
|
|
for _, t := range resetPasswordTokens {
|
|
roles := teleport.Roles{teleport.RoleSignup}
|
|
tok, err := services.NewProvisionToken(t.GetName(), roles, t.Expiry())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tokens = append(tokens, tok)
|
|
}
|
|
return tokens, nil
|
|
}
|
|
|
|
func (s *AuthServer) NewWebSession(username string, roles []string, traits wrappers.Traits) (services.WebSession, error) {
|
|
user, err := s.GetUser(username, false)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
checker, err := services.FetchRoles(roles, s.Access, traits)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
priv, pub, err := s.GetNewKeyPairFromPool()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sessionTTL := checker.AdjustSessionTTL(defaults.CertDuration)
|
|
certs, err := s.generateUserCert(certRequest{
|
|
user: user,
|
|
ttl: sessionTTL,
|
|
publicKey: pub,
|
|
checker: checker,
|
|
traits: traits,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
token, err := utils.CryptoRandomHex(TokenLenBytes)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
bearerToken, err := utils.CryptoRandomHex(TokenLenBytes)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
bearerTokenTTL := utils.MinTTL(sessionTTL, BearerTokenTTL)
|
|
return services.NewWebSession(token, services.WebSessionSpecV2{
|
|
User: user.GetName(),
|
|
Priv: priv,
|
|
Pub: certs.ssh,
|
|
TLSCert: certs.tls,
|
|
Expires: s.clock.Now().UTC().Add(sessionTTL),
|
|
BearerToken: bearerToken,
|
|
BearerTokenExpires: s.clock.Now().UTC().Add(bearerTokenTTL),
|
|
}), nil
|
|
}
|
|
|
|
func (s *AuthServer) UpsertWebSession(user string, sess services.WebSession) error {
|
|
return s.Identity.UpsertWebSession(user, sess.GetName(), sess)
|
|
}
|
|
|
|
func (s *AuthServer) GetWebSession(userName string, id string) (services.WebSession, error) {
|
|
return s.Identity.GetWebSession(userName, id)
|
|
}
|
|
|
|
func (s *AuthServer) GetWebSessionInfo(userName string, id string) (services.WebSession, error) {
|
|
sess, err := s.Identity.GetWebSession(userName, id)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return sess.WithoutSecrets(), nil
|
|
}
|
|
|
|
func (s *AuthServer) DeleteNamespace(namespace string) error {
|
|
if namespace == defaults.Namespace {
|
|
return trace.AccessDenied("can't delete default namespace")
|
|
}
|
|
nodes, err := s.Presence.GetNodes(namespace, services.SkipValidation())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if len(nodes) != 0 {
|
|
return trace.BadParameter("can't delete namespace %v that has %v registered nodes", namespace, len(nodes))
|
|
}
|
|
return s.Presence.DeleteNamespace(namespace)
|
|
}
|
|
|
|
func (s *AuthServer) DeleteWebSession(user string, id string) error {
|
|
return trace.Wrap(s.Identity.DeleteWebSession(user, id))
|
|
}
|
|
|
|
// NewWatcher returns a new event watcher. In case of an auth server
|
|
// this watcher will return events as seen by the auth server's
|
|
// in memory cache, not the backend.
|
|
func (a *AuthServer) NewWatcher(ctx context.Context, watch services.Watch) (services.Watcher, error) {
|
|
return a.GetCache().NewWatcher(ctx, watch)
|
|
}
|
|
|
|
func (a *AuthServer) DeleteRole(name string) error {
|
|
// check if this role is used by CA or Users
|
|
users, err := a.Identity.GetUsers(false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
for _, u := range users {
|
|
for _, r := range u.GetRoles() {
|
|
if r == name {
|
|
// Mask the actual error here as it could be used to enumerate users
|
|
// within the system.
|
|
log.Warnf("Failed to delete role: role %v is used by user %v.", name, u.GetName())
|
|
return trace.BadParameter("failed to delete role that still in use by a user. Check system server logs for more details.")
|
|
}
|
|
}
|
|
}
|
|
// check if it's used by some external cert authorities, e.g.
|
|
// cert authorities related to external cluster
|
|
cas, err := a.Trust.GetCertAuthorities(services.UserCA, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
for _, a := range cas {
|
|
for _, r := range a.GetRoles() {
|
|
if r == name {
|
|
// Mask the actual error here as it could be used to enumerate users
|
|
// within the system.
|
|
log.Warnf("Failed to delete role: role %v is used by user cert authority %v", name, a.GetClusterName())
|
|
return trace.BadParameter("failed to delete role that still in use by a user. Check system server logs for more details.")
|
|
}
|
|
}
|
|
}
|
|
return a.Access.DeleteRole(name)
|
|
}
|
|
|
|
func (a *AuthServer) CreateAccessRequest(ctx context.Context, req services.AccessRequest) error {
|
|
if err := services.ValidateAccessRequest(a, req); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
ttl, err := a.calculateMaxAccessTTL(req)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
now := a.clock.Now().UTC()
|
|
req.SetCreationTime(now)
|
|
exp := now.Add(ttl)
|
|
// Set acccess expiry if an allowable default was not provided.
|
|
if req.GetAccessExpiry().Before(now) || req.GetAccessExpiry().After(exp) {
|
|
req.SetAccessExpiry(exp)
|
|
}
|
|
// By default, resource expiry should match access expiry.
|
|
req.SetExpiry(req.GetAccessExpiry())
|
|
// If the access-request is in a pending state, then the expiry of the underlying resource
|
|
// is capped to to PendingAccessDuration in order to limit orphaned access requests.
|
|
if req.GetState().IsPending() {
|
|
pexp := now.Add(defaults.PendingAccessDuration)
|
|
if pexp.Before(req.Expiry()) {
|
|
req.SetExpiry(pexp)
|
|
}
|
|
}
|
|
if err := a.DynamicAccess.CreateAccessRequest(ctx, req); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
err = a.EmitAuditEvent(events.AccessRequestCreated, events.EventFields{
|
|
events.AccessRequestID: req.GetName(),
|
|
events.EventUser: req.GetUser(),
|
|
events.UserRoles: req.GetRoles(),
|
|
events.AccessRequestState: req.GetState().String(),
|
|
})
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
func (a *AuthServer) SetAccessRequestState(ctx context.Context, reqID string, state services.RequestState) error {
|
|
if err := a.DynamicAccess.SetAccessRequestState(ctx, reqID, state); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
updateBy, err := getUpdateBy(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fields := events.EventFields{
|
|
events.AccessRequestID: reqID,
|
|
events.AccessRequestState: state.String(),
|
|
events.UpdatedBy: updateBy,
|
|
}
|
|
if delegator := getDelegator(ctx); delegator != "" {
|
|
fields[events.AccessRequestDelegator] = delegator
|
|
}
|
|
err = a.EmitAuditEvent(events.AccessRequestUpdated, fields)
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// calculateMaxAccessTTL determines the maximum allowable TTL for a given access request
|
|
// based on the MaxSessionTTLs of the roles being requested (a access request's life cannot
|
|
// exceed the smallest allowable MaxSessionTTL value of the roles that it requests).
|
|
func (a *AuthServer) calculateMaxAccessTTL(req services.AccessRequest) (time.Duration, error) {
|
|
minTTL := defaults.MaxAccessDuration
|
|
for _, roleName := range req.GetRoles() {
|
|
role, err := a.GetRole(roleName)
|
|
if err != nil {
|
|
return 0, trace.Wrap(err)
|
|
}
|
|
roleTTL := time.Duration(role.GetOptions().MaxSessionTTL)
|
|
if roleTTL > 0 && roleTTL < minTTL {
|
|
minTTL = roleTTL
|
|
}
|
|
}
|
|
return minTTL, nil
|
|
}
|
|
|
|
// NewKeepAliver returns a new instance of keep aliver
|
|
func (a *AuthServer) NewKeepAliver(ctx context.Context) (services.KeepAliver, error) {
|
|
cancelCtx, cancel := context.WithCancel(ctx)
|
|
k := &authKeepAliver{
|
|
a: a,
|
|
ctx: cancelCtx,
|
|
cancel: cancel,
|
|
keepAlivesC: make(chan services.KeepAlive),
|
|
}
|
|
go k.forwardKeepAlives()
|
|
return k, nil
|
|
}
|
|
|
|
// GetCertAuthority returns certificate authority by given id. Parameter loadSigningKeys
|
|
// controls if signing keys are loaded
|
|
func (a *AuthServer) GetCertAuthority(id services.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (services.CertAuthority, error) {
|
|
return a.GetCache().GetCertAuthority(id, loadSigningKeys, opts...)
|
|
}
|
|
|
|
// GetCertAuthorities returns a list of authorities of a given type
|
|
// loadSigningKeys controls whether signing keys should be loaded or not
|
|
func (a *AuthServer) GetCertAuthorities(caType services.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]services.CertAuthority, error) {
|
|
return a.GetCache().GetCertAuthorities(caType, loadSigningKeys, opts...)
|
|
}
|
|
|
|
// GetStaticTokens gets the list of static tokens used to provision nodes.
|
|
func (a *AuthServer) GetStaticTokens() (services.StaticTokens, error) {
|
|
return a.GetCache().GetStaticTokens()
|
|
}
|
|
|
|
// GetToken finds and returns token by ID
|
|
func (a *AuthServer) GetToken(token string) (services.ProvisionToken, error) {
|
|
return a.GetCache().GetToken(token)
|
|
}
|
|
|
|
// GetRoles is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetRoles() ([]services.Role, error) {
|
|
return a.GetCache().GetRoles()
|
|
}
|
|
|
|
// GetRole is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetRole(name string) (services.Role, error) {
|
|
return a.GetCache().GetRole(name)
|
|
}
|
|
|
|
// GetNamespace returns namespace
|
|
func (a *AuthServer) GetNamespace(name string) (*services.Namespace, error) {
|
|
return a.GetCache().GetNamespace(name)
|
|
}
|
|
|
|
// GetNamespaces is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetNamespaces() ([]services.Namespace, error) {
|
|
return a.GetCache().GetNamespaces()
|
|
}
|
|
|
|
// GetNodes is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetNodes(namespace string, opts ...services.MarshalOption) ([]services.Server, error) {
|
|
return a.GetCache().GetNodes(namespace, opts...)
|
|
}
|
|
|
|
// GetReverseTunnels is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetReverseTunnels(opts ...services.MarshalOption) ([]services.ReverseTunnel, error) {
|
|
return a.GetCache().GetReverseTunnels(opts...)
|
|
}
|
|
|
|
// GetProxies is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetProxies() ([]services.Server, error) {
|
|
return a.GetCache().GetProxies()
|
|
}
|
|
|
|
// GetUser is a part of auth.AccessPoint implementation.
|
|
func (a *AuthServer) GetUser(name string, withSecrets bool) (user services.User, err error) {
|
|
return a.GetCache().GetUser(name, withSecrets)
|
|
}
|
|
|
|
// GetUsers is a part of auth.AccessPoint implementation
|
|
func (a *AuthServer) GetUsers(withSecrets bool) (users []services.User, err error) {
|
|
return a.GetCache().GetUsers(withSecrets)
|
|
}
|
|
|
|
// GetTunnelConnections is a part of auth.AccessPoint implementation
|
|
// GetTunnelConnections are not using recent cache as they are designed
|
|
// to be called periodically and always return fresh data
|
|
func (a *AuthServer) GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]services.TunnelConnection, error) {
|
|
return a.GetCache().GetTunnelConnections(clusterName, opts...)
|
|
}
|
|
|
|
// GetAllTunnelConnections is a part of auth.AccessPoint implementation
|
|
// GetAllTunnelConnections are not using recent cache, as they are designed
|
|
// to be called periodically and always return fresh data
|
|
func (a *AuthServer) GetAllTunnelConnections(opts ...services.MarshalOption) (conns []services.TunnelConnection, err error) {
|
|
return a.GetCache().GetAllTunnelConnections(opts...)
|
|
}
|
|
|
|
// authKeepAliver is a keep aliver using auth server directly
|
|
type authKeepAliver struct {
|
|
sync.RWMutex
|
|
a *AuthServer
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
keepAlivesC chan services.KeepAlive
|
|
err error
|
|
}
|
|
|
|
// KeepAlives returns a channel accepting keep alive requests
|
|
func (k *authKeepAliver) KeepAlives() chan<- services.KeepAlive {
|
|
return k.keepAlivesC
|
|
}
|
|
|
|
func (k *authKeepAliver) forwardKeepAlives() {
|
|
for {
|
|
select {
|
|
case <-k.a.closeCtx.Done():
|
|
k.Close()
|
|
return
|
|
case <-k.ctx.Done():
|
|
return
|
|
case keepAlive := <-k.keepAlivesC:
|
|
err := k.a.KeepAliveNode(k.ctx, keepAlive)
|
|
if err != nil {
|
|
k.closeWithError(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (k *authKeepAliver) closeWithError(err error) {
|
|
k.Close()
|
|
k.Lock()
|
|
defer k.Unlock()
|
|
k.err = err
|
|
}
|
|
|
|
// Error returns the error if keep aliver
|
|
// has been closed
|
|
func (k *authKeepAliver) Error() error {
|
|
k.RLock()
|
|
defer k.RUnlock()
|
|
return k.err
|
|
}
|
|
|
|
// Done returns channel that is closed whenever
|
|
// keep aliver is closed
|
|
func (k *authKeepAliver) Done() <-chan struct{} {
|
|
return k.ctx.Done()
|
|
}
|
|
|
|
// Close closes keep aliver and cancels all goroutines
|
|
func (k *authKeepAliver) Close() error {
|
|
k.cancel()
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
// BearerTokenTTL specifies standard bearer token to exist before
|
|
// it has to be renewed by the client
|
|
BearerTokenTTL = 10 * time.Minute
|
|
// TokenLenBytes is len in bytes of the invite token
|
|
TokenLenBytes = 16
|
|
)
|
|
|
|
// oidcClient is internal structure that stores OIDC client and its config
|
|
type oidcClient struct {
|
|
client *oidc.Client
|
|
config oidc.ClientConfig
|
|
}
|
|
|
|
// samlProvider is internal structure that stores SAML client and its config
|
|
type samlProvider struct {
|
|
provider *saml2.SAMLServiceProvider
|
|
connector services.SAMLConnector
|
|
}
|
|
|
|
// githubClient is internal structure that stores Github OAuth 2client and its config
|
|
type githubClient struct {
|
|
client *oauth2.Client
|
|
config oauth2.Config
|
|
}
|
|
|
|
// oidcConfigsEqual returns true if the provided OIDC configs are equal
|
|
func oidcConfigsEqual(a, b oidc.ClientConfig) bool {
|
|
if a.RedirectURL != b.RedirectURL {
|
|
return false
|
|
}
|
|
if a.Credentials.ID != b.Credentials.ID {
|
|
return false
|
|
}
|
|
if a.Credentials.Secret != b.Credentials.Secret {
|
|
return false
|
|
}
|
|
if len(a.Scope) != len(b.Scope) {
|
|
return false
|
|
}
|
|
for i := range a.Scope {
|
|
if a.Scope[i] != b.Scope[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// oauth2ConfigsEqual returns true if the provided OAuth2 configs are equal
|
|
func oauth2ConfigsEqual(a, b oauth2.Config) bool {
|
|
if a.Credentials.ID != b.Credentials.ID {
|
|
return false
|
|
}
|
|
if a.Credentials.Secret != b.Credentials.Secret {
|
|
return false
|
|
}
|
|
if a.RedirectURL != b.RedirectURL {
|
|
return false
|
|
}
|
|
if len(a.Scope) != len(b.Scope) {
|
|
return false
|
|
}
|
|
for i := range a.Scope {
|
|
if a.Scope[i] != b.Scope[i] {
|
|
return false
|
|
}
|
|
}
|
|
if a.AuthURL != b.AuthURL {
|
|
return false
|
|
}
|
|
if a.TokenURL != b.TokenURL {
|
|
return false
|
|
}
|
|
if a.AuthMethod != b.AuthMethod {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// isHTTPS checks if the scheme for a URL is https or not.
|
|
func isHTTPS(u string) error {
|
|
earl, err := url.Parse(u)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if earl.Scheme != "https" {
|
|
return trace.BadParameter("expected scheme https, got %q", earl.Scheme)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
// Metrics have to be registered to be exposed:
|
|
prometheus.MustRegister(generateRequestsCount)
|
|
prometheus.MustRegister(generateThrottledRequestsCount)
|
|
prometheus.MustRegister(generateRequestsCurrent)
|
|
prometheus.MustRegister(generateRequestsLatencies)
|
|
}
|