diff --git a/lib/service/service.go b/lib/service/service.go index 0562ffb81dd97..f0708360de602 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -4256,19 +4256,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { tlscfg.InsecureSkipVerify = true tlscfg.ClientAuth = tls.RequireAnyClientCert } - tlscfg.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { - tlsClone := tlscfg.Clone() - - // Build the client CA pool containing the cluster's user CA in - // order to be able to validate certificates provided by users. - var err error - tlsClone.ClientCAs, _, err = auth.DefaultClientCertPool(accessPoint, clusterName) - if err != nil { - return nil, trace.Wrap(err) - } - - return tlsClone, nil - } + tlscfg.GetConfigForClient = auth.WithClusterCAs(tlscfg, accessPoint, clusterName, log) creds, err := auth.NewTransportCredentials(auth.TransportCredentialsConfig{ TransportCredentials: credentials.NewTLS(tlscfg), diff --git a/tool/tsh/common/proxy_test.go b/tool/tsh/common/proxy_test.go index 7eb1267cd515e..0febb429571dd 100644 --- a/tool/tsh/common/proxy_test.go +++ b/tool/tsh/common/proxy_test.go @@ -21,8 +21,13 @@ package common import ( "bytes" "context" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" "encoding/json" + "encoding/pem" "errors" "fmt" "net" @@ -56,17 +61,32 @@ import ( "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/teleagent" + "github.com/gravitational/teleport/lib/tlsca" ) // TestSSH verifies "tsh ssh" command. func TestSSH(t *testing.T) { - lib.SetInsecureDevMode(true) - defer lib.SetInsecureDevMode(false) + if webpkiCACert == nil { + t.Skip("the current platform doesn't support adding CAs to the system pool") + } + + t.Setenv("_TELEPORT_TEST_NO_PARALLEL", "1") + defer lib.SetInsecureDevMode(lib.IsInsecureDevMode()) + lib.SetInsecureDevMode(false) + + d := t.TempDir() + webCertPath := path.Join(d, "cert.pem") + webKeyPath := path.Join(d, "key.pem") + generateWebPKICert(t, webCertPath, webKeyPath) s := newTestSuite(t, withRootConfigFunc(func(cfg *servicecfg.Config) { cfg.Version = defaults.TeleportConfigVersionV2 cfg.Auth.NetworkingConfig.SetProxyListenerMode(types.ProxyListenerMode_Multiplex) + cfg.Proxy.KeyPairs = []servicecfg.KeyPairPath{{ + PrivateKey: webKeyPath, + Certificate: webCertPath, + }} }), withLeafCluster(), withLeafConfigFunc(func(cfg *servicecfg.Config) { @@ -74,6 +94,9 @@ func TestSSH(t *testing.T) { }), ) + // just in case someone changes newTestSuite + require.False(t, lib.IsInsecureDevMode()) + tests := []struct { name string fn func(t *testing.T, s *suite) @@ -102,7 +125,6 @@ func testRootClusterSSHAccess(t *testing.T, s *suite) { identityFile := mustLoginIdentity(t, s) err = Run(context.Background(), []string{ "--proxy", s.root.Config.Proxy.WebAddr.String(), - "--insecure", "-i", identityFile, "ssh", "localhost", @@ -127,7 +149,6 @@ func testLeafClusterSSHAccess(t *testing.T, s *suite) { identityFile := mustLoginIdentity(t, s) err := Run(context.Background(), []string{ "--proxy", s.root.Config.Proxy.WebAddr.String(), - "--insecure", "-i", identityFile, "ssh", "--cluster", s.leaf.Config.Auth.ClusterName.GetClusterName(), @@ -144,7 +165,6 @@ func testJumpHostSSHAccess(t *testing.T, s *suite) { // Switch to leaf cluster err := Run(context.Background(), []string{ "login", - "--insecure", s.leaf.Config.Auth.ClusterName.GetClusterName(), }, setMockSSOLogin(t, s), setHomePath(tshHome)) require.NoError(t, err) @@ -152,7 +172,6 @@ func testJumpHostSSHAccess(t *testing.T, s *suite) { // Connect to leaf node though jump host set to leaf proxy SSH port. err = Run(context.Background(), []string{ "ssh", - "--insecure", "-J", s.leaf.Config.Proxy.SSHAddr.Addr, s.leaf.Config.Hostname, "echo", "hello", @@ -163,7 +182,6 @@ func testJumpHostSSHAccess(t *testing.T, s *suite) { // Connect to leaf node though jump host set to proxy web port where TLS Routing is enabled. err = Run(context.Background(), []string{ "ssh", - "--insecure", "-J", s.leaf.Config.Proxy.WebAddr.Addr, s.leaf.Config.Hostname, "echo", "hello", @@ -179,7 +197,6 @@ func testJumpHostSSHAccess(t *testing.T, s *suite) { // Check JumpHost flow when root cluster is offline. err = Run(context.Background(), []string{ "ssh", - "--insecure", "-J", s.leaf.Config.Proxy.WebAddr.Addr, s.leaf.Config.Hostname, "echo", "hello", @@ -188,6 +205,31 @@ func testJumpHostSSHAccess(t *testing.T, s *suite) { }) } +func generateWebPKICert(t *testing.T, certPath, keyPath string) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + require.NoError(t, err) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + require.NotNil(t, keyPEM) + + ca := &tlsca.CertAuthority{ + Cert: webpkiCACert, + Signer: webpkiCAKey, + } + certPEM, err := ca.GenerateCertificate(tlsca.CertificateRequest{ + PublicKey: key.Public(), + Subject: pkix.Name{CommonName: "proxy web addr cert"}, + NotAfter: time.Now().Add(12 * time.Hour), + DNSNames: []string{"127.0.0.1"}, + KeyUsage: x509.KeyUsageDigitalSignature, + }) + require.NoError(t, err) + + require.NoError(t, os.WriteFile(certPath, certPEM, 0o644)) + require.NoError(t, os.WriteFile(keyPath, keyPEM, 0o644)) +} + // TestWithRsync tests that Teleport works with rsync. func TestWithRsync(t *testing.T) { disableAgent(t) diff --git a/tool/tsh/common/webpki_test.go b/tool/tsh/common/webpki_test.go new file mode 100644 index 0000000000000..e71882b1d759a --- /dev/null +++ b/tool/tsh/common/webpki_test.go @@ -0,0 +1,92 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// We use x509usefallbackroots to replace the system cert pool with a system +// cert pool with an extra CA. +//go:debug x509usefallbackroots=1 + +package common + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "runtime" + "time" +) + +// webpkiCACert and webpkiCAKey are a CA added to the system cert pool. +var ( + webpkiCACert *x509.Certificate + webpkiCAKey *ecdsa.PrivateKey +) + +func init() { + // we can't add to the system cert pool on platforms with a system verifier, + // because verifying against the default cert pool only uses the system + // verifier, so on those we just don't set up our additional CA + // + // TODO(espadolini): see if embedding the mozilla trust store could work + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" { + return + } + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + panic(err) + } + + template := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{CommonName: "Teleport testing web PKI CA"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, + }, + IsCA: true, + BasicConstraintsValid: true, + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(err) + } + + der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if err != nil { + panic(err) + } + + cert, err := x509.ParseCertificate(der) + if err != nil { + panic(err) + } + + pool, err := x509.SystemCertPool() + if err != nil { + pool = x509.NewCertPool() + } + pool.AddCert(cert) + x509.SetFallbackRoots(pool) + + webpkiCACert, webpkiCAKey = cert, key +}