mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 06:08:23 +08:00
Update metalinter, fix a few lint warnings and replace deprecated linters. `deadcode`, `structcheck` and `varcheck` are abandoned and now replaced by [`unused`][1]. Since 1.19, `go fmt` reformats godocs according to https://go.dev/doc/comment. I've done a bulk-reformatting of the codebase to keep the linter happy. Backporting is mostly harmless (the exception being `lib/services/role_test.go`, that for some reason breaks the _old_ linter using the new format). [1]: https://golangci-lint.run/usage/linters/ * Bump golangci-lint version * Replace abandoned linters * Fix bodyclose on lib/auth/github.com * Fix bodyclose on lib/kube/proxy/streamproto/proto_test.go * Fix bodyclose on lib/srv/alpnproxy/proxy_test.go * Fix bodyclose on lib/web/conn_upgrade_test.go * Silence staticcheck on lib/kube/proxy/forwarder_test.go * Silence staticcheck on lib/utils/certs_test.go * Address BuildNameToCertificate deprecation warnings * Run `go fmt ./...` * Run `go fmt ./...` on api/ * Ignore formatting in role_test.go * Remove redundant initializers in lib/srv/uacc/ * Update e/
391 lines
11 KiB
Go
391 lines
11 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 (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"os"
|
|
|
|
"github.com/gravitational/teleport/api/constants"
|
|
"github.com/gravitational/teleport/api/identityfile"
|
|
"github.com/gravitational/teleport/api/profile"
|
|
"github.com/gravitational/teleport/api/utils"
|
|
|
|
"github.com/gravitational/trace"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/net/http2"
|
|
)
|
|
|
|
// Credentials are used to authenticate the API auth client. Some Credentials
|
|
// also provide other functionality, such as automatic address discovery and
|
|
// ssh connectivity.
|
|
//
|
|
// See the examples below for an example of each loader.
|
|
type Credentials interface {
|
|
// Dialer is used to create a dialer used to connect to the Auth server.
|
|
Dialer(cfg Config) (ContextDialer, error)
|
|
// TLSConfig returns TLS configuration used to authenticate the client.
|
|
TLSConfig() (*tls.Config, error)
|
|
// SSHClientConfig returns SSH configuration used to connect to the
|
|
// Auth server through a reverse tunnel.
|
|
SSHClientConfig() (*ssh.ClientConfig, error)
|
|
}
|
|
|
|
// LoadTLS is used to load Credentials directly from a *tls.Config.
|
|
//
|
|
// TLS creds can only be used to connect directly to a Teleport Auth server.
|
|
func LoadTLS(tlsConfig *tls.Config) Credentials {
|
|
return &tlsConfigCreds{
|
|
tlsConfig: tlsConfig,
|
|
}
|
|
}
|
|
|
|
// tlsConfigCreds use a defined *tls.Config to provide client credentials.
|
|
type tlsConfigCreds struct {
|
|
tlsConfig *tls.Config
|
|
}
|
|
|
|
// Dialer is used to dial a connection to an Auth server.
|
|
func (c *tlsConfigCreds) Dialer(cfg Config) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration.
|
|
func (c *tlsConfigCreds) TLSConfig() (*tls.Config, error) {
|
|
if c.tlsConfig == nil {
|
|
return nil, trace.BadParameter("tls config is nil")
|
|
}
|
|
return configureTLS(c.tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration.
|
|
func (c *tlsConfigCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
|
|
return nil, trace.NotImplemented("no ssh config")
|
|
}
|
|
|
|
// LoadKeyPair is used to load Credentials from a certicate keypair on disk.
|
|
//
|
|
// KeyPair Credentials can only be used to connect directly to a Teleport Auth server.
|
|
//
|
|
// New KeyPair files can be generated with tsh or tctl.
|
|
//
|
|
// $ tctl auth sign --format=tls --user=api-user --out=path/to/certs
|
|
//
|
|
// The certificates' time to live can be specified with --ttl.
|
|
//
|
|
// See the example below for usage.
|
|
func LoadKeyPair(certFile, keyFile, caFile string) Credentials {
|
|
return &keypairCreds{
|
|
certFile: certFile,
|
|
keyFile: keyFile,
|
|
caFile: caFile,
|
|
}
|
|
}
|
|
|
|
// keypairCreds use keypair certificates to provide client credentials.
|
|
type keypairCreds struct {
|
|
certFile string
|
|
keyFile string
|
|
caFile string
|
|
}
|
|
|
|
// Dialer is used to dial a connection to an Auth server.
|
|
func (c *keypairCreds) Dialer(cfg Config) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration.
|
|
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 := os.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 configureTLS(&tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
RootCAs: pool,
|
|
}), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration.
|
|
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.
|
|
//
|
|
// Identity Credentials can be used to connect to an auth server directly
|
|
// or through a reverse tunnel.
|
|
//
|
|
// A new identity file can be generated with tsh or tctl.
|
|
//
|
|
// $ tsh login --user=api-user --out=identity-file-path
|
|
// $ tctl auth sign --user=api-user --out=identity-file-path
|
|
//
|
|
// The identity file's time to live can be specified with --ttl.
|
|
//
|
|
// See the example below for usage.
|
|
func LoadIdentityFile(path string) Credentials {
|
|
return &identityCredsFile{
|
|
path: path,
|
|
}
|
|
}
|
|
|
|
// identityCredsFile use an identity file to provide client credentials.
|
|
type identityCredsFile struct {
|
|
identityFile *identityfile.IdentityFile
|
|
path string
|
|
}
|
|
|
|
// Dialer is used to dial a connection to an Auth server.
|
|
func (c *identityCredsFile) Dialer(cfg Config) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration.
|
|
func (c *identityCredsFile) 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 configureTLS(tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration.
|
|
func (c *identityCredsFile) 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 *identityCredsFile) load() error {
|
|
if c.identityFile != nil {
|
|
return nil
|
|
}
|
|
var err error
|
|
if c.identityFile, err = identityfile.ReadFile(c.path); err != nil {
|
|
return trace.BadParameter("identity file could not be decoded: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadIdentityFileFromString is used to load Credentials from a string containing identity file contents.
|
|
//
|
|
// Identity Credentials can be used to connect to an auth server directly
|
|
// or through a reverse tunnel.
|
|
//
|
|
// A new identity file can be generated with tsh or tctl.
|
|
//
|
|
// $ tsh login --user=api-user --out=identity-file-path
|
|
// $ tctl auth sign --user=api-user --out=identity-file-path
|
|
//
|
|
// The identity file's time to live can be specified with --ttl.
|
|
//
|
|
// See the example below for usage.
|
|
func LoadIdentityFileFromString(content string) Credentials {
|
|
return &identityCredsString{
|
|
content: content,
|
|
}
|
|
}
|
|
|
|
// identityCredsString use an identity file loaded to string to provide client credentials.
|
|
type identityCredsString struct {
|
|
identityFile *identityfile.IdentityFile
|
|
content string
|
|
}
|
|
|
|
// Dialer is used to dial a connection to an Auth server.
|
|
func (c *identityCredsString) Dialer(cfg Config) (ContextDialer, error) {
|
|
return nil, trace.NotImplemented("no dialer")
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration.
|
|
func (c *identityCredsString) 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 configureTLS(tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration.
|
|
func (c *identityCredsString) 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 a string.
|
|
func (c *identityCredsString) load() error {
|
|
if c.identityFile != nil {
|
|
return nil
|
|
}
|
|
var err error
|
|
if c.identityFile, err = identityfile.FromString(c.content); 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 on disk.
|
|
//
|
|
// dir is the profile directory. It will defaults to "~/.tsh".
|
|
//
|
|
// name is the profile name. It will default to the currently active tsh profile.
|
|
//
|
|
// Profile Credentials can be used to connect to an auth server directly
|
|
// or through a reverse tunnel.
|
|
//
|
|
// Profile Credentials will automatically attempt to find your reverse
|
|
// tunnel address and make a connection through it.
|
|
//
|
|
// A new profile can be generated with tsh.
|
|
//
|
|
// $ tsh login --user=api-user
|
|
func LoadProfile(dir, name string) Credentials {
|
|
return &profileCreds{
|
|
dir: dir,
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
// profileCreds use a tsh profile to provide client credentials.
|
|
type profileCreds struct {
|
|
dir string
|
|
name string
|
|
profile *profile.Profile
|
|
}
|
|
|
|
// Dialer is used to dial a connection to an Auth server.
|
|
func (c *profileCreds) Dialer(cfg Config) (ContextDialer, error) {
|
|
sshConfig, err := c.SSHClientConfig()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return NewProxyDialer(
|
|
*sshConfig,
|
|
cfg.KeepAlivePeriod,
|
|
cfg.DialTimeout,
|
|
c.profile.WebProxyAddr,
|
|
cfg.InsecureAddressDiscovery,
|
|
), nil
|
|
}
|
|
|
|
// TLSConfig returns TLS configuration.
|
|
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 configureTLS(tlsConfig), nil
|
|
}
|
|
|
|
// SSHClientConfig returns SSH configuration.
|
|
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 = profile.FromDir(c.dir, c.name); err != nil {
|
|
return trace.BadParameter("profile could not be decoded: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func configureTLS(c *tls.Config) *tls.Config {
|
|
tlsConfig := c.Clone()
|
|
tlsConfig.NextProtos = utils.Deduplicate(append(tlsConfig.NextProtos, http2.NextProtoTLS))
|
|
|
|
// If SNI isn't set, set it to the default name that can be found
|
|
// on all Teleport issued certificates. This is needed because we
|
|
// don't always know which host we will be connecting to.
|
|
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
|
|
}
|