mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
Adds `UpsertCertAuthority` to the `teleport.v1.trust.TrustService` and deprecates the HTTP api. The `services.Trust` interface was also updated to include a `context.Context` in both `CreateCertAuthority` and `UpsertCertAuthority. To ensure backwards compatibility the `auth.Client` will first attempt to use the gRPC client and if that fails with a not implemented error it will fallback to using HTTP. Part of #6394
1162 lines
32 KiB
Go
1162 lines
32 KiB
Go
/*
|
|
Copyright 2017-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
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/api/breaker"
|
|
"github.com/gravitational/teleport/api/client"
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
"github.com/gravitational/teleport/api/constants"
|
|
"github.com/gravitational/teleport/api/types"
|
|
apiutils "github.com/gravitational/teleport/api/utils"
|
|
"github.com/gravitational/teleport/lib/auth/keystore"
|
|
"github.com/gravitational/teleport/lib/auth/native"
|
|
authority "github.com/gravitational/teleport/lib/auth/testauthority"
|
|
"github.com/gravitational/teleport/lib/authz"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/backend/memory"
|
|
"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/services/suite"
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// TestAuthServerConfig is auth server test config
|
|
type TestAuthServerConfig struct {
|
|
// ClusterName is cluster name
|
|
ClusterName string
|
|
// Dir is directory for local backend
|
|
Dir string
|
|
// AcceptedUsage is an optional list of restricted
|
|
// server usage
|
|
AcceptedUsage []string
|
|
// CipherSuites is the list of ciphers that the server supports.
|
|
CipherSuites []uint16
|
|
// Clock is used to control time in tests.
|
|
Clock clockwork.FakeClock
|
|
// ClusterNetworkingConfig allows a test to change the default
|
|
// networking configuration.
|
|
ClusterNetworkingConfig types.ClusterNetworkingConfig
|
|
// Streamer allows a test to set its own audit events streamer.
|
|
Streamer events.Streamer
|
|
// AuditLog allows a test to configure its own audit log.
|
|
AuditLog events.AuditLogSessionStreamer
|
|
// TraceClient allows a test to configure the trace client
|
|
TraceClient otlptrace.Client
|
|
// AuthPreferenceSpec is custom initial AuthPreference spec for the test.
|
|
AuthPreferenceSpec *types.AuthPreferenceSpecV2
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets defaults
|
|
func (cfg *TestAuthServerConfig) CheckAndSetDefaults() error {
|
|
if cfg.ClusterName == "" {
|
|
cfg.ClusterName = "localhost"
|
|
}
|
|
if cfg.Dir == "" {
|
|
return trace.BadParameter("missing parameter Dir")
|
|
}
|
|
if cfg.Clock == nil {
|
|
cfg.Clock = clockwork.NewFakeClock()
|
|
}
|
|
if len(cfg.CipherSuites) == 0 {
|
|
cfg.CipherSuites = utils.DefaultCipherSuites()
|
|
}
|
|
if cfg.AuthPreferenceSpec == nil {
|
|
cfg.AuthPreferenceSpec = &types.AuthPreferenceSpecV2{
|
|
Type: constants.Local,
|
|
SecondFactor: constants.SecondFactorOff,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TestServer defines the set of server components for a test
|
|
type TestServer struct {
|
|
TLS *TestTLSServer
|
|
AuthServer *TestAuthServer
|
|
}
|
|
|
|
// TestServerConfig defines the configuration for all server components
|
|
type TestServerConfig struct {
|
|
// Auth specifies the auth server configuration
|
|
Auth TestAuthServerConfig
|
|
// TLS optionally specifies the configuration for the TLS server.
|
|
// If unspecified, will be generated automatically
|
|
TLS *TestTLSServerConfig
|
|
}
|
|
|
|
// NewTestServer creates a new test server configuration
|
|
func NewTestServer(cfg TestServerConfig) (*TestServer, error) {
|
|
authServer, err := NewTestAuthServer(cfg.Auth)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// Set the (test) auth server in cfg.TLS and set any defaults that
|
|
// are not set.
|
|
tlsCfg := cfg.TLS
|
|
if tlsCfg == nil {
|
|
tlsCfg = &TestTLSServerConfig{}
|
|
}
|
|
if tlsCfg.APIConfig == nil {
|
|
tlsCfg.APIConfig = &APIConfig{}
|
|
}
|
|
|
|
tlsCfg.AuthServer = authServer
|
|
tlsCfg.APIConfig.AuthServer = authServer.AuthServer
|
|
|
|
if tlsCfg.APIConfig.Authorizer == nil {
|
|
tlsCfg.APIConfig.Authorizer = authServer.Authorizer
|
|
}
|
|
if tlsCfg.APIConfig.AuditLog == nil {
|
|
tlsCfg.APIConfig.AuditLog = authServer.AuditLog
|
|
}
|
|
if tlsCfg.APIConfig.Emitter == nil {
|
|
tlsCfg.APIConfig.Emitter = authServer.AuthServer.emitter
|
|
}
|
|
if tlsCfg.AcceptedUsage == nil {
|
|
tlsCfg.AcceptedUsage = authServer.AcceptedUsage
|
|
}
|
|
|
|
tlsServer, err := NewTestTLSServer(*tlsCfg)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &TestServer{
|
|
AuthServer: authServer,
|
|
TLS: tlsServer,
|
|
}, nil
|
|
}
|
|
|
|
// Auth returns the underlying auth server instance
|
|
func (a *TestServer) Auth() *Server {
|
|
return a.AuthServer.AuthServer
|
|
}
|
|
|
|
func (a *TestServer) NewClient(identity TestIdentity) (*Client, error) {
|
|
return a.TLS.NewClient(identity)
|
|
}
|
|
|
|
func (a *TestServer) ClusterName() string {
|
|
return a.TLS.ClusterName()
|
|
}
|
|
|
|
// Shutdown stops this server instance gracefully
|
|
func (a *TestServer) Shutdown(ctx context.Context) error {
|
|
return trace.NewAggregate(
|
|
a.TLS.Shutdown(ctx),
|
|
a.AuthServer.Close(),
|
|
)
|
|
}
|
|
|
|
// WithClock is a functional server option that sets the server's clock
|
|
func WithClock(clock clockwork.Clock) ServerOption {
|
|
return func(s *Server) error {
|
|
s.clock = clock
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// TestAuthServer is auth server using local filesystem backend
|
|
// and test certificate authority key generation that speeds up
|
|
// keygen by using the same private key
|
|
type TestAuthServer struct {
|
|
// TestAuthServer config is configuration used for auth server setup
|
|
TestAuthServerConfig
|
|
// AuthServer is an auth server
|
|
AuthServer *Server
|
|
// AuditLog is an event audit log
|
|
AuditLog events.AuditLogSessionStreamer
|
|
// Backend is a backend for auth server
|
|
Backend backend.Backend
|
|
// Authorizer is an authorizer used in tests
|
|
Authorizer authz.Authorizer
|
|
// LockWatcher is a lock watcher used in tests.
|
|
LockWatcher *services.LockWatcher
|
|
}
|
|
|
|
// NewTestAuthServer returns new instances of Auth server
|
|
func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
|
|
ctx := context.Background()
|
|
|
|
if err := cfg.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
srv := &TestAuthServer{
|
|
TestAuthServerConfig: cfg,
|
|
}
|
|
b, err := memory.New(memory.Config{
|
|
Context: ctx,
|
|
Clock: cfg.Clock,
|
|
EventsOff: false,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Wrap backend in sanitizer like in production.
|
|
srv.Backend = backend.NewSanitizer(b)
|
|
|
|
if cfg.AuditLog != nil {
|
|
srv.AuditLog = cfg.AuditLog
|
|
} else {
|
|
localLog, err := events.NewAuditLog(events.AuditLogConfig{
|
|
DataDir: cfg.Dir,
|
|
ServerID: cfg.ClusterName,
|
|
Clock: cfg.Clock,
|
|
UploadHandler: events.NewMemoryUploader(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
srv.AuditLog = localLog
|
|
}
|
|
|
|
access := local.NewAccessService(srv.Backend)
|
|
identity := local.NewIdentityService(srv.Backend)
|
|
|
|
emitter, err := events.NewCheckingEmitter(events.CheckingEmitterConfig{
|
|
Inner: srv.AuditLog,
|
|
Clock: cfg.Clock,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
srv.AuthServer, err = NewServer(&InitConfig{
|
|
Backend: srv.Backend,
|
|
Authority: authority.NewWithClock(cfg.Clock),
|
|
Access: access,
|
|
Identity: identity,
|
|
AuditLog: srv.AuditLog,
|
|
Streamer: cfg.Streamer,
|
|
SkipPeriodicOperations: true,
|
|
Emitter: emitter,
|
|
TraceClient: cfg.TraceClient,
|
|
KeyStoreConfig: keystore.Config{
|
|
Software: keystore.SoftwareConfig{
|
|
RSAKeyPairSource: authority.New().GenerateKeyPair,
|
|
},
|
|
},
|
|
}, WithClock(cfg.Clock))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = srv.AuthServer.SetClusterAuditConfig(ctx, types.DefaultClusterAuditConfig())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
clusterNetworkingCfg := cfg.ClusterNetworkingConfig
|
|
if clusterNetworkingCfg == nil {
|
|
clusterNetworkingCfg = types.DefaultClusterNetworkingConfig()
|
|
}
|
|
|
|
err = srv.AuthServer.SetClusterNetworkingConfig(ctx, clusterNetworkingCfg)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = srv.AuthServer.SetSessionRecordingConfig(ctx, types.DefaultSessionRecordingConfig())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{
|
|
ClusterName: cfg.ClusterName,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = srv.AuthServer.SetClusterName(clusterName)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
authPreference, err := types.NewAuthPreferenceFromConfigFile(*cfg.AuthPreferenceSpec)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = srv.AuthServer.SetAuthPreference(ctx, authPreference)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// set static tokens
|
|
staticTokens, err := types.NewStaticTokens(types.StaticTokensSpecV2{
|
|
StaticTokens: []types.ProvisionTokenV1{},
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = srv.AuthServer.SetStaticTokens(staticTokens)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// create the default role
|
|
err = srv.AuthServer.UpsertRole(ctx, services.NewImplicitRole())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Setup certificate and signing authorities.
|
|
for _, caType := range types.CertAuthTypes {
|
|
if err = srv.AuthServer.UpsertCertAuthority(ctx, suite.NewTestCAWithConfig(suite.TestCAConfig{
|
|
Type: caType,
|
|
ClusterName: srv.ClusterName,
|
|
Clock: cfg.Clock,
|
|
})); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
srv.LockWatcher, err = services.NewLockWatcher(ctx, services.LockWatcherConfig{
|
|
ResourceWatcherConfig: services.ResourceWatcherConfig{
|
|
Component: teleport.ComponentAuth,
|
|
Client: srv.AuthServer,
|
|
Clock: cfg.Clock,
|
|
MaxRetryPeriod: defaults.HighResPollingPeriod,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
srv.AuthServer.SetLockWatcher(srv.LockWatcher)
|
|
|
|
headlessAuthenticationWatcher, err := local.NewHeadlessAuthenticationWatcher(ctx, local.HeadlessAuthenticationWatcherConfig{
|
|
Backend: b,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
srv.AuthServer.SetHeadlessAuthenticationWatcher(headlessAuthenticationWatcher)
|
|
|
|
srv.Authorizer, err = authz.NewAuthorizer(authz.AuthorizerOpts{
|
|
ClusterName: srv.ClusterName,
|
|
AccessPoint: srv.AuthServer,
|
|
LockWatcher: srv.LockWatcher,
|
|
// AuthServer does explicit device authorization checks.
|
|
DisableDeviceAuthorization: true,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return srv, nil
|
|
}
|
|
|
|
func (a *TestAuthServer) Close() error {
|
|
defer a.LockWatcher.Close()
|
|
|
|
return trace.NewAggregate(
|
|
a.AuthServer.Close(),
|
|
a.Backend.Close(),
|
|
a.AuditLog.Close(),
|
|
)
|
|
}
|
|
|
|
// GenerateUserCert takes the public key in the OpenSSH `authorized_keys`
|
|
// plain text format, signs it using User Certificate Authority signing key and returns the
|
|
// resulting certificate.
|
|
func (a *TestAuthServer) GenerateUserCert(key []byte, username string, ttl time.Duration, compatibility string) ([]byte, error) {
|
|
user, err := a.AuthServer.GetUser(username, false)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
accessInfo := services.AccessInfoFromUser(user)
|
|
checker, err := services.NewAccessChecker(accessInfo, a.ClusterName, a.AuthServer)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
certs, err := a.AuthServer.generateUserCert(certRequest{
|
|
user: user,
|
|
ttl: ttl,
|
|
compatibility: compatibility,
|
|
publicKey: key,
|
|
checker: checker,
|
|
traits: user.GetTraits(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return certs.SSH, nil
|
|
}
|
|
|
|
// PrivateKeyToPublicKeyTLS gets the TLS public key from a raw private key.
|
|
func PrivateKeyToPublicKeyTLS(privateKey []byte) (tlsPublicKey []byte, err error) {
|
|
sshPrivate, err := ssh.ParseRawPrivateKey(privateKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
tlsPublicKey, err = tlsca.MarshalPublicKeyFromPrivateKeyPEM(sshPrivate)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return tlsPublicKey, nil
|
|
}
|
|
|
|
// generateCertificate generates certificate for identity,
|
|
// returns private public key pair
|
|
func generateCertificate(authServer *Server, identity TestIdentity) ([]byte, []byte, error) {
|
|
priv, pub, err := native.GenerateKeyPair()
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
tlsPublicKey, err := PrivateKeyToPublicKeyTLS(priv)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
clusterName, err := authServer.GetClusterName()
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
switch id := identity.I.(type) {
|
|
case authz.LocalUser:
|
|
user, err := authServer.GetUser(id.Username, false)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
accessInfo := services.AccessInfoFromUser(user)
|
|
checker, err := services.NewAccessChecker(accessInfo, clusterName.GetClusterName(), authServer)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
if identity.TTL == 0 {
|
|
identity.TTL = time.Hour
|
|
}
|
|
|
|
certs, err := authServer.generateUserCert(certRequest{
|
|
publicKey: pub,
|
|
user: user,
|
|
ttl: identity.TTL,
|
|
usage: identity.AcceptedUsage,
|
|
routeToCluster: identity.RouteToCluster,
|
|
checker: checker,
|
|
traits: user.GetTraits(),
|
|
renewable: identity.Renewable,
|
|
generation: identity.Generation,
|
|
deviceExtensions: DeviceExtensions(id.Identity.DeviceExtensions),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
return certs.TLS, priv, nil
|
|
case authz.BuiltinRole:
|
|
certs, err := authServer.GenerateHostCerts(context.Background(),
|
|
&proto.HostCertsRequest{
|
|
HostID: id.Username,
|
|
NodeName: id.Username,
|
|
Role: id.Role,
|
|
PublicTLSKey: tlsPublicKey,
|
|
PublicSSHKey: pub,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
return certs.TLS, priv, nil
|
|
case authz.RemoteBuiltinRole:
|
|
certs, err := authServer.GenerateHostCerts(context.Background(),
|
|
&proto.HostCertsRequest{
|
|
HostID: id.Username,
|
|
NodeName: id.Username,
|
|
Role: id.Role,
|
|
PublicTLSKey: tlsPublicKey,
|
|
PublicSSHKey: pub,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
return certs.TLS, priv, nil
|
|
default:
|
|
return nil, nil, trace.BadParameter("identity of unknown type %T is unsupported", identity)
|
|
}
|
|
}
|
|
|
|
// NewCertificate returns new TLS credentials generated by test auth server
|
|
func (a *TestAuthServer) NewCertificate(identity TestIdentity) (*tls.Certificate, error) {
|
|
cert, key, err := generateCertificate(a.AuthServer, identity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tlsCert, err := tls.X509KeyPair(cert, key)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &tlsCert, nil
|
|
}
|
|
|
|
// Clock returns clock used by auth server
|
|
func (a *TestAuthServer) Clock() clockwork.Clock {
|
|
return a.AuthServer.GetClock()
|
|
}
|
|
|
|
// Trust adds other server host certificate authority as trusted
|
|
func (a *TestAuthServer) Trust(ctx context.Context, remote *TestAuthServer, roleMap types.RoleMap) error {
|
|
remoteCA, err := remote.AuthServer.GetCertAuthority(ctx, types.CertAuthID{
|
|
Type: types.HostCA,
|
|
DomainName: remote.ClusterName,
|
|
}, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
err = a.AuthServer.UpsertCertAuthority(ctx, remoteCA)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
remoteCA, err = remote.AuthServer.GetCertAuthority(ctx, types.CertAuthID{
|
|
Type: types.DatabaseCA,
|
|
DomainName: remote.ClusterName,
|
|
}, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
err = a.AuthServer.UpsertCertAuthority(ctx, remoteCA)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
remoteCA, err = remote.AuthServer.GetCertAuthority(ctx, types.CertAuthID{
|
|
Type: types.OpenSSHCA,
|
|
DomainName: remote.ClusterName,
|
|
}, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
err = a.AuthServer.UpsertCertAuthority(ctx, remoteCA)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
remoteCA, err = remote.AuthServer.GetCertAuthority(ctx, types.CertAuthID{
|
|
Type: types.UserCA,
|
|
DomainName: remote.ClusterName,
|
|
}, false)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
remoteCA.SetRoleMap(roleMap)
|
|
err = a.AuthServer.UpsertCertAuthority(ctx, remoteCA)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewTestTLSServer returns new test TLS server
|
|
func (a *TestAuthServer) NewTestTLSServer() (*TestTLSServer, error) {
|
|
apiConfig := &APIConfig{
|
|
AuthServer: a.AuthServer,
|
|
Authorizer: a.Authorizer,
|
|
AuditLog: a.AuditLog,
|
|
Emitter: a.AuthServer.emitter,
|
|
}
|
|
srv, err := NewTestTLSServer(TestTLSServerConfig{
|
|
APIConfig: apiConfig,
|
|
AuthServer: a,
|
|
AcceptedUsage: a.AcceptedUsage,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return srv, nil
|
|
}
|
|
|
|
// NewRemoteClient creates new client to the remote server using identity
|
|
// generated for this certificate authority
|
|
func (a *TestAuthServer) NewRemoteClient(identity TestIdentity, addr net.Addr, pool *x509.CertPool) (*Client, error) {
|
|
tlsConfig := utils.TLSConfig(a.CipherSuites)
|
|
cert, err := a.NewCertificate(identity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{*cert}
|
|
tlsConfig.RootCAs = pool
|
|
tlsConfig.ServerName = apiutils.EncodeClusterName(a.ClusterName)
|
|
tlsConfig.Time = a.AuthServer.clock.Now
|
|
|
|
return NewClient(client.Config{
|
|
Addrs: []string{addr.String()},
|
|
Credentials: []client.Credentials{
|
|
client.LoadTLS(tlsConfig),
|
|
},
|
|
CircuitBreakerConfig: breaker.NoopBreakerConfig(),
|
|
})
|
|
}
|
|
|
|
// TestTLSServerConfig is a configuration for test TLS server
|
|
type TestTLSServerConfig struct {
|
|
// APIConfig is a configuration of API server
|
|
APIConfig *APIConfig
|
|
// AuthServer is a test auth server used to serve requests
|
|
AuthServer *TestAuthServer
|
|
// Limiter is a connection and request limiter
|
|
Limiter *limiter.Config
|
|
// Listener is a listener to serve requests on
|
|
Listener net.Listener
|
|
// AcceptedUsage is a list of accepted usage restrictions
|
|
AcceptedUsage []string
|
|
}
|
|
|
|
// Auth returns auth server used by this TLS server
|
|
func (t *TestTLSServer) Auth() *Server {
|
|
return t.AuthServer.AuthServer
|
|
}
|
|
|
|
// TestTLSServer is a test TLS server
|
|
type TestTLSServer struct {
|
|
// TestTLSServerConfig is a configuration for TLS server
|
|
TestTLSServerConfig
|
|
// Identity is a generated TLS/SSH identity used to answer in TLS
|
|
Identity *Identity
|
|
// TLSServer is a configured TLS server
|
|
TLSServer *TLSServer
|
|
}
|
|
|
|
// ClusterName returns name of test TLS server cluster
|
|
func (t *TestTLSServer) ClusterName() string {
|
|
return t.AuthServer.ClusterName
|
|
}
|
|
|
|
// Clock returns clock used by auth server
|
|
func (t *TestTLSServer) Clock() clockwork.Clock {
|
|
return t.AuthServer.Clock()
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets limiter defaults
|
|
func (cfg *TestTLSServerConfig) CheckAndSetDefaults() error {
|
|
if cfg.APIConfig == nil {
|
|
return trace.BadParameter("missing parameter APIConfig")
|
|
}
|
|
if cfg.AuthServer == nil {
|
|
return trace.BadParameter("missing parameter AuthServer")
|
|
}
|
|
// use very permissive limiter configuration by default
|
|
if cfg.Limiter == nil {
|
|
cfg.Limiter = &limiter.Config{
|
|
MaxConnections: 1000,
|
|
MaxNumberOfUsers: 1000,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewTestTLSServer returns new test TLS server that is started and is listening
|
|
// on 127.0.0.1 loopback on any available port
|
|
func NewTestTLSServer(cfg TestTLSServerConfig) (*TestTLSServer, error) {
|
|
err := cfg.CheckAndSetDefaults()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
srv := &TestTLSServer{
|
|
TestTLSServerConfig: cfg,
|
|
}
|
|
srv.Identity, err = NewServerIdentity(srv.AuthServer.AuthServer, "test-tls-server", types.RoleAuth)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// Register TLS endpoint of the auth service
|
|
tlsConfig, err := srv.Identity.TLSConfig(srv.AuthServer.CipherSuites)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tlsConfig.Time = cfg.AuthServer.Clock().Now
|
|
|
|
accessPoint, err := NewAdminAuthServer(srv.AuthServer.AuthServer, srv.AuthServer.AuditLog)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
srv.Listener, err = net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
srv.TLSServer, err = NewTLSServer(TLSServerConfig{
|
|
Listener: srv.Listener,
|
|
AccessPoint: accessPoint,
|
|
TLS: tlsConfig,
|
|
APIConfig: *srv.APIConfig,
|
|
LimiterConfig: *srv.Limiter,
|
|
AcceptedUsage: cfg.AcceptedUsage,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := srv.Start(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return srv, nil
|
|
}
|
|
|
|
// TestIdentity is test identity spec used to generate identities in tests
|
|
type TestIdentity struct {
|
|
I authz.IdentityGetter
|
|
TTL time.Duration
|
|
AcceptedUsage []string
|
|
RouteToCluster string
|
|
Renewable bool
|
|
Generation uint64
|
|
}
|
|
|
|
// TestUser returns TestIdentity for local user
|
|
func TestUser(username string) TestIdentity {
|
|
return TestIdentity{
|
|
I: authz.LocalUser{
|
|
Username: username,
|
|
Identity: tlsca.Identity{Username: username},
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestUserWithDeviceExtensions returns a TestIdentity for a local user,
|
|
// including the supplied device extensions in the tlsca.Identity.
|
|
func TestUserWithDeviceExtensions(username string, exts tlsca.DeviceExtensions) TestIdentity {
|
|
return TestIdentity{
|
|
I: authz.LocalUser{
|
|
Username: username,
|
|
Identity: tlsca.Identity{
|
|
Username: username,
|
|
DeviceExtensions: exts,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestUser returns a TestIdentity for a local user
|
|
// with renewable credentials.
|
|
func TestRenewableUser(username string, generation uint64) TestIdentity {
|
|
return TestIdentity{
|
|
I: authz.LocalUser{
|
|
Username: username,
|
|
Identity: tlsca.Identity{
|
|
Username: username,
|
|
},
|
|
},
|
|
Renewable: true,
|
|
Generation: generation,
|
|
}
|
|
}
|
|
|
|
// TestNop returns "Nop" - unauthenticated identity
|
|
func TestNop() TestIdentity {
|
|
return TestIdentity{
|
|
I: nil,
|
|
}
|
|
}
|
|
|
|
// TestAdmin returns TestIdentity for admin user
|
|
func TestAdmin() TestIdentity {
|
|
return TestBuiltin(types.RoleAdmin)
|
|
}
|
|
|
|
// TestBuiltin returns TestIdentity for builtin user
|
|
func TestBuiltin(role types.SystemRole) TestIdentity {
|
|
return TestIdentity{
|
|
I: authz.BuiltinRole{
|
|
Role: role,
|
|
Username: string(role),
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestServerID returns a TestIdentity for a node with the passed in serverID.
|
|
func TestServerID(role types.SystemRole, serverID string) TestIdentity {
|
|
return TestIdentity{
|
|
I: authz.BuiltinRole{
|
|
Role: role,
|
|
Username: serverID,
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestRemoteBuiltin returns TestIdentity for a remote builtin role.
|
|
func TestRemoteBuiltin(role types.SystemRole, remoteCluster string) TestIdentity {
|
|
return TestIdentity{
|
|
I: authz.RemoteBuiltinRole{
|
|
Role: role,
|
|
Username: string(role),
|
|
ClusterName: remoteCluster,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewClientFromWebSession returns new authenticated client from web session
|
|
func (t *TestTLSServer) NewClientFromWebSession(sess types.WebSession) (*Client, error) {
|
|
tlsConfig, err := t.Identity.TLSConfig(t.AuthServer.CipherSuites)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tlsCert, err := tls.X509KeyPair(sess.GetTLSCert(), sess.GetPriv())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "failed to parse TLS cert and key")
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
|
tlsConfig.Time = t.AuthServer.AuthServer.clock.Now
|
|
|
|
return NewClient(client.Config{
|
|
Addrs: []string{t.Addr().String()},
|
|
Credentials: []client.Credentials{
|
|
client.LoadTLS(tlsConfig),
|
|
},
|
|
CircuitBreakerConfig: breaker.NoopBreakerConfig(),
|
|
})
|
|
}
|
|
|
|
// CertPool returns cert pool that auth server represents
|
|
func (t *TestTLSServer) CertPool() (*x509.CertPool, error) {
|
|
tlsConfig, err := t.Identity.TLSConfig(t.AuthServer.CipherSuites)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return tlsConfig.RootCAs, nil
|
|
}
|
|
|
|
// ClientTLSConfig returns client TLS config based on the identity
|
|
func (t *TestTLSServer) ClientTLSConfig(identity TestIdentity) (*tls.Config, error) {
|
|
tlsConfig, err := t.Identity.TLSConfig(t.AuthServer.CipherSuites)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if identity.I != nil {
|
|
cert, err := t.AuthServer.NewCertificate(identity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{*cert}
|
|
} else {
|
|
// this client is not authenticated, which means that auth
|
|
// server should apply Nop builtin role
|
|
tlsConfig.Certificates = nil
|
|
}
|
|
tlsConfig.Time = t.AuthServer.AuthServer.clock.Now
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
// CloneClient uses the same credentials as the passed client
|
|
// but forces the client to be recreated
|
|
func (t *TestTLSServer) CloneClient(clt *Client) *Client {
|
|
newClient, err := NewClient(client.Config{
|
|
Addrs: []string{t.Addr().String()},
|
|
Credentials: []client.Credentials{
|
|
client.LoadTLS(clt.Config()),
|
|
},
|
|
CircuitBreakerConfig: breaker.NoopBreakerConfig(),
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return newClient
|
|
}
|
|
|
|
// NewClientWithCert creates a new client using given cert and private key
|
|
func (t *TestTLSServer) NewClientWithCert(clientCert tls.Certificate) *Client {
|
|
tlsConfig, err := t.Identity.TLSConfig(t.AuthServer.CipherSuites)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
tlsConfig.Time = t.AuthServer.AuthServer.clock.Now
|
|
tlsConfig.Certificates = []tls.Certificate{clientCert}
|
|
newClient, err := NewClient(client.Config{
|
|
Addrs: []string{t.Addr().String()},
|
|
Credentials: []client.Credentials{
|
|
client.LoadTLS(tlsConfig),
|
|
},
|
|
CircuitBreakerConfig: breaker.NoopBreakerConfig(),
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return newClient
|
|
}
|
|
|
|
// NewClient returns new client to test server authenticated with identity
|
|
func (t *TestTLSServer) NewClient(identity TestIdentity) (*Client, error) {
|
|
tlsConfig, err := t.ClientTLSConfig(identity)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
newClient, err := NewClient(client.Config{
|
|
DialInBackground: true,
|
|
Addrs: []string{t.Addr().String()},
|
|
Credentials: []client.Credentials{
|
|
client.LoadTLS(tlsConfig),
|
|
},
|
|
CircuitBreakerConfig: breaker.NoopBreakerConfig(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return newClient, nil
|
|
}
|
|
|
|
// Addr returns address of TLS server
|
|
func (t *TestTLSServer) Addr() net.Addr {
|
|
return t.Listener.Addr()
|
|
}
|
|
|
|
// Start starts TLS server on loopback address on the first listening socket
|
|
func (t *TestTLSServer) Start() error {
|
|
go t.TLSServer.Serve()
|
|
return nil
|
|
}
|
|
|
|
// Close closes the listener and HTTP server
|
|
func (t *TestTLSServer) Close() error {
|
|
err := t.TLSServer.Close()
|
|
if t.Listener != nil {
|
|
t.Listener.Close()
|
|
}
|
|
if t.AuthServer.Backend != nil {
|
|
t.AuthServer.Backend.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Shutdown closes the listener and HTTP server gracefully
|
|
func (t *TestTLSServer) Shutdown(ctx context.Context) error {
|
|
errs := []error{t.TLSServer.Shutdown(ctx)}
|
|
if t.Listener != nil {
|
|
errs = append(errs, t.Listener.Close())
|
|
}
|
|
if t.AuthServer.Backend != nil {
|
|
errs = append(errs, t.AuthServer.Backend.Close())
|
|
}
|
|
return trace.NewAggregate(errs...)
|
|
}
|
|
|
|
// Stop stops listening server, but does not close the auth backend
|
|
func (t *TestTLSServer) Stop() error {
|
|
err := t.TLSServer.Close()
|
|
if t.Listener != nil {
|
|
t.Listener.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// NewServerIdentity generates new server identity, used in tests
|
|
func NewServerIdentity(clt *Server, hostID string, role types.SystemRole) (*Identity, error) {
|
|
priv, pub, err := native.GenerateKeyPair()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
publicTLS, err := PrivateKeyToPublicKeyTLS(priv)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
certs, err := clt.GenerateHostCerts(context.Background(),
|
|
&proto.HostCertsRequest{
|
|
HostID: hostID,
|
|
NodeName: hostID,
|
|
Role: types.RoleAuth,
|
|
PublicTLSKey: publicTLS,
|
|
PublicSSHKey: pub,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return ReadIdentityFromKeyPair(priv, certs)
|
|
}
|
|
|
|
// clt limits required interface to the necessary methods
|
|
// used to pass different clients in tests
|
|
type clt interface {
|
|
UpsertRole(context.Context, types.Role) error
|
|
UpsertUser(types.User) error
|
|
}
|
|
|
|
// CreateRole creates a role without assigning any users. Used in tests.
|
|
func CreateRole(ctx context.Context, clt clt, name string, spec types.RoleSpecV6) (types.Role, error) {
|
|
role, err := types.NewRole(name, spec)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = clt.UpsertRole(ctx, role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return role, nil
|
|
}
|
|
|
|
// CreateUserRoleAndRequestable creates two roles for a user, one base role with allowed login
|
|
// matching username, and another role with a login matching rolename that can be requested.
|
|
func CreateUserRoleAndRequestable(clt clt, username string, rolename string) (types.User, error) {
|
|
ctx := context.TODO()
|
|
user, err := types.NewUser(username)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
baseRole := services.RoleForUser(user)
|
|
baseRole.SetAccessRequestConditions(types.Allow, types.AccessRequestConditions{
|
|
Roles: []string{rolename},
|
|
})
|
|
baseRole.SetLogins(types.Allow, nil)
|
|
err = clt.UpsertRole(ctx, baseRole)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
user.AddRole(baseRole.GetName())
|
|
err = clt.UpsertUser(user)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
requestableRole := services.RoleForUser(user)
|
|
requestableRole.SetName(rolename)
|
|
requestableRole.SetLogins(types.Allow, []string{rolename})
|
|
err = clt.UpsertRole(ctx, requestableRole)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
// CreateAccessPluginUser creates a user with list/read abilites for access requests, and list/read/update
|
|
// abilities for access plugin data.
|
|
func CreateAccessPluginUser(ctx context.Context, clt clt, username string) (types.User, error) {
|
|
user, err := types.NewUser(username)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
role := services.RoleForUser(user)
|
|
rules := role.GetRules(types.Allow)
|
|
rules = append(rules,
|
|
types.Rule{
|
|
Resources: []string{types.KindAccessRequest},
|
|
Verbs: []string{types.VerbRead, types.VerbList},
|
|
},
|
|
types.Rule{
|
|
Resources: []string{types.KindAccessPluginData},
|
|
Verbs: []string{types.VerbRead, types.VerbList, types.VerbUpdate},
|
|
},
|
|
)
|
|
role.SetRules(types.Allow, rules)
|
|
role.SetLogins(types.Allow, nil)
|
|
if err := clt.UpsertRole(ctx, role); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
user.AddRole(role.GetName())
|
|
if err := clt.UpsertUser(user); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
// CreateUser creates user and role and assigns role to a user, used in tests
|
|
func CreateUser(clt clt, username string, roles ...types.Role) (types.User, error) {
|
|
ctx := context.TODO()
|
|
user, err := types.NewUser(username)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
for _, role := range roles {
|
|
err = clt.UpsertRole(ctx, role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
user.AddRole(role.GetName())
|
|
}
|
|
|
|
err = clt.UpsertUser(user)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
// CreateUserAndRole creates user and role and assigns role to a user, used in tests
|
|
// If allowRules is nil, the role has admin privileges.
|
|
// If allowRules is not-nil, then the rules associated with the role will be
|
|
// replaced with those specified.
|
|
func CreateUserAndRole(clt clt, username string, allowedLogins []string, allowRules []types.Rule) (types.User, types.Role, error) {
|
|
ctx := context.TODO()
|
|
user, err := types.NewUser(username)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
role := services.RoleForUser(user)
|
|
role.SetLogins(types.Allow, allowedLogins)
|
|
if allowRules != nil {
|
|
role.SetRules(types.Allow, allowRules)
|
|
}
|
|
|
|
err = clt.UpsertRole(ctx, role)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
user.AddRole(role.GetName())
|
|
err = clt.UpsertUser(user)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
return user, role, nil
|
|
}
|
|
|
|
// CreateUserAndRoleWithoutRoles creates user and role, but does not assign user to a role, used in tests
|
|
func CreateUserAndRoleWithoutRoles(clt clt, username string, allowedLogins []string) (types.User, types.Role, error) {
|
|
ctx := context.TODO()
|
|
user, err := types.NewUser(username)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
role := services.RoleForUser(user)
|
|
set := services.MakeRuleSet(role.GetRules(types.Allow))
|
|
delete(set, types.KindRole)
|
|
role.SetRules(types.Allow, set.Slice())
|
|
role.SetLogins(types.Allow, []string{user.GetName()})
|
|
err = clt.UpsertRole(ctx, role)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
user.AddRole(role.GetName())
|
|
err = clt.UpsertUser(user)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
return user, role, nil
|
|
}
|