Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key Generation Fixes #1686

Merged
merged 2 commits into from
Feb 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
41 changes: 39 additions & 2 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
106 changes: 57 additions & 49 deletions lib/auth/native/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/native/native_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
}

Expand Down
3 changes: 3 additions & 0 deletions lib/auth/testauthority/testauthority.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ func New() *Keygen {
return &Keygen{}
}

func (n *Keygen) Close() {
}

func (n *Keygen) GetNewKeyPairFromPool() ([]byte, []byte, error) {
return n.GenerateKeyPair("")
}
Expand Down
12 changes: 6 additions & 6 deletions lib/client/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
14 changes: 7 additions & 7 deletions lib/reversetunnel/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/reversetunnel/localsite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
7 changes: 6 additions & 1 deletion lib/reversetunnel/srv.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Loading