Files
teleport/lib/reversetunnel/cache.go
T
Andrew LeFevre de2e55bb69 connect to registered OpenSSH nodes with certs signed by OpenSSH CA (#21953)
To prevent users from being able to directly connect to registered
OpenSSH (Agentless) nodes, the OpenSSH CA was introduced. When
connecting to Agentless nodes, the Proxy will forward the connection
so the session can be recorded. The forwarding SSH server is configured
to authenticate to the target node with a certificate signed by OpenSSH
CA, which users do not have access to.
2023-03-14 16:58:16 +00:00

174 lines
4.4 KiB
Go

/*
Copyright 2017 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 reversetunnel
import (
"context"
"strings"
"sync"
"time"
"github.com/gravitational/trace"
"github.com/gravitational/ttlmap"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/sshutils"
"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"
)
type certificateCache struct {
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(keygen sshca.Authority, authClient auth.ClientI) (*certificateCache, error) {
native.PrecomputeKeys() // ensure native package is set to precompute keys
cache, err := ttlmap.New(defaults.HostCertCacheSize)
if err != nil {
return nil, trace.Wrap(err)
}
return &certificateCache{
keygen: keygen,
cache: cache,
authClient: authClient,
}, nil
}
// getHostCertificate will fetch a certificate from the cache. If the certificate
// is not in the cache, it will be generated, put in the cache, and returned.
// Multiple callers can arrive and generate a host certificate at the same time.
// This is a tradeoff to prevent long delays here due to the expensive
// certificate generation call.
func (c *certificateCache) getHostCertificate(ctx context.Context, addr string, additionalPrincipals []string) (ssh.Signer, error) {
var certificate ssh.Signer
var err error
var ok bool
var principals []string
principals = append(principals, addr)
principals = append(principals, additionalPrincipals...)
certificate, ok = c.get(strings.Join(principals, "."))
if !ok {
certificate, err = c.generateHostCert(ctx, principals)
if err != nil {
return nil, trace.Wrap(err)
}
err = c.set(addr, certificate, defaults.HostCertCacheTime)
if err != nil {
return nil, trace.Wrap(err)
}
}
return certificate, nil
}
// get is goroutine safe and will return a ssh.Signer for a principal from
// the cache.
func (c *certificateCache) get(addr string) (ssh.Signer, bool) {
c.mu.Lock()
defer c.mu.Unlock()
certificate, ok := c.cache.Get(addr)
if !ok {
return nil, false
}
certificateSigner, ok := certificate.(ssh.Signer)
if !ok {
return nil, false
}
return certificateSigner, true
}
// set is goroutine safe and will set a ssh.Signer for a principal in
// the cache.
func (c *certificateCache) set(addr string, certificate ssh.Signer, ttl time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
err := c.cache.Set(addr, certificate, ttl)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// generateHostCert will generate a SSH host certificate for a given
// principal.
func (c *certificateCache) generateHostCert(ctx context.Context, principals []string) (ssh.Signer, error) {
if len(principals) == 0 {
return nil, trace.BadParameter("at least one principal must be provided")
}
// Generate public/private keypair.
privBytes, pubBytes, err := native.GenerateKeyPair()
if err != nil {
return nil, trace.Wrap(err)
}
// Generate a SSH host certificate.
clusterName, err := c.authClient.GetDomainName(context.TODO())
if err != nil {
return nil, trace.Wrap(err)
}
certBytes, err := c.authClient.GenerateHostCert(
ctx,
pubBytes,
principals[0],
principals[0],
principals,
clusterName,
types.RoleNode,
0)
if err != nil {
return nil, trace.Wrap(err)
}
// create a *ssh.Certificate
privateKey, err := ssh.ParsePrivateKey(privBytes)
if err != nil {
return nil, trace.Wrap(err)
}
cert, err := sshutils.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
// return a ssh.Signer
s, err := ssh.NewCertSigner(cert, privateKey)
if err != nil {
return nil, trace.Wrap(err)
}
return s, nil
}