From a48c4f77654dfadcac40fcadddfb8169e92dd252 Mon Sep 17 00:00:00 2001 From: Nic Klaassen Date: Wed, 26 Jun 2024 16:06:22 -0700 Subject: [PATCH] configurable key algorithms for proxy to database agent certs (#43329) * configurable algorithms for proxy to database agent certs * rfd edit * use v1 suite in test * fix typo * fix bad merge --- api/utils/keys/privatekey.go | 14 ++-- lib/cryptosuites/suites.go | 92 ++++++++++++++----------- lib/srv/db/access_test.go | 5 +- lib/srv/db/proxyserver.go | 7 +- rfd/0136-modern-signature-algorithms.md | 32 +++++++-- 5 files changed, 94 insertions(+), 56 deletions(-) diff --git a/api/utils/keys/privatekey.go b/api/utils/keys/privatekey.go index 8abcad2b22adf..94ea8f37b3669 100644 --- a/api/utils/keys/privatekey.go +++ b/api/utils/keys/privatekey.go @@ -91,11 +91,17 @@ func (k *PrivateKey) PrivateKeyPEM() []byte { return k.keyPEM } -// TLSCertificate parses the given TLS certificate(s) paired with the private key -// to rerturn a tls.Certificate, ready to be used in a TLS handshake. +// TLSCertificate parses the given TLS certificate(s) paired with the private +// key to return a tls.Certificate, ready to be used in a TLS handshake. func (k *PrivateKey) TLSCertificate(certPEMBlock []byte) (tls.Certificate, error) { + return TLSCertificateForSigner(k.Signer, certPEMBlock) +} + +// TLSCertificate parses the given TLS certificate(s) paired with the given +// signer to return a tls.Certificate, ready to be used in a TLS handshake. +func TLSCertificateForSigner(signer crypto.Signer, certPEMBlock []byte) (tls.Certificate, error) { cert := tls.Certificate{ - PrivateKey: k.Signer, + PrivateKey: signer, } var skippedBlockTypes []string @@ -125,7 +131,7 @@ func (k *PrivateKey) TLSCertificate(certPEMBlock []byte) (tls.Certificate, error return tls.Certificate{}, trace.Wrap(err) } - if keyPub, ok := k.Public().(cryptoPublicKeyI); !ok { + if keyPub, ok := signer.Public().(cryptoPublicKeyI); !ok { return tls.Certificate{}, trace.BadParameter("private key does not contain a valid public key") } else if !keyPub.Equal(x509Cert.PublicKey) { return tls.Certificate{}, trace.BadParameter("private key does not match certificate's public key") diff --git a/lib/cryptosuites/suites.go b/lib/cryptosuites/suites.go index 224e587bc1373..765b73dc246a2 100644 --- a/lib/cryptosuites/suites.go +++ b/lib/cryptosuites/suites.go @@ -75,9 +75,11 @@ const ( // SPIFFECAJWT represents the JWT key for the spiffe CA. SPIFFECAJWT - // New key purposes should be added here. + // ProxyToDatabaseAgent represents keys used by the Proxy to dial the + // Database agent over a reverse tunnel. + ProxyToDatabaseAgent - // TODO(nklaassen): define subject key purposes. Currently only CA key purposes are defined above. + // TODO(nklaassen): define remaining key purposes. // keyPurposeMax is 1 greater than the last valid key purpose, used to test that all values less than this // are valid for each suite. @@ -134,45 +136,50 @@ var ( SAMLIdPCATLS: RSA2048, SPIFFECATLS: RSA2048, SPIFFECAJWT: RSA2048, - // TODO(nklaassen): subject key purposes. + // We could consider updating this algorithm even in the legacy suite, only database agents need to + // accept these connections and they have never restricted algorithm support. + ProxyToDatabaseAgent: RSA2048, + // TODO(nklaassen): define remaining key purposes. } // balancedV1 strikes a balance between security, compatibility, and // performance. It uses ECDSA256, Ed25591, and 2048-bit RSA. It is not // completely implemented yet. balancedV1 = suite{ - UserCATLS: ECDSAP256, - UserCASSH: Ed25519, - HostCATLS: ECDSAP256, - HostCASSH: Ed25519, - DatabaseCATLS: RSA2048, - DatabaseClientCATLS: RSA2048, - OpenSSHCASSH: Ed25519, - JWTCAJWT: ECDSAP256, - OIDCIdPCAJWT: ECDSAP256, - SAMLIdPCATLS: ECDSAP256, - SPIFFECATLS: ECDSAP256, - SPIFFECAJWT: ECDSAP256, - // TODO(nklaassen): subject key purposes. + UserCATLS: ECDSAP256, + UserCASSH: Ed25519, + HostCATLS: ECDSAP256, + HostCASSH: Ed25519, + DatabaseCATLS: RSA2048, + DatabaseClientCATLS: RSA2048, + OpenSSHCASSH: Ed25519, + JWTCAJWT: ECDSAP256, + OIDCIdPCAJWT: ECDSAP256, + SAMLIdPCATLS: ECDSAP256, + SPIFFECATLS: ECDSAP256, + SPIFFECAJWT: ECDSAP256, + ProxyToDatabaseAgent: ECDSAP256, + // TODO(nklaassen): define remaining key purposes. } // fipsv1 is an algorithm suite tailored for FIPS compliance. It is based on // the balancedv1 suite but replaces all instances of Ed25519 with ECDSA on // the NIST P256 curve. It is not completely implemented yet. fipsv1 = suite{ - UserCATLS: ECDSAP256, - UserCASSH: ECDSAP256, - HostCATLS: ECDSAP256, - HostCASSH: ECDSAP256, - DatabaseCATLS: RSA2048, - DatabaseClientCATLS: RSA2048, - OpenSSHCASSH: ECDSAP256, - JWTCAJWT: ECDSAP256, - OIDCIdPCAJWT: ECDSAP256, - SAMLIdPCATLS: ECDSAP256, - SPIFFECATLS: ECDSAP256, - SPIFFECAJWT: ECDSAP256, - // TODO(nklaassen): subject key purposes. + UserCATLS: ECDSAP256, + UserCASSH: ECDSAP256, + HostCATLS: ECDSAP256, + HostCASSH: ECDSAP256, + DatabaseCATLS: RSA2048, + DatabaseClientCATLS: RSA2048, + OpenSSHCASSH: ECDSAP256, + JWTCAJWT: ECDSAP256, + OIDCIdPCAJWT: ECDSAP256, + SAMLIdPCATLS: ECDSAP256, + SPIFFECATLS: ECDSAP256, + SPIFFECAJWT: ECDSAP256, + ProxyToDatabaseAgent: ECDSAP256, + // TODO(nklaassen): define remaining key purposes. } // hsmv1 in an algorithm suite tailored for clusters using an HSM or KMS @@ -181,19 +188,20 @@ var ( // only*. It is also valid to use the legacy or fipsv1 suites if your // cluster uses an HSM or KMS. It is not completely implemented yet. hsmv1 = suite{ - UserCATLS: ECDSAP256, - UserCASSH: ECDSAP256, - HostCATLS: ECDSAP256, - HostCASSH: ECDSAP256, - DatabaseCATLS: RSA2048, - DatabaseClientCATLS: RSA2048, - OpenSSHCASSH: ECDSAP256, - JWTCAJWT: ECDSAP256, - OIDCIdPCAJWT: ECDSAP256, - SAMLIdPCATLS: ECDSAP256, - SPIFFECATLS: ECDSAP256, - SPIFFECAJWT: ECDSAP256, - // TODO(nklaassen): subject key purposes. + UserCATLS: ECDSAP256, + UserCASSH: ECDSAP256, + HostCATLS: ECDSAP256, + HostCASSH: ECDSAP256, + DatabaseCATLS: RSA2048, + DatabaseClientCATLS: RSA2048, + OpenSSHCASSH: ECDSAP256, + JWTCAJWT: ECDSAP256, + OIDCIdPCAJWT: ECDSAP256, + SAMLIdPCATLS: ECDSAP256, + SPIFFECATLS: ECDSAP256, + SPIFFECAJWT: ECDSAP256, + ProxyToDatabaseAgent: ECDSAP256, + // TODO(nklaassen): define remaining key purposes. } allSuites = map[types.SignatureAlgorithmSuite]suite{ diff --git a/lib/srv/db/access_test.go b/lib/srv/db/access_test.go index a0ba90f38f70b..bb6101c1cebb6 100644 --- a/lib/srv/db/access_test.go +++ b/lib/srv/db/access_test.go @@ -2240,7 +2240,10 @@ func setupTestContext(ctx context.Context, t testing.TB, withDatabases ...withDa authServer, err := auth.NewTestAuthServer(auth.TestAuthServerConfig{ Clock: testCtx.clock, ClusterName: testCtx.clusterName, - Dir: t.TempDir(), + AuthPreferenceSpec: &types.AuthPreferenceSpecV2{ + SignatureAlgorithmSuite: types.SignatureAlgorithmSuite_SIGNATURE_ALGORITHM_SUITE_BALANCED_V1, + }, + Dir: t.TempDir(), }) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, authServer.Close()) }) diff --git a/lib/srv/db/proxyserver.go b/lib/srv/db/proxyserver.go index f67f7cb204bbb..7b7a94e6bd6b3 100644 --- a/lib/srv/db/proxyserver.go +++ b/lib/srv/db/proxyserver.go @@ -42,10 +42,11 @@ import ( apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" + "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/authclient" - "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/cryptosuites" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/observability/metrics" @@ -616,7 +617,7 @@ func (s *ProxyServer) getDatabaseServers(ctx context.Context, identity tlsca.Ide func (s *ProxyServer) getConfigForServer(ctx context.Context, identity tlsca.Identity, server types.DatabaseServer) (*tls.Config, error) { defer observeLatency(tlsConfigTime.With(getLabelsFromDB(server.GetDatabase())))() - privateKey, err := native.GeneratePrivateKey() + privateKey, err := cryptosuites.GenerateKey(ctx, s.cfg.AccessPoint, cryptosuites.ProxyToDatabaseAgent) if err != nil { return nil, trace.Wrap(err) } @@ -637,7 +638,7 @@ func (s *ProxyServer) getConfigForServer(ctx context.Context, identity tlsca.Ide return nil, trace.Wrap(err) } - cert, err := privateKey.TLSCertificate(response.Cert) + cert, err := keys.TLSCertificateForSigner(privateKey, response.Cert) if err != nil { return nil, trace.Wrap(err) } diff --git a/rfd/0136-modern-signature-algorithms.md b/rfd/0136-modern-signature-algorithms.md index d56e620bf9e8f..484d1921e0182 100644 --- a/rfd/0136-modern-signature-algorithms.md +++ b/rfd/0136-modern-signature-algorithms.md @@ -169,16 +169,16 @@ The following key types will be used when the configured algorithm suite is * TLS: ECDSA with NIST P-256 (X.509 cert signed by host CA) * OpenSSH hosts * SSH: Ed25519 (SSH cert signed by host CA) - * User certs for "Agentless" SSH connections + * proxy -> Agentless/OpenSSH certs * SSH: Ed25519 (SSH certs signed by OpenSSH CA) * proxy -> database agent - * 2048-bit RSA (X.509 cert signed by Database CA) - * this may be forwarded directly to the database + * ECDSA with NIST P-256 (X.509 cert signed by Host CA) * database agent -> self-hosted database - * 2048-bit RSA (X.509 cert signed by Database CA) + * 2048-bit RSA (X.509 cert signed by Database Client CA) + * for Snowflake access this is a JWT signed by the Database Client CA + * maybe we could choose the subject key algorithm per-database * self-hosted database * 2048-bit RSA (X.509 cert signed by Database CA) - * this may be a JWT for Snowflake access * windows desktop service -> RDP server * 2048-bit RSA (X.509 cert signed by user CA) * this is a current limitation of our rdpclient implementation, we could @@ -280,7 +280,11 @@ uses: user ssh cert signing, user tls cert signing, ssh hosts trust this CA keys: ssh, tls -uses: host ssh cert signing, host tls cert signing, ssh clients trust this CA +uses: + +* signs host ssh certs +* signs host tls certs +* ssh clients trust this CA * current/`legacy` SSH key type: 2048-bit RSA * proposed `balanced-v1` key type: Ed25519 @@ -310,6 +314,22 @@ uses: * signs (often) long-lived db cert used to authenticate db to database service * signs short-lived proxy cert used to authenticate proxy to database service + +* current/`legacy` TLS key type: 2048-bit RSA +* proposed `balanced-v1` key type: 2048-bit RSA +* proposed `fips-v1` key type: 2048-bit RSA +* proposed `hsm-v1` key type: 2048-bit RSA +* reasoning: + * some database protocols still require RSA, reduce friction by keeping it as + the default + +#### Database Client CA + +keys: tls + +uses: + +* signs short-lived certs used to authenticate db service to databases * signs snowflake JWTs * self-hosted databases (and Snowflake) trust this CA