Add strict CA checks on newKeySet() and address test code duplication (#62177)

* Add strict CA checks on newKeySet() and NewTestCAWithConfig()

* Consolidate the many copies of NewTestCAWithConfig()

* Add authcatest to depguard

* Check CAKeySet before returning

* Appease linter (authtest can import test packages)

* Rename types, drop TODO

* Remove panics from authcatest

* Propagate errors on lib/cache

* Remove lib/cache.NewTestCA alias

* Remove lib/authtest.NewCAWithConfig alias

* Remove lib/authtest.CAConfig alias

* Remove the lib/authtest.NewCA alias

* Remove lib/services/local.NewTestCA alias
This commit is contained in:
Alan Parra
2025-12-12 15:08:26 -03:00
committed by GitHub
parent 097ce25b9f
commit 383b96f353
14 changed files with 396 additions and 509 deletions
+6 -3
View File
@@ -192,17 +192,18 @@ linters:
test_packages:
files:
- '!$test'
- '!**/integrations/operator/controllers/resources/testlib/**.go'
- '!**/e/lib/aws/identitycenter/test/**'
- '!**/e/lib/devicetrust/testenv/**'
- '!**/e/lib/jamf/testenv/**'
- '!**/e/lib/intune/testenv/**'
- '!**/e/lib/idp/saml/testenv/**'
- '!**/e/lib/intune/testenv/**'
- '!**/e/lib/jamf/testenv/**'
- '!**/e/lib/operatortest/**'
- '!**/e/tests/**'
- '!**/integration/db/fixture.go'
- '!**/integration/helpers/**'
- '!**/integrations/lib/testing/**'
- '!**/integrations/operator/controllers/resources/testlib/**.go'
- '!**/lib/auth/authtest/**'
deny:
- pkg: github.com/gravitational/teleport/e/lib/aws/identitycenter/test
desc: testing packages should not be imported outside of _test.go files
@@ -230,6 +231,8 @@ linters:
desc: testing packages should not be imported outside of _test.go files
- pkg: github.com/gravitational/teleport/lib/utils/logtesttest
desc: testing packages should not be imported outside of _test.go files
- pkg: github.com/gravitational/teleport/lib/auth/authcatest
desc: testing packages should not be imported outside of _test.go files
- pkg: github.com/gravitational/teleport/lib/auth/authtest
desc: testing packages should not be imported outside of _test.go files
- pkg: github.com/gravitational/teleport/lib/cryptosuites/cryptosuitestest
+26 -1
View File
@@ -8247,8 +8247,28 @@ func (a *Server) addAdditionalTrustedKeysAtomic(ctx context.Context, ca types.Ce
}
// newKeySet generates a new sets of keys for a given CA type.
// Keep this function in sync with lib/services/suite/suite.go:NewTestCAWithConfig().
// Keep this function in sync with lib/auth/authcatest.NewTestCAWithConfig().
func newKeySet(ctx context.Context, keyStore *keystore.Manager, caID types.CertAuthID) (types.CAKeySet, error) {
switch caID.Type {
case
types.HostCA,
types.UserCA,
types.DatabaseCA,
types.DatabaseClientCA,
types.OpenSSHCA,
types.JWTSigner,
types.SAMLIDPCA,
types.OIDCIdPCA,
types.SPIFFECA,
types.OktaCA,
types.AWSRACA,
types.BoundKeypairCA:
// OK, known CA type.
default:
return types.CAKeySet{}, trace.BadParameter(
"cannot generate new key set for unknown CA type %q", caID.Type)
}
var keySet types.CAKeySet
// Add SSH keys if necessary.
@@ -8281,6 +8301,11 @@ func newKeySet(ctx context.Context, keyStore *keystore.Manager, caID types.CertA
keySet.JWT = append(keySet.JWT, jwtKeyPair)
}
// Sanity check that the key set has at least one key.
if len(keySet.SSH) == 0 && len(keySet.TLS) == 0 && len(keySet.JWT) == 0 {
return types.CAKeySet{}, trace.BadParameter("no keys generated for CA type %q", caID.Type)
}
return keySet, nil
}
+23 -11
View File
@@ -68,6 +68,7 @@ import (
"github.com/gravitational/teleport/api/utils/sshutils"
"github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/authtest"
"github.com/gravitational/teleport/lib/auth/keystore"
@@ -216,14 +217,19 @@ func newTestPack(ctx context.Context, opts testPackOptions) (p testPack, err err
return testPack{}, trace.Wrap(err)
}
if err := p.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.UserCA, p.clusterName.GetClusterName())); err != nil {
return testPack{}, trace.Wrap(err)
}
if err := p.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.HostCA, p.clusterName.GetClusterName())); err != nil {
return testPack{}, trace.Wrap(err)
}
if err := p.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.OpenSSHCA, p.clusterName.GetClusterName())); err != nil {
return testPack{}, trace.Wrap(err)
clusterName := p.clusterName.GetClusterName()
for _, caType := range []types.CertAuthType{
types.UserCA,
types.HostCA,
types.OpenSSHCA,
} {
ca, err := authcatest.NewCA(caType, clusterName)
if err != nil {
return testPack{}, trace.Wrap(err)
}
if err := p.a.UpsertCertAuthority(ctx, ca); err != nil {
return testPack{}, trace.Wrap(err)
}
}
return p, nil
@@ -1315,8 +1321,12 @@ func TestTrustedClusterCRUDEventEmitted(t *testing.T) {
_, err = s.a.Services.UpsertTrustedCluster(ctx, tc)
require.NoError(t, err)
require.NoError(t, s.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.UserCA, "test")))
require.NoError(t, s.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.HostCA, "test")))
userCA, err := authcatest.NewCA(types.UserCA, "test")
require.NoError(t, err)
require.NoError(t, s.a.UpsertCertAuthority(ctx, userCA))
hostCA, err := authcatest.NewCA(types.HostCA, "test")
require.NoError(t, err)
require.NoError(t, s.a.UpsertCertAuthority(ctx, hostCA))
err = s.a.CreateReverseTunnel(ctx, tc)
require.NoError(t, err)
@@ -4047,7 +4057,9 @@ func TestCAGeneration(t *testing.T) {
for _, caType := range types.CertAuthTypes {
t.Run(string(caType), func(t *testing.T) {
testKeySet := authtest.NewTestCA(caType, clusterName, privKey).Spec.ActiveKeys
ca, err := authcatest.NewCA(caType, clusterName, privKey)
require.NoError(t, err)
testKeySet := ca.Spec.ActiveKeys
keySet, err := auth.NewKeySet(ctx, keyStoreManager, types.CertAuthID{Type: caType, DomainName: clusterName})
require.NoError(t, err)
+195
View File
@@ -0,0 +1,195 @@
// Teleport
// Copyright (C) 2025 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 <http://www.gnu.org/licenses/>.
// Package authcatest holds helpers to create CAs for testing.
package authcatest
import (
"crypto/x509/pkix"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/cryptosuites"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/tlsca"
)
// CAConfig defines the configuration for generating a test certificate
// authority.
type CAConfig struct {
Type types.CertAuthType
PrivateKeys [][]byte
Clock clockwork.Clock
ClusterName string
// the below string fields default to ClusterName if left empty
ResourceName string
SubjectOrganization string
}
// NewCA returns new test authority with a test key as a public and signing key.
func NewCA(
caType types.CertAuthType,
clusterName string,
privateKeys ...[]byte,
) (*types.CertAuthorityV2, error) {
return NewCAWithConfig(CAConfig{
Type: caType,
ClusterName: clusterName,
PrivateKeys: privateKeys,
Clock: clockwork.NewRealClock(),
})
}
// NewCAWithConfig generates a new certificate authority with the specified
// configuration.
//
// Keep this function in-sync with lib/auth.newKeySet().
func NewCAWithConfig(config CAConfig) (*types.CertAuthorityV2, error) {
switch config.Type {
case
types.HostCA,
types.UserCA,
types.DatabaseCA,
types.DatabaseClientCA,
types.OpenSSHCA,
types.JWTSigner,
types.SAMLIDPCA,
types.OIDCIdPCA,
types.SPIFFECA,
types.OktaCA,
types.AWSRACA,
types.BoundKeypairCA:
// OK, known CA type.
default:
return nil, trace.BadParameter("cannot generate new key set for unknown CA type %q", config.Type)
}
var keyPEM []byte
var key *keys.PrivateKey
if config.ResourceName == "" {
config.ResourceName = config.ClusterName
}
if config.SubjectOrganization == "" {
config.SubjectOrganization = config.ClusterName
}
switch config.Type {
case types.DatabaseCA, types.SAMLIDPCA, types.OIDCIdPCA:
// These CAs only support RSA.
keyPEM = fixtures.PEMBytes["rsa"]
case types.DatabaseClientCA:
// The db client CA also only supports RSA, but some tests rely on it
// being different than the DB CA.
keyPEM = fixtures.PEMBytes["rsa-db-client"]
}
if len(config.PrivateKeys) > 0 {
// Allow test to override the private key.
keyPEM = config.PrivateKeys[0]
}
if keyPEM != nil {
var err error
key, err = keys.ParsePrivateKey(keyPEM)
if err != nil {
return nil, trace.Wrap(err)
}
} else {
// If config.PrivateKeys was not set and this CA does not exclusively
// support RSA, generate an ECDSA key. Signatures are ~10x faster than
// RSA and generating a new key is actually faster than parsing a PEM
// fixture.
signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
if err != nil {
return nil, trace.Wrap(err)
}
key, err = keys.NewPrivateKey(signer)
if err != nil {
return nil, trace.Wrap(err)
}
keyPEM = key.PrivateKeyPEM()
}
ca := &types.CertAuthorityV2{
Kind: types.KindCertAuthority,
SubKind: string(config.Type),
Version: types.V2,
Metadata: types.Metadata{
Name: config.ResourceName,
Namespace: apidefaults.Namespace,
},
Spec: types.CertAuthoritySpecV2{
Type: config.Type,
ClusterName: config.ClusterName,
},
}
// Add SSH keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.OpenSSHCA:
ca.Spec.ActiveKeys.SSH = []*types.SSHKeyPair{{
PrivateKey: keyPEM,
PublicKey: key.MarshalSSHPublicKey(),
}}
}
// Add TLS keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.DatabaseCA, types.DatabaseClientCA, types.SAMLIDPCA, types.SPIFFECA, types.AWSRACA:
cert, err := tlsca.GenerateSelfSignedCAWithConfig(tlsca.GenerateCAConfig{
Signer: key.Signer,
Entity: pkix.Name{
CommonName: config.ClusterName,
Organization: []string{config.SubjectOrganization},
},
TTL: defaults.CATTL,
Clock: config.Clock,
})
if err != nil {
return nil, trace.Wrap(err)
}
ca.Spec.ActiveKeys.TLS = []*types.TLSKeyPair{{
Key: keyPEM,
Cert: cert,
}}
}
// Add JWT keys if necessary.
switch config.Type {
case types.JWTSigner, types.OIDCIdPCA, types.SPIFFECA, types.OktaCA, types.BoundKeypairCA:
pubKeyPEM, err := keys.MarshalPublicKey(key.Public())
if err != nil {
return nil, trace.Wrap(err)
}
ca.Spec.ActiveKeys.JWT = []*types.JWTKeyPair{{
PrivateKey: keyPEM,
PublicKey: pubKeyPEM,
}}
}
// Sanity check that the CA has at least one active key.
aks := ca.Spec.ActiveKeys
if len(aks.SSH) == 0 && len(aks.TLS) == 0 && len(aks.JWT) == 0 {
return nil, trace.BadParameter("no keys generated for CA type %q", config.Type)
}
return ca, nil
}
+7 -139
View File
@@ -22,7 +22,6 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"net"
"strings"
"testing"
@@ -41,12 +40,12 @@ import (
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/constants"
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/accesspoint"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/recordingmetadata"
"github.com/gravitational/teleport/lib/auth/state"
@@ -60,7 +59,6 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/events/eventstest"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
@@ -415,11 +413,15 @@ func NewAuthServer(cfg AuthServerConfig) (*AuthServer, error) {
// Setup certificate and signing authorities.
for _, caType := range types.CertAuthTypes {
if err = srv.AuthServer.UpsertCertAuthority(ctx, NewTestCAWithConfig(TestCAConfig{
ca, err := authcatest.NewCAWithConfig(authcatest.CAConfig{
Type: caType,
ClusterName: srv.ClusterName,
Clock: cfg.Clock,
})); err != nil {
})
if err != nil {
return nil, trace.Wrap(err)
}
if err = srv.AuthServer.UpsertCertAuthority(ctx, ca); err != nil {
return nil, trace.Wrap(err)
}
}
@@ -1603,137 +1605,3 @@ func FlushCache(t *testing.T, clt Flusher) {
}
}
}
// TestCAConfig defines the configuration for generating
// a test certificate authority
type TestCAConfig struct {
Type types.CertAuthType
PrivateKeys [][]byte
Clock clockwork.Clock
ClusterName string
// the below string fields default to ClusterName if left empty
ResourceName string
SubjectOrganization string
}
// NewTestCA returns new test authority with a test key as a public and
// signing key
func NewTestCA(caType types.CertAuthType, clusterName string, privateKeys ...[]byte) *types.CertAuthorityV2 {
return NewTestCAWithConfig(TestCAConfig{
Type: caType,
ClusterName: clusterName,
PrivateKeys: privateKeys,
Clock: clockwork.NewRealClock(),
})
}
// NewTestCAWithConfig generates a new certificate authority with the specified
// configuration
// Keep this function in-sync with lib/auth/auth.go:newKeySet().
// TODO(jakule): reuse keystore.KeyStore interface to match newKeySet().
func NewTestCAWithConfig(config TestCAConfig) *types.CertAuthorityV2 {
var keyPEM []byte
var key *keys.PrivateKey
if config.ResourceName == "" {
config.ResourceName = config.ClusterName
}
if config.SubjectOrganization == "" {
config.SubjectOrganization = config.ClusterName
}
switch config.Type {
case types.DatabaseCA, types.SAMLIDPCA, types.OIDCIdPCA:
// These CAs only support RSA.
keyPEM = fixtures.PEMBytes["rsa"]
case types.DatabaseClientCA:
// The db client CA also only supports RSA, but some tests rely on it
// being different than the DB CA.
keyPEM = fixtures.PEMBytes["rsa-db-client"]
}
if len(config.PrivateKeys) > 0 {
// Allow test to override the private key.
keyPEM = config.PrivateKeys[0]
}
if keyPEM != nil {
var err error
key, err = keys.ParsePrivateKey(keyPEM)
if err != nil {
panic(err)
}
} else {
// If config.PrivateKeys was not set and this CA does not exclusively
// support RSA, generate an ECDSA key. Signatures are ~10x faster than
// RSA and generating a new key is actually faster than parsing a PEM
// fixture.
signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
if err != nil {
panic(err)
}
key, err = keys.NewPrivateKey(signer)
if err != nil {
panic(err)
}
keyPEM = key.PrivateKeyPEM()
}
ca := &types.CertAuthorityV2{
Kind: types.KindCertAuthority,
SubKind: string(config.Type),
Version: types.V2,
Metadata: types.Metadata{
Name: config.ResourceName,
Namespace: apidefaults.Namespace,
},
Spec: types.CertAuthoritySpecV2{
Type: config.Type,
ClusterName: config.ClusterName,
},
}
// Add SSH keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.OpenSSHCA:
ca.Spec.ActiveKeys.SSH = []*types.SSHKeyPair{{
PrivateKey: keyPEM,
PublicKey: key.MarshalSSHPublicKey(),
}}
}
// Add TLS keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.DatabaseCA, types.DatabaseClientCA, types.SAMLIDPCA, types.SPIFFECA, types.AWSRACA:
cert, err := tlsca.GenerateSelfSignedCAWithConfig(tlsca.GenerateCAConfig{
Signer: key.Signer,
Entity: pkix.Name{
CommonName: config.ClusterName,
Organization: []string{config.SubjectOrganization},
},
TTL: defaults.CATTL,
Clock: config.Clock,
})
if err != nil {
panic(err)
}
ca.Spec.ActiveKeys.TLS = []*types.TLSKeyPair{{
Key: keyPEM,
Cert: cert,
}}
}
// Add JWT keys if necessary.
switch config.Type {
case types.JWTSigner, types.OIDCIdPCA, types.SPIFFECA, types.OktaCA, types.BoundKeypairCA:
pubKeyPEM, err := keys.MarshalPublicKey(key.Public())
if err != nil {
panic(err)
}
ca.Spec.ActiveKeys.JWT = []*types.JWTKeyPair{{
PrivateKey: keyPEM,
PublicKey: pubKeyPEM,
}}
}
return ca
}
+6 -2
View File
@@ -61,6 +61,7 @@ import (
"github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/auth/authtest"
"github.com/gravitational/teleport/lib/auth/state"
"github.com/gravitational/teleport/lib/auth/storage"
@@ -228,11 +229,14 @@ func TestSignatureAlgorithmSuite(t *testing.T) {
}
// Pre-generate all CAs to keep tests fast esp. with SoftHSM.
for _, caType := range types.CertAuthTypes {
cfg.BootstrapResources = append(cfg.BootstrapResources, authtest.NewTestCAWithConfig(authtest.TestCAConfig{
ca, err := authcatest.NewCAWithConfig(authcatest.CAConfig{
Type: caType,
ClusterName: cfg.ClusterName.GetClusterName(),
Clock: cfg.Clock,
}))
})
require.NoError(t, err)
cfg.BootstrapResources = append(cfg.BootstrapResources, ca)
}
return cfg
}
+18 -14
View File
@@ -42,6 +42,7 @@ import (
apievents "github.com/gravitational/teleport/api/types/events"
wanpb "github.com/gravitational/teleport/api/types/webauthn"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/authtest"
authority "github.com/gravitational/teleport/lib/auth/testauthority"
@@ -906,14 +907,20 @@ func (s *passwordSuite) prepareForPasswordChange(user string, pass []byte, secon
OldPassword: pass,
}
err := s.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.UserCA, "me.localhost"))
userCA, err := authcatest.NewCA(types.UserCA, "me.localhost")
if err != nil {
return req, err
return nil, trace.Wrap(err)
}
if err := s.a.UpsertCertAuthority(ctx, userCA); err != nil {
return nil, err
}
err = s.a.UpsertCertAuthority(ctx, authtest.NewTestCA(types.HostCA, "me.localhost"))
hostCA, err := authcatest.NewCA(types.HostCA, "me.localhost")
if err != nil {
return req, err
return nil, err
}
if err := s.a.UpsertCertAuthority(ctx, hostCA); err != nil {
return nil, err
}
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
@@ -921,21 +928,18 @@ func (s *passwordSuite) prepareForPasswordChange(user string, pass []byte, secon
SecondFactor: secondFactorType,
})
if err != nil {
return req, err
return nil, err
}
_, err = s.a.UpsertAuthPreference(ctx, ap)
if err != nil {
return req, err
if _, err := s.a.UpsertAuthPreference(ctx, ap); err != nil {
return nil, err
}
_, _, err = authtest.CreateUserAndRole(s.a, user, []string{user}, nil)
if err != nil {
return req, err
if _, _, err := authtest.CreateUserAndRole(s.a, user, []string{user}, nil); err != nil {
return nil, err
}
err = s.a.UpsertPassword(user, pass)
if err != nil {
return req, err
if err := s.a.UpsertPassword(user, pass); err != nil {
return nil, err
}
return req, nil
+13 -6
View File
@@ -63,6 +63,7 @@ import (
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/sshutils"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/authtest"
"github.com/gravitational/teleport/lib/auth/state"
@@ -4380,7 +4381,8 @@ func TestEvents(t *testing.T) {
LoadSecrets: true,
},
crud: func(context.Context) types.Resource {
ca := authtest.NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, testSrv.Auth().UpsertCertAuthority(ctx, ca))
out, err := testSrv.Auth().GetCertAuthority(ctx, *ca.ID(), true)
@@ -4401,7 +4403,8 @@ func TestEvents(t *testing.T) {
LoadSecrets: false,
},
crud: func(context.Context) types.Resource {
ca := authtest.NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, testSrv.Auth().UpsertCertAuthority(ctx, ca))
out, err := testSrv.Auth().GetCertAuthority(ctx, *ca.ID(), false)
@@ -5748,10 +5751,14 @@ func TestVerifyPeerCert(t *testing.T) {
}
)
localHostCA := authtest.NewTestCA(types.HostCA, localClusterName)
remoteHostCA := authtest.NewTestCA(types.HostCA, remoteClusterName)
localUserCA := authtest.NewTestCA(types.UserCA, localClusterName)
remoteUserCA := authtest.NewTestCA(types.UserCA, remoteClusterName)
localHostCA, err := authcatest.NewCA(types.HostCA, localClusterName)
require.NoError(t, err)
remoteHostCA, err := authcatest.NewCA(types.HostCA, remoteClusterName)
require.NoError(t, err)
localUserCA, err := authcatest.NewCA(types.UserCA, localClusterName)
require.NoError(t, err)
remoteUserCA, err := authcatest.NewCA(types.UserCA, remoteClusterName)
require.NoError(t, err)
caPool := buildPoolInfo(
t,
+38 -24
View File
@@ -34,6 +34,7 @@ import (
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/authtest"
authority "github.com/gravitational/teleport/lib/auth/testauthority"
@@ -247,34 +248,38 @@ func TestValidateTrustedCluster(t *testing.T) {
})
t.Run("more than one CA", func(t *testing.T) {
ca1, err := authcatest.NewCA(types.HostCA, "rc1")
require.NoError(t, err)
ca2, err := authcatest.NewCA(types.HostCA, "rc2")
require.NoError(t, err)
_, err = a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{
authtest.NewTestCA(types.HostCA, "rc1"),
authtest.NewTestCA(types.HostCA, "rc2"),
},
CAs: []types.CertAuthority{ca1, ca2},
})
require.Error(t, err)
require.Contains(t, err.Error(), "expected exactly one")
})
t.Run("wrong CA type", func(t *testing.T) {
ca, err := authcatest.NewCA(types.UserCA, "rc3")
require.NoError(t, err)
_, err = a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{
authtest.NewTestCA(types.UserCA, "rc3"),
},
CAs: []types.CertAuthority{ca},
})
require.Error(t, err)
require.Contains(t, err.Error(), "expected host certificate authority")
})
t.Run("wrong CA name", func(t *testing.T) {
ca, err := authcatest.NewCA(types.HostCA, localClusterName)
require.NoError(t, err)
_, err = a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{
authtest.NewTestCA(types.HostCA, localClusterName),
},
CAs: []types.CertAuthority{ca},
})
require.Error(t, err)
require.Contains(t, err.Error(), "same name as this cluster")
@@ -289,18 +294,21 @@ func TestValidateTrustedCluster(t *testing.T) {
_, err = a.Services.UpsertTrustedCluster(ctx, trustedCluster)
require.NoError(t, err)
ca, err := authcatest.NewCA(types.HostCA, trustedCluster.GetName())
require.NoError(t, err)
_, err = a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{
authtest.NewTestCA(types.HostCA, trustedCluster.GetName()),
},
CAs: []types.CertAuthority{ca},
})
require.Error(t, err)
require.Contains(t, err.Error(), "same name as trusted cluster")
})
t.Run("all CAs are returned when v10+", func(t *testing.T) {
leafClusterCA := types.CertAuthority(authtest.NewTestCA(types.HostCA, "leafcluster-1"))
ca, err := authcatest.NewCA(types.HostCA, "leafcluster-1")
require.NoError(t, err)
leafClusterCA := types.CertAuthority(ca)
resp, err := a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{leafClusterCA},
@@ -375,7 +383,10 @@ func TestValidateTrustedCluster(t *testing.T) {
})
t.Run("Host User and Database CA are returned by default", func(t *testing.T) {
leafClusterCA := types.CertAuthority(authtest.NewTestCA(types.HostCA, "leafcluster-2"))
ca, err := authcatest.NewCA(types.HostCA, "leafcluster-2")
require.NoError(t, err)
leafClusterCA := types.CertAuthority(ca)
resp, err := a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{leafClusterCA},
@@ -406,15 +417,16 @@ func TestValidateTrustedCluster(t *testing.T) {
})
t.Run("CA cluster name does not match subject organization", func(t *testing.T) {
ca, err := authcatest.NewCAWithConfig(authcatest.CAConfig{
Type: types.HostCA,
ClusterName: "remoteCluster",
SubjectOrganization: "commonName",
})
require.NoError(t, err)
_, err = a.ValidateTrustedCluster(ctx, &authclient.ValidateTrustedClusterRequest{
Token: validToken,
CAs: []types.CertAuthority{
authtest.NewTestCAWithConfig(authtest.TestCAConfig{
Type: types.HostCA,
ClusterName: "remoteCluster",
SubjectOrganization: "commonName",
}),
},
CAs: []types.CertAuthority{ca},
})
require.Error(t, err)
require.Contains(t, err.Error(), "the subject organization of a CA certificate does not match the cluster name of the CA")
@@ -500,7 +512,8 @@ func TestUpsertTrustedCluster(t *testing.T) {
trustedCluster, err := types.NewTrustedCluster("trustedcluster", trustedClusterSpec)
require.NoError(t, err)
ca := authtest.NewTestCA(types.UserCA, "trustedcluster")
ca, err := authcatest.NewCA(types.UserCA, "trustedcluster")
require.NoError(t, err)
auth.ConfigureCAsForTrustedCluster(trustedCluster, []types.CertAuthority{ca})
@@ -639,7 +652,8 @@ func TestUpdateTrustedCluster(t *testing.T) {
trustedCluster, err := types.NewTrustedCluster(testClusterName, trustedClusterSpec)
require.NoError(t, err)
ca := authtest.NewTestCA(types.UserCA, testClusterName)
ca, err := authcatest.NewCA(types.UserCA, testClusterName)
require.NoError(t, err)
auth.ConfigureCAsForTrustedCluster(trustedCluster, []types.CertAuthority{ca})
+15 -146
View File
@@ -20,7 +20,6 @@ package cache
import (
"context"
"crypto/x509/pkix"
"fmt"
"iter"
"log/slog"
@@ -76,13 +75,11 @@ import (
"github.com/gravitational/teleport/api/types/userprovisioning"
"github.com/gravitational/teleport/api/types/usertasks"
"github.com/gravitational/teleport/api/utils/clientutils"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/memory"
"github.com/gravitational/teleport/lib/cryptosuites"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/itertools/stream"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/modules/modulestest"
@@ -90,7 +87,6 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local"
"github.com/gravitational/teleport/lib/srv/db/common/databaseobject"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/log/logtest"
)
@@ -648,7 +644,8 @@ func TestWatchers(t *testing.T) {
t.Fatalf("Timeout waiting for event.")
}
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, ca))
select {
@@ -707,7 +704,8 @@ func TestWatchers(t *testing.T) {
// this ca will not be matched by our filter, so the same reasoning applies
// as we upsert it and delete it
filteredCa := NewTestCA(types.HostCA, "example.net")
filteredCa, err := authcatest.NewCA(types.HostCA, "example.net")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, filteredCa))
require.NoError(t, p.trustS.DeleteCertAuthority(ctx, filteredCa.GetID()))
@@ -802,7 +800,8 @@ func TestCompletenessInit(t *testing.T) {
// put lots of CAs in the backend
for i := range caCount {
ca := NewTestCA(types.UserCA, fmt.Sprintf("%d.example.com", i))
ca, err := authcatest.NewCA(types.UserCA, fmt.Sprintf("%d.example.com", i))
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, ca))
}
@@ -895,7 +894,8 @@ func TestCompletenessReset(t *testing.T) {
// put lots of CAs in the backend
for i := range caCount {
ca := NewTestCA(types.UserCA, fmt.Sprintf("%d.example.com", i))
ca, err := authcatest.NewCA(types.UserCA, fmt.Sprintf("%d.example.com", i))
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, ca))
}
@@ -1217,7 +1217,8 @@ func initStrategy(t *testing.T) {
_, err = p.cache.GetCertAuthorities(ctx, types.UserCA, false)
require.True(t, trace.IsConnectionProblem(err))
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
// NOTE 1: this could produce event processed
// below, based on whether watcher restarts to get the event
// or not, which is normal, but has to be accounted below
@@ -1279,7 +1280,8 @@ func TestRecovery(t *testing.T) {
p := newPackForAuth(t)
t.Cleanup(p.Close)
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, ca))
select {
@@ -1298,7 +1300,8 @@ func TestRecovery(t *testing.T) {
waitForRestart(t, p.eventsC)
// add modification and expect the resource to recover
ca2 := NewTestCA(types.UserCA, "example2.com")
ca2, err := authcatest.NewCA(types.UserCA, "example2.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, ca2))
// wait for watcher to receive an event
@@ -2805,140 +2808,6 @@ func listResource(ctx context.Context, lister resourcesLister, kind string, page
return resp.Resources, resp.NextKey, nil
}
// NewTestCA returns new test authority with a test key as a public and
// signing key
func NewTestCA(caType types.CertAuthType, clusterName string, privateKeys ...[]byte) *types.CertAuthorityV2 {
return NewTestCAWithConfig(TestCAConfig{
Type: caType,
ClusterName: clusterName,
PrivateKeys: privateKeys,
Clock: clockwork.NewRealClock(),
})
}
// TestCAConfig defines the configuration for generating
// a test certificate authority
type TestCAConfig struct {
Type types.CertAuthType
PrivateKeys [][]byte
Clock clockwork.Clock
ClusterName string
// the below string fields default to ClusterName if left empty
ResourceName string
SubjectOrganization string
}
// NewTestCAWithConfig generates a new certificate authority with the specified
// configuration
// Keep this function in-sync with lib/auth/auth.go:newKeySet().
// TODO(jakule): reuse keystore.KeyStore interface to match newKeySet().
func NewTestCAWithConfig(config TestCAConfig) *types.CertAuthorityV2 {
var keyPEM []byte
var key *keys.PrivateKey
if config.ResourceName == "" {
config.ResourceName = config.ClusterName
}
if config.SubjectOrganization == "" {
config.SubjectOrganization = config.ClusterName
}
switch config.Type {
case types.DatabaseCA, types.SAMLIDPCA, types.OIDCIdPCA:
// These CAs only support RSA.
keyPEM = fixtures.PEMBytes["rsa"]
case types.DatabaseClientCA:
// The db client CA also only supports RSA, but some tests rely on it
// being different than the DB CA.
keyPEM = fixtures.PEMBytes["rsa-db-client"]
}
if len(config.PrivateKeys) > 0 {
// Allow test to override the private key.
keyPEM = config.PrivateKeys[0]
}
if keyPEM != nil {
var err error
key, err = keys.ParsePrivateKey(keyPEM)
if err != nil {
panic(err)
}
} else {
// If config.PrivateKeys was not set and this CA does not exclusively
// support RSA, generate an ECDSA key. Signatures are ~10x faster than
// RSA and generating a new key is actually faster than parsing a PEM
// fixture.
signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
if err != nil {
panic(err)
}
key, err = keys.NewPrivateKey(signer)
if err != nil {
panic(err)
}
keyPEM = key.PrivateKeyPEM()
}
ca := &types.CertAuthorityV2{
Kind: types.KindCertAuthority,
SubKind: string(config.Type),
Version: types.V2,
Metadata: types.Metadata{
Name: config.ResourceName,
Namespace: apidefaults.Namespace,
},
Spec: types.CertAuthoritySpecV2{
Type: config.Type,
ClusterName: config.ClusterName,
},
}
// Add SSH keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.OpenSSHCA:
ca.Spec.ActiveKeys.SSH = []*types.SSHKeyPair{{
PrivateKey: keyPEM,
PublicKey: key.MarshalSSHPublicKey(),
}}
}
// Add TLS keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.DatabaseCA, types.DatabaseClientCA, types.SAMLIDPCA, types.SPIFFECA, types.AWSRACA:
cert, err := tlsca.GenerateSelfSignedCAWithConfig(tlsca.GenerateCAConfig{
Signer: key.Signer,
Entity: pkix.Name{
CommonName: config.ClusterName,
Organization: []string{config.SubjectOrganization},
},
TTL: defaults.CATTL,
Clock: config.Clock,
})
if err != nil {
panic(err)
}
ca.Spec.ActiveKeys.TLS = []*types.TLSKeyPair{{
Key: keyPEM,
Cert: cert,
}}
}
// Add JWT keys if necessary.
switch config.Type {
case types.JWTSigner, types.OIDCIdPCA, types.SPIFFECA, types.OktaCA, types.BoundKeypairCA:
pubKeyPEM, err := keys.MarshalPublicKey(key.Public())
if err != nil {
panic(err)
}
ca.Spec.ActiveKeys.JWT = []*types.JWTKeyPair{{
PrivateKey: keyPEM,
PublicKey: pubKeyPEM,
}}
}
return ca
}
// NewServer creates a new server resource
func NewServer(kind, name, addr, namespace string) *types.ServerV2 {
return &types.ServerV2{
+15 -7
View File
@@ -27,6 +27,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/services"
)
@@ -38,11 +39,14 @@ func TestCA(t *testing.T) {
t.Cleanup(p.Close)
ctx := context.Background()
userCA := NewTestCA(types.UserCA, "example.com")
userCA, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, userCA))
dbCA := NewTestCA(types.DatabaseCA, "example.com")
dbCA, err := authcatest.NewCA(types.DatabaseCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, dbCA))
dbClientCA := NewTestCA(types.DatabaseClientCA, "example.com")
dbClientCA, err := authcatest.NewCA(types.DatabaseClientCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, dbClientCA))
const totalCAs = 3
@@ -129,7 +133,8 @@ func TestNodeCAFiltering(t *testing.T) {
require.Equal(t, types.OpInit, fetchEvent().Type)
// upsert and delete a local host CA, we expect to see a Put and a Delete event
localCA := NewTestCA(types.HostCA, "example.com")
localCA, err := authcatest.NewCA(types.HostCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, localCA))
require.NoError(t, p.trustS.DeleteCertAuthority(ctx, localCA.GetID()))
@@ -144,7 +149,8 @@ func TestNodeCAFiltering(t *testing.T) {
require.Equal(t, "example.com", ev.Resource.GetName())
// upsert and delete a nonlocal host CA, we expect to only see the Delete event
nonlocalCA := NewTestCA(types.HostCA, "example.net")
nonlocalCA, err := authcatest.NewCA(types.HostCA, "example.net")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, nonlocalCA))
require.NoError(t, p.trustS.DeleteCertAuthority(ctx, nonlocalCA.GetID()))
@@ -154,7 +160,8 @@ func TestNodeCAFiltering(t *testing.T) {
require.Equal(t, "example.net", ev.Resource.GetName())
// whereas we expect to see the Put and Delete for a trusted *user* CA
trustedUserCA := NewTestCA(types.UserCA, "example.net")
trustedUserCA, err := authcatest.NewCA(types.UserCA, "example.net")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, trustedUserCA))
require.NoError(t, p.trustS.DeleteCertAuthority(ctx, trustedUserCA.GetID()))
@@ -217,7 +224,8 @@ func TestCAWatcherFilters(t *testing.T) {
}
// generate an OpPut event.
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertCertAuthority(ctx, ca))
const fetchTimeout = time.Second
+13 -6
View File
@@ -28,6 +28,7 @@ import (
provisioningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/provisioning/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/memory"
"github.com/gravitational/teleport/lib/services"
@@ -83,9 +84,12 @@ func TestWatchers(t *testing.T) {
kind: types.KindCertAuthority,
causeEvents: func(subtestCtx context.Context, subtestT *testing.T, backend backend.Backend) {
// GIVEN an empty backend, WHEN I create 3 new CAs
userCA := NewTestCA(types.UserCA, "example.com")
hostCA := NewTestCA(types.HostCA, "example.com")
hostCARemote := NewTestCA(types.HostCA, "remote.com")
userCA, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
hostCA, err := authcatest.NewCA(types.HostCA, "example.com")
require.NoError(t, err)
hostCARemote, err := authcatest.NewCA(types.HostCA, "remote.com")
require.NoError(t, err)
require.NoError(subtestT, CreateResources(subtestCtx, backend, userCA, hostCA, hostCARemote))
},
validateEvents: func(subtestCtx context.Context, subtestT *testing.T, watcher types.Watcher) {
@@ -117,9 +121,12 @@ func TestWatchers(t *testing.T) {
filter: types.CertAuthorityFilter{types.HostCA: "example.com"}.IntoMap(),
causeEvents: func(subtestCtx context.Context, subtestT *testing.T, backend backend.Backend) {
// GIVEN an empty backend, WHEN I create some new CAs
userCA := NewTestCA(types.UserCA, "example.com")
hostCA := NewTestCA(types.HostCA, "example.com")
hostCARemote := NewTestCA(types.HostCA, "remote.com")
userCA, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
hostCA, err := authcatest.NewCA(types.HostCA, "example.com")
require.NoError(t, err)
hostCARemote, err := authcatest.NewCA(types.HostCA, "remote.com")
require.NoError(t, err)
require.NoError(subtestT, CreateResources(subtestCtx, backend, userCA, hostCA, hostCARemote))
},
validateEvents: func(subtestCtx context.Context, subtestT *testing.T, watcher types.Watcher) {
+7 -6
View File
@@ -33,6 +33,7 @@ import (
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/services"
)
@@ -144,17 +145,17 @@ func TestCertAuthorityResource(t *testing.T) {
ctx := context.Background()
tt := setupServicesContext(ctx, t)
userCA := NewTestCA(types.UserCA, "example.com")
hostCA := NewTestCA(types.HostCA, "example.com")
userCA, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
hostCA, err := authcatest.NewCA(types.HostCA, "example.com")
require.NoError(t, err)
// Check basic dynamic item creation
err := CreateResources(ctx, tt.bk, userCA, hostCA)
require.NoError(t, err)
require.NoError(t, CreateResources(ctx, tt.bk, userCA, hostCA))
// Check that dynamically created item is compatible with service
s := NewCAService(tt.bk)
err = s.CompareAndSwapCertAuthority(userCA, userCA)
require.NoError(t, err)
require.NoError(t, s.CompareAndSwapCertAuthority(userCA, userCA))
}
func TestTrustedClusterResource(t *testing.T) {
+14 -144
View File
@@ -18,7 +18,6 @@ package local
import (
"context"
"crypto/x509/pkix"
"fmt"
"log/slog"
"sort"
@@ -45,14 +44,12 @@ import (
"github.com/gravitational/teleport/api/types/clusterconfig"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/clientutils"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/auth/authcatest"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/cryptosuites"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/itertools/stream"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
)
// ServicesTestSuite is an acceptance test suite
@@ -224,7 +221,8 @@ func (s *ServicesTestSuite) LoginAttempts(t *testing.T) {
func (s *ServicesTestSuite) CertAuthCRUD(t *testing.T) {
ctx := context.Background()
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, s.TrustS.UpsertCertAuthority(ctx, ca))
out, err := s.TrustS.GetCertAuthority(ctx, ca.GetID(), true)
@@ -250,7 +248,8 @@ func (s *ServicesTestSuite) CertAuthCRUD(t *testing.T) {
require.NoError(t, err)
// test compare and swap
ca = NewTestCA(types.UserCA, "example.com")
ca, err = authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, s.TrustS.CreateCertAuthority(ctx, ca))
clock := clockwork.NewFakeClock()
@@ -271,7 +270,8 @@ func (s *ServicesTestSuite) CertAuthCRUD(t *testing.T) {
require.Empty(t, cmp.Diff(&newCA, out, cmpopts.EquateApproxTime(time.Second), cmpopts.IgnoreFields(types.Metadata{}, "Revision")))
// test conditional update
ca = NewTestCA(types.UserCA, "update.example.com")
ca, err = authcatest.NewCA(types.UserCA, "update.example.com")
require.NoError(t, err)
rev, err := s.TrustInternalS.CreateCertAuthorities(ctx, ca)
require.NoError(t, err)
@@ -308,7 +308,9 @@ func (s *ServicesTestSuite) CertAuthCRUD(t *testing.T) {
cas = nil
for _, cn := range clusterNames {
cas = append(cas, NewTestCA(types.UserCA, cn))
ca, err := authcatest.NewCA(types.UserCA, cn)
require.NoError(t, err)
cas = append(cas, ca)
}
rev, err = s.TrustInternalS.CreateCertAuthorities(ctx, cas...)
@@ -1996,7 +1998,8 @@ func (s *ServicesTestSuite) Events(t *testing.T) {
LoadSecrets: true,
},
crud: func(context.Context) types.Resource {
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, s.TrustS.UpsertCertAuthority(ctx, ca))
out, err := s.TrustS.GetCertAuthority(ctx, *ca.ID(), true)
@@ -2017,7 +2020,8 @@ func (s *ServicesTestSuite) Events(t *testing.T) {
LoadSecrets: false,
},
crud: func(context.Context) types.Resource {
ca := NewTestCA(types.UserCA, "example.com")
ca, err := authcatest.NewCA(types.UserCA, "example.com")
require.NoError(t, err)
require.NoError(t, s.TrustS.UpsertCertAuthority(ctx, ca))
out, err := s.TrustS.GetCertAuthority(ctx, *ca.ID(), false)
@@ -2541,137 +2545,3 @@ waitLoop:
}
}
}
// NewTestCA returns new test authority with a test key as a public and
// signing key
func NewTestCA(caType types.CertAuthType, clusterName string, privateKeys ...[]byte) *types.CertAuthorityV2 {
return NewTestCAWithConfig(TestCAConfig{
Type: caType,
ClusterName: clusterName,
PrivateKeys: privateKeys,
Clock: clockwork.NewRealClock(),
})
}
// TestCAConfig defines the configuration for generating
// a test certificate authority
type TestCAConfig struct {
Type types.CertAuthType
PrivateKeys [][]byte
Clock clockwork.Clock
ClusterName string
// the below string fields default to ClusterName if left empty
ResourceName string
SubjectOrganization string
}
// NewTestCAWithConfig generates a new certificate authority with the specified
// configuration
// Keep this function in-sync with lib/auth/auth.go:newKeySet().
// TODO(jakule): reuse keystore.KeyStore interface to match newKeySet().
func NewTestCAWithConfig(config TestCAConfig) *types.CertAuthorityV2 {
var keyPEM []byte
var key *keys.PrivateKey
if config.ResourceName == "" {
config.ResourceName = config.ClusterName
}
if config.SubjectOrganization == "" {
config.SubjectOrganization = config.ClusterName
}
switch config.Type {
case types.DatabaseCA, types.SAMLIDPCA, types.OIDCIdPCA:
// These CAs only support RSA.
keyPEM = fixtures.PEMBytes["rsa"]
case types.DatabaseClientCA:
// The db client CA also only supports RSA, but some tests rely on it
// being different than the DB CA.
keyPEM = fixtures.PEMBytes["rsa-db-client"]
}
if len(config.PrivateKeys) > 0 {
// Allow test to override the private key.
keyPEM = config.PrivateKeys[0]
}
if keyPEM != nil {
var err error
key, err = keys.ParsePrivateKey(keyPEM)
if err != nil {
panic(err)
}
} else {
// If config.PrivateKeys was not set and this CA does not exclusively
// support RSA, generate an ECDSA key. Signatures are ~10x faster than
// RSA and generating a new key is actually faster than parsing a PEM
// fixture.
signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
if err != nil {
panic(err)
}
key, err = keys.NewPrivateKey(signer)
if err != nil {
panic(err)
}
keyPEM = key.PrivateKeyPEM()
}
ca := &types.CertAuthorityV2{
Kind: types.KindCertAuthority,
SubKind: string(config.Type),
Version: types.V2,
Metadata: types.Metadata{
Name: config.ResourceName,
Namespace: apidefaults.Namespace,
},
Spec: types.CertAuthoritySpecV2{
Type: config.Type,
ClusterName: config.ClusterName,
},
}
// Add SSH keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.OpenSSHCA:
ca.Spec.ActiveKeys.SSH = []*types.SSHKeyPair{{
PrivateKey: keyPEM,
PublicKey: key.MarshalSSHPublicKey(),
}}
}
// Add TLS keys if necessary.
switch config.Type {
case types.UserCA, types.HostCA, types.DatabaseCA, types.DatabaseClientCA, types.SAMLIDPCA, types.SPIFFECA, types.AWSRACA:
cert, err := tlsca.GenerateSelfSignedCAWithConfig(tlsca.GenerateCAConfig{
Signer: key.Signer,
Entity: pkix.Name{
CommonName: config.ClusterName,
Organization: []string{config.SubjectOrganization},
},
TTL: defaults.CATTL,
Clock: config.Clock,
})
if err != nil {
panic(err)
}
ca.Spec.ActiveKeys.TLS = []*types.TLSKeyPair{{
Key: keyPEM,
Cert: cert,
}}
}
// Add JWT keys if necessary.
switch config.Type {
case types.JWTSigner, types.OIDCIdPCA, types.SPIFFECA, types.OktaCA, types.BoundKeypairCA:
pubKeyPEM, err := keys.MarshalPublicKey(key.Public())
if err != nil {
panic(err)
}
ca.Spec.ActiveKeys.JWT = []*types.JWTKeyPair{{
PrivateKey: keyPEM,
PublicKey: pubKeyPEM,
}}
}
return ca
}