mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
290 lines
8.1 KiB
Go
290 lines
8.1 KiB
Go
/*
|
|
Copyright 2021 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 client
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"io/ioutil"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport/api/constants"
|
|
|
|
"github.com/gravitational/trace"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/net/http2"
|
|
)
|
|
|
|
// Credentials are used to authenticate to Auth.
|
|
type Credentials interface {
|
|
// Dialer is used to create a dialer used to connect to Auth.
|
|
Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error)
|
|
// TLSConfig returns TLS configuration used to connect to Auth.
|
|
TLSConfig() (*tls.Config, error)
|
|
// SSHClientConfig returns SSH configuration used to connect to Proxy through tunnel.
|
|
SSHClientConfig() (*ssh.ClientConfig, error)
|
|
}
|
|
|
|
// LoadTLS is used to load credentials directly from another *tls.Config.
|
|
func LoadTLS(tlsConfig *tls.Config) *TLSConfigCreds {
|
|
return &TLSConfigCreds{
|
|
tlsConfig: tlsConfig,
|
|
}
|
|
}
|
|
|
|
// TLSConfigCreds are used to authenticate the client
|
|
// with a predefined *tls.Config.
|
|
type TLSConfigCreds struct {
|
|
tlsConfig *tls.Config
|
|
}
|
|
|
|
// Dialer is used to dial a connection to Auth.
|
|
func (c *TLSConfigCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration used to connect to Auth.
|
|
func (c *TLSConfigCreds) TLSConfig() (*tls.Config, error) {
|
|
if c.tlsConfig == nil {
|
|
return nil, trace.BadParameter("tls config is nil")
|
|
}
|
|
return configure(c.tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration used to connect to Proxy.
|
|
func (c *TLSConfigCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
|
|
return nil, trace.NotImplemented("no ssh config")
|
|
}
|
|
|
|
// LoadKeyPair is used to load credentials from files on disk.
|
|
func LoadKeyPair(certFile string, keyFile string, caFile string) *KeyPairCreds {
|
|
return &KeyPairCreds{
|
|
certFile: certFile,
|
|
keyFile: keyFile,
|
|
caFile: caFile,
|
|
}
|
|
}
|
|
|
|
// KeyPairCreds are used to authenticate the client
|
|
// with certificates generated in the given file paths.
|
|
type KeyPairCreds struct {
|
|
certFile string
|
|
keyFile string
|
|
caFile string
|
|
}
|
|
|
|
// Dialer is used to dial a connection to Auth.
|
|
func (c *KeyPairCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration used to connect to Auth.
|
|
func (c *KeyPairCreds) TLSConfig() (*tls.Config, error) {
|
|
cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
cas, err := ioutil.ReadFile(c.caFile)
|
|
if err != nil {
|
|
return nil, trace.ConvertSystemError(err)
|
|
}
|
|
|
|
pool := x509.NewCertPool()
|
|
if ok := pool.AppendCertsFromPEM(cas); !ok {
|
|
return nil, trace.BadParameter("invalid TLS CA cert PEM")
|
|
}
|
|
|
|
return configure(&tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
RootCAs: pool,
|
|
}), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration used to connect to Proxy.
|
|
func (c *KeyPairCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
|
|
return nil, trace.NotImplemented("no ssh config")
|
|
}
|
|
|
|
// LoadIdentityFile is used to load credentials from an identity file on disk.
|
|
func LoadIdentityFile(path string) *IdentityCreds {
|
|
return &IdentityCreds{
|
|
path: path,
|
|
}
|
|
}
|
|
|
|
// IdentityCreds are used to authenticate the client
|
|
// with an identity file generated in the given file path.
|
|
type IdentityCreds struct {
|
|
path string
|
|
identityFile *IdentityFile
|
|
}
|
|
|
|
// Dialer is used to dial a connection to Auth.
|
|
func (c *IdentityCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration used to connect to Auth.
|
|
func (c *IdentityCreds) TLSConfig() (*tls.Config, error) {
|
|
if err := c.load(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
tlsConfig, err := c.identityFile.TLSConfig()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return configure(tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration used to connect to Proxy.
|
|
func (c *IdentityCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
|
|
if err := c.load(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sshConfig, err := c.identityFile.SSHClientConfig()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return sshConfig, nil
|
|
}
|
|
|
|
// load is used to lazy load the identity file from persistent storage.
|
|
// This allows LoadIdentity to avoid possible errors for UX purposes.
|
|
func (c *IdentityCreds) load() error {
|
|
if c.identityFile != nil {
|
|
return nil
|
|
}
|
|
var err error
|
|
if c.identityFile, err = ReadIdentityFile(c.path); err != nil {
|
|
return trace.BadParameter("identity file could not be decoded: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadProfile is used to load credentials from a tsh Profile.
|
|
// If dir is not specified, the default profile path will be used.
|
|
// If name is not specified, the current profile name will be used.
|
|
func LoadProfile(dir, name string) *ProfileCreds {
|
|
return &ProfileCreds{
|
|
dir: dir,
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
// ProfileCreds are used to authenticate the client
|
|
// with a tsh profile with the given directory and name.
|
|
type ProfileCreds struct {
|
|
dir string
|
|
name string
|
|
profile *Profile
|
|
}
|
|
|
|
// Dialer is used to dial a connection to Auth.
|
|
func (c *ProfileCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) {
|
|
sshConfig, err := c.SSHClientConfig()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
dialer := NewTunnelDialer(*sshConfig, keepAliveInterval, dialTimeout)
|
|
return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) {
|
|
// Ping web proxy to retrieve tunnel proxy address.
|
|
pr, err := Find(ctx, c.profile.WebProxyAddr, false, nil)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
conn, err = dialer.DialContext(ctx, network, pr.Proxy.SSH.TunnelPublicAddr)
|
|
if err != nil {
|
|
// not wrapping on purpose to preserve the original error
|
|
return nil, err
|
|
}
|
|
return conn, nil
|
|
}), nil
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration used to connect to Auth.
|
|
func (c *ProfileCreds) TLSConfig() (*tls.Config, error) {
|
|
if err := c.load(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
tlsConfig, err := c.profile.TLSConfig()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return configure(tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration used to connect to Proxy.
|
|
func (c *ProfileCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
|
|
if err := c.load(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sshConfig, err := c.profile.SSHClientConfig()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return sshConfig, nil
|
|
}
|
|
|
|
// load is used to lazy load the profile from persistent storage.
|
|
// This allows LoadProfile to avoid possible errors for UX purposes.
|
|
func (c *ProfileCreds) load() error {
|
|
if c.profile != nil {
|
|
return nil
|
|
}
|
|
var err error
|
|
if c.profile, err = ProfileFromDir(c.dir, c.name); err != nil {
|
|
return trace.BadParameter("profile could not be decoded: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func configure(c *tls.Config) *tls.Config {
|
|
tlsConfig := c.Clone()
|
|
|
|
tlsConfig.NextProtos = []string{http2.NextProtoTLS}
|
|
|
|
if tlsConfig.ServerName == "" {
|
|
tlsConfig.ServerName = constants.APIDomain
|
|
}
|
|
|
|
// This logic still appears to be necessary to force client to always send
|
|
// a certificate regardless of the server setting. Otherwise the client may pick
|
|
// not to send the client certificate by looking at certificate request.
|
|
if len(tlsConfig.Certificates) > 0 {
|
|
cert := tlsConfig.Certificates[0]
|
|
tlsConfig.Certificates = nil
|
|
tlsConfig.GetClientCertificate = func(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
|
return &cert, nil
|
|
}
|
|
}
|
|
|
|
return tlsConfig
|
|
}
|