mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-21 14:35:22 +08:00
Add support for backward compatible API Client behavior (#11567)
This commit is contained in:
+56
-5
@@ -109,8 +109,41 @@ func (p *Profile) TLSConfig() (*tls.Config, error) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
pool, err := certPoolFromProfile(p)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: pool,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func certPoolFromProfile(p *Profile) (*x509.CertPool, error) {
|
||||
// Check if CAS dir exist if not try to load certs from legacy certs.pem file.
|
||||
if _, err := os.Stat(p.TLSClusterCASDir()); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
pool, err := certPoolFromLegacyCAFile(p)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// Load CertPool from CAS directory.
|
||||
pool, err := certPoolFromCASDir(p)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func certPoolFromCASDir(p *Profile) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
err = filepath.Walk(p.TLSClusterCASDir(), func(path string, info fs.FileInfo, err error) error {
|
||||
err := filepath.Walk(p.TLSClusterCASDir(), func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -129,11 +162,19 @@ func (p *Profile) TLSConfig() (*tls.Config, error) {
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: pool,
|
||||
}, nil
|
||||
func certPoolFromLegacyCAFile(p *Profile) (*x509.CertPool, error) {
|
||||
caCerts, err := os.ReadFile(p.TLSCAsPath())
|
||||
if err != nil {
|
||||
return nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(caCerts) {
|
||||
return nil, trace.BadParameter("invalid CA cert PEM")
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// SSHClientConfig returns the profile's associated SSHClientConfig.
|
||||
@@ -336,6 +377,11 @@ func (p *Profile) TLSCertPath() string {
|
||||
return keypaths.TLSCertPath(p.Dir, p.Name(), p.Username)
|
||||
}
|
||||
|
||||
// TLSCAsLegacyPath returns the path to the profile's TLS certificate authorities.
|
||||
func (p *Profile) TLSCAsLegacyPath() string {
|
||||
return keypaths.TLSCAsPath(p.Dir, p.Name())
|
||||
}
|
||||
|
||||
// TLSCAPathCluster returns CA for particular cluster.
|
||||
func (p *Profile) TLSCAPathCluster(cluster string) string {
|
||||
return keypaths.TLSCAsPathCluster(p.Dir, p.Name(), cluster)
|
||||
@@ -346,6 +392,11 @@ func (p *Profile) TLSClusterCASDir() string {
|
||||
return keypaths.CAsDir(p.Dir, p.Name())
|
||||
}
|
||||
|
||||
// TLSCAsPath returns the legacy path to the profile's TLS certificate authorities.
|
||||
func (p *Profile) TLSCAsPath() string {
|
||||
return keypaths.TLSCAsPath(p.Dir, p.Name())
|
||||
}
|
||||
|
||||
// SSHDir returns the path to the profile's ssh directory.
|
||||
func (p *Profile) SSHDir() string {
|
||||
return keypaths.SSHDir(p.Dir, p.Name(), p.Username)
|
||||
|
||||
@@ -34,6 +34,8 @@ const (
|
||||
fileNameKnownHosts = "known_hosts"
|
||||
// fileExtTLSCert is the suffix/extension of a file where a TLS cert is stored.
|
||||
fileExtTLSCert = "-x509.pem"
|
||||
// fileNameTLSCerts is a file where TLS Cert Authorities are stored.
|
||||
fileNameTLSCerts = "certs.pem"
|
||||
// fileExtCert is the suffix/extension of a file where an SSH Cert is stored.
|
||||
fileExtSSHCert = "-cert.pub"
|
||||
// fileExtPub is the extension of a file where a public key is stored.
|
||||
@@ -144,6 +146,14 @@ func CAsDir(baseDir, proxy string) string {
|
||||
return filepath.Join(ProxyKeyDir(baseDir, proxy), casDir)
|
||||
}
|
||||
|
||||
// TLSCAsPath returns the path to the users's TLS CA's certificates
|
||||
// for the given proxy.
|
||||
// <baseDir>/keys/<proxy>/certs.pem
|
||||
// DELETE IN 10.0. Deprecated
|
||||
func TLSCAsPath(baseDir, proxy string) string {
|
||||
return filepath.Join(ProxyKeyDir(baseDir, proxy), fileNameTLSCerts)
|
||||
}
|
||||
|
||||
// TLSCAsPathCluster returns the path to the specified cluster's CA directory.
|
||||
//
|
||||
// <baseDir>/keys/<proxy>/cas/<cluster>.pem
|
||||
|
||||
+39
-1
@@ -520,6 +520,11 @@ func (fs *fsLocalNonSessionKeyStore) tlsCertPath(idx KeyIndex) string {
|
||||
return keypaths.TLSCertPath(fs.KeyDir, idx.ProxyHost, idx.Username)
|
||||
}
|
||||
|
||||
// tlsCAsPath returns the TLS CA certificates path for the given KeyIndex.
|
||||
func (fs *fsLocalNonSessionKeyStore) tlsCAsPath(proxy string) string {
|
||||
return keypaths.TLSCAsPath(fs.KeyDir, proxy)
|
||||
}
|
||||
|
||||
// sshCertPath returns the SSH certificate path for the given KeyIndex.
|
||||
func (fs *fsLocalNonSessionKeyStore) sshCertPath(idx KeyIndex) string {
|
||||
return keypaths.SSHCertPath(fs.KeyDir, idx.ProxyHost, idx.Username, idx.ClusterName)
|
||||
@@ -680,6 +685,20 @@ func (fs *fsLocalNonSessionKeyStore) SaveTrustedCerts(proxyHost string, cas []au
|
||||
return trace.ConvertSystemError(err)
|
||||
}
|
||||
|
||||
// Save trusted clusters certs in CAS directory.
|
||||
if err := fs.saveTrustedCertsInCASDir(proxyHost, cas); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// For backward compatibility save trusted in legacy certs.pem file.
|
||||
if err := fs.saveTrustedCertsInLegacyCAFile(proxyHost, cas); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fsLocalNonSessionKeyStore) saveTrustedCertsInCASDir(proxyHost string, cas []auth.TrustedCerts) error {
|
||||
casDirPath := filepath.Join(fs.casDir(proxyHost))
|
||||
if err := os.MkdirAll(casDirPath, os.ModeDir|profileDirPerms); err != nil {
|
||||
fs.log.Error(err)
|
||||
@@ -700,11 +719,30 @@ func (fs *fsLocalNonSessionKeyStore) SaveTrustedCerts(proxyHost string, cas []au
|
||||
if err := writeClusterCertificates(caFile, ca.TLSCertificates); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fsLocalNonSessionKeyStore) saveTrustedCertsInLegacyCAFile(proxyHost string, cas []auth.TrustedCerts) (retErr error) {
|
||||
certsFile := fs.tlsCAsPath(proxyHost)
|
||||
fp, err := os.OpenFile(certsFile, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return trace.ConvertSystemError(err)
|
||||
}
|
||||
defer utils.StoreErrorOf(fp.Close, &retErr)
|
||||
for _, ca := range cas {
|
||||
for _, cert := range ca.TLSCertificates {
|
||||
if _, err := fp.Write(cert); err != nil {
|
||||
return trace.ConvertSystemError(err)
|
||||
}
|
||||
if _, err := fmt.Fprintln(fp); err != nil {
|
||||
return trace.ConvertSystemError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fp.Sync()
|
||||
}
|
||||
|
||||
// isSafeClusterName check if cluster name is safe and doesn't contain miscellaneous characters.
|
||||
func isSafeClusterName(name string) bool {
|
||||
return !strings.Contains(name, "..")
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/user"
|
||||
"testing"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
apiclient "github.com/gravitational/teleport/api/client"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/config"
|
||||
"github.com/gravitational/teleport/lib/service"
|
||||
@@ -246,3 +248,16 @@ func waitForEvents(t *testing.T, svc service.Supervisor, events ...string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustCreateAuthClientFormUserProfile(t *testing.T, tshHomePath, addr string) {
|
||||
ctx := context.Background()
|
||||
credentials := apiclient.LoadProfile(tshHomePath, "")
|
||||
c, err := apiclient.New(context.Background(), apiclient.Config{
|
||||
Addrs: []string{addr},
|
||||
Credentials: []apiclient.Credentials{credentials},
|
||||
InsecureAddressDiscovery: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = c.Ping(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
apidefaults "github.com/gravitational/teleport/api/defaults"
|
||||
"github.com/gravitational/teleport/api/profile"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
@@ -1234,6 +1235,45 @@ func TestSetX11Config(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuthClientFromTSHProfile tests if API Client can be successfully created from tsh profile where clusters
|
||||
// certs are stored separately in CAS directory and in case where legacy certs.pem file was used.
|
||||
func TestAuthClientFromTSHProfile(t *testing.T) {
|
||||
tmpHomePath := t.TempDir()
|
||||
|
||||
connector := mockConnector(t)
|
||||
alice, err := types.NewUser("alice@example.com")
|
||||
require.NoError(t, err)
|
||||
alice.SetRoles([]string{"access"})
|
||||
authProcess, proxyProcess := makeTestServers(t, withBootstrap(connector, alice))
|
||||
authServer := authProcess.GetAuthServer()
|
||||
require.NotNil(t, authServer)
|
||||
proxyAddr, err := proxyProcess.ProxyWebAddr()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = Run([]string{
|
||||
"login",
|
||||
"--insecure",
|
||||
"--debug",
|
||||
"--auth", connector.GetName(),
|
||||
"--proxy", proxyAddr.String(),
|
||||
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
|
||||
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
profile, err := profile.FromDir(tmpHomePath, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
mustCreateAuthClientFormUserProfile(t, tmpHomePath, proxyAddr.String())
|
||||
|
||||
// Simulate legacy tsh client behavior where all clusters certs were stored in the certs.pem file.
|
||||
require.NoError(t, os.RemoveAll(profile.TLSClusterCASDir()))
|
||||
|
||||
// Verify that authClient created from profile will create a valid client in case where cas dir doesn't exit.
|
||||
mustCreateAuthClientFormUserProfile(t, tmpHomePath, proxyAddr.String())
|
||||
}
|
||||
|
||||
type testServersOpts struct {
|
||||
bootstrap []types.Resource
|
||||
authConfigFuncs []func(cfg *service.AuthConfig)
|
||||
|
||||
Reference in New Issue
Block a user