Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions lib/client/ca_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,16 +337,24 @@ func exportTLSAuthority(ctx context.Context, client authclient.ClientI, req expo
return nil, trace.Wrap(err)
}

activeKeys := certAuthority.GetActiveKeys().TLS
// TODO(codingllama): Export AdditionalTrustedKeys as well?
keyPairs := append(
certAuthority.GetActiveKeys().TLS,
certAuthority.GetAdditionalTrustedKeys().TLS...,
)

authorities := make([]*ExportedAuthority, len(activeKeys))
for i, activeKey := range activeKeys {
authorities := make([]*ExportedAuthority, 0, len(keyPairs))
for _, activeKey := range keyPairs {
bytesToExport := activeKey.Cert
if req.ExportPrivateKeys {
bytesToExport = activeKey.Key
}

// Skip empty keys (may happen with keys, unexpected with certs but it's
// fine to skip either way).
if len(bytesToExport) == 0 {
continue
}

if req.UnpackPEM {
block, _ := pem.Decode(bytesToExport)
if block == nil {
Expand All @@ -355,9 +363,9 @@ func exportTLSAuthority(ctx context.Context, client authclient.ClientI, req expo
bytesToExport = block.Bytes
}

authorities[i] = &ExportedAuthority{
authorities = append(authorities, &ExportedAuthority{
Data: bytesToExport,
}
})
}

return authorities, nil
Expand Down
132 changes: 125 additions & 7 deletions lib/client/ca_export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"time"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"

Expand All @@ -43,6 +44,7 @@ import (
"github.com/gravitational/teleport/lib/cryptosuites"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/tlsca"
)

type mockAuthClient struct {
Expand Down Expand Up @@ -83,18 +85,17 @@ func (m *mockIntegrationsClient) ExportIntegrationCertAuthorities(ctx context.Co
}, nil
}

func TestExportAuthorities(t *testing.T) {
func TestExportAllAuthorities(t *testing.T) {
t.Parallel()

ctx := context.Background()
const localClusterName = "localcluster"

testAuth, err := authtest.NewAuthServer(authtest.AuthServerConfig{
ClusterName: localClusterName,
Dir: t.TempDir(),
})
require.NoError(t, err, "failed to create authtest.NewAuthServer")
t.Cleanup(func() { require.NoError(t, testAuth.Close()) })
t.Cleanup(func() { assert.NoError(t, testAuth.Close()) })

validateTLSCertificateDERFunc := func(t *testing.T, s string) {
cert, err := x509.ParseCertificate([]byte(s))
Expand Down Expand Up @@ -295,6 +296,8 @@ func TestExportAuthorities(t *testing.T) {
exportFunc func(context.Context, authclient.ClientI, ExportAuthoritiesRequest) ([]*ExportedAuthority, error),
assertFunc func(t *testing.T, output string),
) {
ctx := t.Context()

authorities, err := exportFunc(ctx, mockedAuthClient, tt.req)
tt.errorCheck(t, err)
if err != nil {
Expand Down Expand Up @@ -323,9 +326,122 @@ func TestExportAuthorities(t *testing.T) {
}
}

func TestExportAllAuthorities_additionalKeys(t *testing.T) {
t.Parallel()

const clusterName = "zarq"
testAuth, err := authtest.NewAuthServer(authtest.AuthServerConfig{
ClusterName: clusterName,
Dir: t.TempDir(),
})
require.NoError(t, err)
t.Cleanup(func() { assert.NoError(t, testAuth.Close()) })

authServer := testAuth.AuthServer
ctx := t.Context()

const caType = types.UserCA
ca, err := authServer.GetCertAuthority(ctx, types.CertAuthID{
Type: caType,
DomainName: clusterName,
}, true /* loadKeys */)
require.NoError(t, err)

makeNewTLSKey := func(t *testing.T) *types.TLSKeyPair {
t.Helper()

const ttl = 1 * time.Hour // Arbitrary. Actual CA TTLs are much larger.
keyPEM, certPEM, err := tlsca.GenerateSelfSignedCA(
pkix.Name{
Organization: []string{clusterName},
CommonName: clusterName,
},
nil /* dnsNames */, ttl)
require.NoError(t, err)

return &types.TLSKeyPair{
Cert: certPEM,
Key: keyPEM,
KeyType: types.PrivateKeyType_RAW,
}
}

kp1 := makeNewTLSKey(t)

// Make sure multiple keys exist in both active and additionalTrusted sets.
aks := ca.GetActiveKeys()
aks.TLS = append(aks.TLS,
makeNewTLSKey(t),
&types.TLSKeyPair{Cert: kp1.Cert}, // Cert without Key.
// 3 entries total (existing + new + cert only)
)
require.NoError(t, ca.SetActiveKeys(aks))
tks := ca.GetAdditionalTrustedKeys()
tks.TLS = append(tks.TLS,
makeNewTLSKey(t),
makeNewTLSKey(t),
// 2 entries total (both new)
)
require.NoError(t, ca.SetAdditionalTrustedKeys(tks))

// Update CA with new keys.
_, err = authServer.UpdateCertAuthority(ctx, ca)
require.NoError(t, err)

var wantCerts, wantKeys []*ExportedAuthority
for _, keySet := range [][]*types.TLSKeyPair{aks.TLS, tks.TLS} {
for _, kp := range keySet {
wantCerts = append(wantCerts, &ExportedAuthority{Data: kp.Cert})
if len(kp.Key) > 0 {
wantKeys = append(wantKeys, &ExportedAuthority{Data: kp.Key})
}
}
}
// Sanity check.
require.Len(t, wantCerts, 5, "Unexpected number of wanted certs")
require.Len(t, wantKeys, 4, "Unexpected number of wanted keys")

authClient := &mockAuthClient{
server: authServer,
}

exportReq := ExportAuthoritiesRequest{
AuthType: "tls-user", // UserCA TLS certificates in PEM form.
}

tests := []struct {
name string
exportFunc func(context.Context, authclient.ClientI, ExportAuthoritiesRequest) ([]*ExportedAuthority, error)
want []*ExportedAuthority
}{
{
name: "certs",
exportFunc: ExportAllAuthorities,
want: wantCerts,
},
{
name: "secrets",
exportFunc: ExportAllAuthoritiesSecrets,
want: wantKeys,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()

ctx := t.Context()
got, err := test.exportFunc(ctx, authClient, exportReq)
require.NoError(t, err)
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("Export mismatch (-want +got)\n%s", diff)
}
})
}
}

// Tests a scenario similar to
// https://github.com/gravitational/teleport/issues/35444.
func TestExportAllAuthorities_mutipleActiveKeys(t *testing.T) {
func TestExportAllAuthorities_multipleActiveKeys(t *testing.T) {
t.Parallel()

softwareKey, err := cryptosuites.GeneratePrivateKeyWithAlgorithm(cryptosuites.ECDSAP256)
Expand Down Expand Up @@ -419,7 +535,6 @@ func TestExportAllAuthorities_mutipleActiveKeys(t *testing.T) {
clusterName: clusterName,
certAuthorities: []types.CertAuthority{userCA},
}
ctx := context.Background()

tests := []struct {
name string
Expand Down Expand Up @@ -464,6 +579,8 @@ func TestExportAllAuthorities_mutipleActiveKeys(t *testing.T) {
exportAllFunc func(context.Context, authclient.ClientI, ExportAuthoritiesRequest) ([]*ExportedAuthority, error),
want []*ExportedAuthority,
) {
ctx := t.Context()

got, err := exportAllFunc(ctx, authClient, *test.req)
require.NoError(t, err, "exportAllFunc errored")
if diff := cmp.Diff(want, got); diff != "" {
Expand Down Expand Up @@ -516,13 +633,12 @@ func (m *multiCAAuthClient) PerformMFACeremony(
func TestExportIntegrationAuthorities(t *testing.T) {
t.Parallel()

ctx := context.Background()
testAuth, err := authtest.NewAuthServer(authtest.AuthServerConfig{
ClusterName: "localcluster",
Dir: t.TempDir(),
})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, testAuth.Close()) })
t.Cleanup(func() { assert.NoError(t, testAuth.Close()) })

fingerprint, err := sshutils.AuthorizedKeyFingerprint([]byte(fixtures.SSHCAPublicKey))
require.NoError(t, err)
Expand Down Expand Up @@ -595,6 +711,8 @@ func TestExportIntegrationAuthorities(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()

authorities, err := ExportIntegrationAuthorities(ctx, mockedAuthClient, tc.req)
tc.checkError(t, err)
if tc.checkOutput != nil {
Expand Down
Loading