diff --git a/constants.go b/constants.go index 4f77de534a09e..e52d8889818de 100644 --- a/constants.go +++ b/constants.go @@ -115,6 +115,9 @@ const ( // ComponentConnectProxy is the HTTP CONNECT proxy used to tunnel connection. ComponentConnectProxy = "http:proxy" + // ComponentKeyGen is the public/private keypair generator. + ComponentKeyGen = "keygen" + // DebugEnvVar tells tests to use verbose debug output DebugEnvVar = "DEBUG" diff --git a/integration/integration_test.go b/integration/integration_test.go index c89bb87e68148..bde475eb904be 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -315,8 +315,45 @@ func (s *IntSuite) TestAuditOn(c *check.C) { c.Assert(strings.Contains(string(sessionStream), "echo hi"), check.Equals, true, comment) c.Assert(strings.Contains(string(sessionStream), "exit"), check.Equals, true, comment) - // now lets look at session events: - history, err := site.GetSessionEvents(defaults.Namespace, session.ID, 0) + // Wait until session.start, session.leave, and session.end events have arrived. + getSessions := func(site auth.ClientI) ([]events.EventFields, error) { + tickCh := time.Tick(500 * time.Millisecond) + stopCh := time.After(10 * time.Second) + for { + select { + case <-tickCh: + // Get all session events from the backend. + sessionEvents, err := site.GetSessionEvents(defaults.Namespace, session.ID, 0) + if err != nil { + return nil, trace.Wrap(err) + } + + // Look through all session events for the three wanted. + var hasStart bool + var hasEnd bool + var hasLeave bool + for _, se := range sessionEvents { + if se.GetType() == events.SessionStartEvent { + hasStart = true + } + if se.GetType() == events.SessionEndEvent { + hasEnd = true + } + if se.GetType() == events.SessionLeaveEvent { + hasLeave = true + } + } + + // Make sure all three events were found. + if hasStart && hasEnd && hasLeave { + return sessionEvents, nil + } + case <-stopCh: + return nil, trace.BadParameter("unable to find all session events after 10s (mode=%v)", tt.inRecordLocation) + } + } + } + history, err := getSessions(site) c.Assert(err, check.IsNil) getChunk := func(e events.EventFields, maxlen int) string { diff --git a/lib/auth/native/native.go b/lib/auth/native/native.go index c2c7710431c8c..abb8d6a6eed8c 100644 --- a/lib/auth/native/native.go +++ b/lib/auth/native/native.go @@ -13,89 +13,86 @@ 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 native import ( + "context" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" - "sync" "time" + "golang.org/x/crypto/ssh" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" + + "github.com/sirupsen/logrus" ) -var ( - // this global configures how many pre-calculated keypairs to keep in the - // background (perform key genreation in a separate goroutine, useful for - // web sesssion for snappy UI) - PrecalculatedKeysNum = 10 +var log = logrus.WithFields(logrus.Fields{ + trace.Component: teleport.ComponentKeyGen, +}) - // only one global copy of 'nauth' exists - singleton nauth = nauth{ - closeC: make(chan bool), - } -) +// PrecomputedNum is the number of keys to precompute and keep cached. +var PrecomputedNum = 25 type keyPair struct { privPem []byte pubBytes []byte } -type nauth struct { - generatedKeysC chan keyPair - closeC chan bool - mutex sync.Mutex +// keygen is a key generator that precomputes keys to provide quick access to +// public/private key pairs. +type keygen struct { + keysCh chan keyPair + + ctx context.Context + cancel context.CancelFunc } -// New returns a pointer to a key generator for production purposes -func New() *nauth { - singleton.mutex.Lock() - defer singleton.mutex.Unlock() +// New returns a new key generator. +func New() *keygen { + ctx, cancel := context.WithCancel(context.Background()) - if singleton.generatedKeysC == nil && PrecalculatedKeysNum > 0 { - singleton.generatedKeysC = make(chan keyPair, PrecalculatedKeysNum) - go singleton.precalculateKeys() + k := &keygen{ + keysCh: make(chan keyPair, PrecomputedNum), + ctx: ctx, + cancel: cancel, } - return &singleton -} + go k.precomputeKeys() -// Close() closes and re-sets the key generator (better to call it only once, -// when the process is stopping, to avoid costly re-initialization) -func (n *nauth) Close() { - n.mutex.Lock() - defer n.mutex.Unlock() + return k +} - close(n.closeC) - n.generatedKeysC = nil - n.closeC = make(chan bool) +// Close stops the precomputation of keys (if enabled) and releases all resources. +func (k *keygen) Close() { + k.cancel() } -// GetNewKeyPairFromPool returns pre-generated keypair from a channel, which -// gets replenished by `precalculateKeys` goroutine -func (n *nauth) GetNewKeyPairFromPool() ([]byte, []byte, error) { +// GetNewKeyPairFromPool returns precomputed key pair from the pool. +func (k *keygen) GetNewKeyPairFromPool() ([]byte, []byte, error) { select { - case key := <-n.generatedKeysC: + case key := <-k.keysCh: return key.privPem, key.pubBytes, nil default: - return n.GenerateKeyPair("") + return GenerateKeyPair("") } } -func (n *nauth) precalculateKeys() { +// precomputeKeys continues loops forever trying to compute cache key pairs. +func (k *keygen) precomputeKeys() { for { - privPem, pubBytes, err := n.GenerateKeyPair("") + privPem, pubBytes, err := GenerateKeyPair("") if err != nil { - log.Errorf(err.Error()) + log.Errorf("Unable to generate key pair: %v.", err) continue } key := keyPair{ @@ -104,17 +101,18 @@ func (n *nauth) precalculateKeys() { } select { - case <-n.closeC: - log.Infof("[KEYS] precalculateKeys() exited") + case <-k.ctx.Done(): + log.Infof("Stopping key precomputation routine.") return - case n.generatedKeysC <- key: + case k.keysCh <- key: continue } } } -// GenerateKeyPair returns fresh priv/pub keypair, takes about 300ms to execute -func (n *nauth) GenerateKeyPair(passphrase string) ([]byte, []byte, error) { +// GenerateKeyPair returns fresh priv/pub keypair, takes about 300ms to +// execute. +func GenerateKeyPair(passphrase string) ([]byte, []byte, error) { priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err @@ -135,7 +133,15 @@ func (n *nauth) GenerateKeyPair(passphrase string) ([]byte, []byte, error) { return privPem, pubBytes, nil } -func (n *nauth) GenerateHostCert(c services.HostCertParams) ([]byte, error) { +// GenerateKeyPair returns fresh priv/pub keypair, takes about 300ms to +// execute. +func (k *keygen) GenerateKeyPair(passphrase string) ([]byte, []byte, error) { + return GenerateKeyPair(passphrase) +} + +// GenerateHostCert generates a host certificate with the passed in parameters. +// The private key of the CA to sign the certificate must be provided. +func (k *keygen) GenerateHostCert(c services.HostCertParams) ([]byte, error) { if err := c.Check(); err != nil { return nil, trace.Wrap(err) } @@ -183,7 +189,9 @@ func (n *nauth) GenerateHostCert(c services.HostCertParams) ([]byte, error) { return ssh.MarshalAuthorizedKey(cert), nil } -func (n *nauth) GenerateUserCert(c services.UserCertParams) ([]byte, error) { +// GenerateUserCert generates a host certificate with the passed in parameters. +// The private key of the CA to sign the certificate must be provided. +func (k *keygen) GenerateUserCert(c services.UserCertParams) ([]byte, error) { if c.TTL < defaults.MinCertDuration { return nil, trace.BadParameter("wrong certificate TTL") } diff --git a/lib/auth/native/native_test.go b/lib/auth/native/native_test.go index 28fbda915bb2a..f4188c917935c 100644 --- a/lib/auth/native/native_test.go +++ b/lib/auth/native/native_test.go @@ -41,7 +41,7 @@ var _ = fmt.Printf func (s *NativeSuite) SetUpSuite(c *C) { utils.InitLoggerForTests() - PrecalculatedKeysNum = 1 + PrecomputedNum = 1 s.suite = &test.AuthSuite{A: New()} } diff --git a/lib/auth/testauthority/testauthority.go b/lib/auth/testauthority/testauthority.go index eeef1f5852f81..f2757b157b967 100644 --- a/lib/auth/testauthority/testauthority.go +++ b/lib/auth/testauthority/testauthority.go @@ -37,6 +37,9 @@ func New() *Keygen { return &Keygen{} } +func (n *Keygen) Close() { +} + func (n *Keygen) GetNewKeyPairFromPool() ([]byte, []byte, error) { return n.GenerateKeyPair("") } diff --git a/lib/client/identity.go b/lib/client/identity.go index d9ab0d639bcc9..2025825f847cf 100644 --- a/lib/client/identity.go +++ b/lib/client/identity.go @@ -12,7 +12,6 @@ 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 client @@ -29,14 +28,15 @@ import ( // NewKey generates a new unsigned key. Such key must be signed by a // Teleport CA (auth server) before it becomes useful. func NewKey() (key *Key, err error) { - key = &Key{} - keygen := native.New() - defer keygen.Close() - key.Priv, key.Pub, err = keygen.GenerateKeyPair("") + priv, pub, err := native.GenerateKeyPair("") if err != nil { return nil, trace.Wrap(err) } - return key, nil + + return &Key{ + Priv: priv, + Pub: pub, + }, nil } // IdentityFileFormat describes possible file formats how a user identity can be sotred diff --git a/lib/reversetunnel/cache.go b/lib/reversetunnel/cache.go index a7a0677c3aa4d..bf415757919ba 100644 --- a/lib/reversetunnel/cache.go +++ b/lib/reversetunnel/cache.go @@ -25,28 +25,31 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/auth" - "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/trace" "github.com/gravitational/ttlmap" ) type certificateCache struct { - mu sync.Mutex + mu sync.Mutex + cache *ttlmap.TTLMap authClient auth.ClientI + keygen sshca.Authority } // NewHostCertificateCache creates a shared host certificate cache that is // used by the forwarding server. -func NewHostCertificateCache(authClient auth.ClientI) (*certificateCache, error) { +func NewHostCertificateCache(keygen sshca.Authority, authClient auth.ClientI) (*certificateCache, error) { cache, err := ttlmap.New(defaults.HostCertCacheSize) if err != nil { return nil, trace.Wrap(err) } return &certificateCache{ + keygen: keygen, cache: cache, authClient: authClient, }, nil @@ -120,11 +123,8 @@ func (c *certificateCache) set(principal string, certificate ssh.Signer, ttl tim // generateHostCert will generate a SSH host certificate for a given // principal. func (c *certificateCache) generateHostCert(principal string) (ssh.Signer, error) { - keygen := native.New() - defer keygen.Close() - // generate public/private keypair - privBytes, pubBytes, err := keygen.GenerateKeyPair("") + privBytes, pubBytes, err := c.keygen.GetNewKeyPairFromPool() if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/reversetunnel/localsite.go b/lib/reversetunnel/localsite.go index d1a41168c01e7..99b8e9fd69a71 100644 --- a/lib/reversetunnel/localsite.go +++ b/lib/reversetunnel/localsite.go @@ -44,7 +44,7 @@ func newlocalSite(srv *server, domainName string, client auth.ClientI) (*localSi // certificate cache is created in each site (instead of creating it in // reversetunnel.server and passing it along) so that the host certificate // is signed by the correct certificate authority. - certificateCache, err := NewHostCertificateCache(client) + certificateCache, err := NewHostCertificateCache(srv.Config.KeyGen, client) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/reversetunnel/srv.go b/lib/reversetunnel/srv.go index c534bf6a90b5a..44171e1fdcc48 100644 --- a/lib/reversetunnel/srv.go +++ b/lib/reversetunnel/srv.go @@ -33,6 +33,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/state" "github.com/gravitational/teleport/lib/utils" @@ -128,6 +129,10 @@ type Config struct { // wall clock if not set Clock clockwork.Clock + // KeyGen is a process wide key generator. It is shared to speed up + // generation of public/private keypairs. + KeyGen sshca.Authority + // Ciphers is a list of ciphers that the server supports. If omitted, // the defaults will be used. Ciphers []string @@ -824,7 +829,7 @@ func newRemoteSite(srv *server, domainName string) (*remoteSite, error) { // certificate cache is created in each site (instead of creating it in // reversetunnel.server and passing it along) so that the host certificate // is signed by the correct certificate authority. - certificateCache, err := NewHostCertificateCache(srv.localAuthClient) + certificateCache, err := NewHostCertificateCache(srv.Config.KeyGen, srv.localAuthClient) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/service/service.go b/lib/service/service.go index ffe933acac23d..46f8e11362d70 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -51,7 +51,6 @@ import ( "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/srv/regular" - "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/state" "github.com/gravitational/teleport/lib/system" "github.com/gravitational/teleport/lib/utils" @@ -379,11 +378,14 @@ func NewTeleport(cfg *Config) (*TeleportProcess, error) { warnOnErr(process.closeImportedDescriptors(teleport.ComponentDiagnostic)) } + // Create a process wide key generator that will be shared. This is so the + // key generator can pre-generate keys and share these across services. + if cfg.Keygen == nil { + cfg.Keygen = native.New() + } + if cfg.Auth.Enabled { - if cfg.Keygen == nil { - cfg.Keygen = native.New() - } - if err := process.initAuthService(cfg.Keygen); err != nil { + if err := process.initAuthService(); err != nil { return nil, trace.Wrap(err) } serviceStarted = true @@ -429,7 +431,7 @@ func (process *TeleportProcess) getLocalAuth() *auth.AuthServer { } // initAuthService can be called to initialize auth server service -func (process *TeleportProcess) initAuthService(authority sshca.Authority) error { +func (process *TeleportProcess) initAuthService() error { var ( askedToExit = false err error @@ -493,7 +495,7 @@ func (process *TeleportProcess) initAuthService(authority sshca.Authority) error // first, create the AuthServer authServer, identity, err := auth.Init(auth.InitConfig{ Backend: b, - Authority: authority, + Authority: cfg.Keygen, ClusterConfiguration: cfg.ClusterConfiguration, ClusterConfig: cfg.Auth.ClusterConfig, ClusterName: cfg.Auth.ClusterName, @@ -1020,7 +1022,13 @@ func (process *TeleportProcess) initProxy() error { if !ok { return trace.BadParameter("unsupported connector type: %T", event.Payload) } - return trace.Wrap(process.initProxyEndpoint(conn)) + + err := process.initProxyEndpoint(conn) + if err != nil { + return trace.Wrap(err) + } + + return nil }) return nil } @@ -1187,6 +1195,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { Client: conn.Client, }, }, + KeyGen: cfg.Keygen, Ciphers: cfg.Ciphers, KEXAlgorithms: cfg.KEXAlgorithms, MACAlgorithms: cfg.MACAlgorithms, @@ -1390,10 +1399,14 @@ func (process *TeleportProcess) Shutdown(ctx context.Context) { // Close broadcasts close signals and exits immediately func (process *TeleportProcess) Close() error { process.BroadcastEvent(Event{Name: TeleportExitEvent}) + + process.Config.Keygen.Close() + localAuth := process.getLocalAuth() if localAuth != nil { return trace.Wrap(process.localAuth.Close()) } + return nil } diff --git a/lib/service/supervisor.go b/lib/service/supervisor.go index 300c503dfb3de..17f94c1cd628f 100644 --- a/lib/service/supervisor.go +++ b/lib/service/supervisor.go @@ -131,17 +131,17 @@ func (s *LocalSupervisor) RegisterFunc(name string, fn ServiceFunc) { // RemoveService removes service from supervisor tracking list func (s *LocalSupervisor) RemoveService(srv Service) error { - log := log.WithFields(logrus.Fields{"service": srv.Name()}) + l := logrus.WithFields(logrus.Fields{"service": srv.Name()}) s.Lock() defer s.Unlock() for i, el := range s.services { if el == srv { s.services = append(s.services[:i], s.services[i+1:]...) - log.Debugf("Service is completed and removed.") + l.Debugf("Service is completed and removed.") return nil } } - log.Warningf("Service is completed but not found.") + l.Warningf("Service is completed but not found.") return trace.NotFound("service %v is not found", srv) } diff --git a/lib/sshca/sshca.go b/lib/sshca/sshca.go index 66aaf2f40bc51..b1e346304777d 100644 --- a/lib/sshca/sshca.go +++ b/lib/sshca/sshca.go @@ -38,4 +38,7 @@ type Authority interface { // GenerateUserCert generates user certificate, it takes pkey as a signing // private key (user certificate authority) GenerateUserCert(certParams services.UserCertParams) ([]byte, error) + + // Close will close the key-management facility. + Close() }