teleport/lib/client/ca_export_test.go
2022-10-28 20:20:28 +00:00

242 lines
7.1 KiB
Go

/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"context"
"crypto/x509"
"encoding/pem"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/services"
)
type mockAuthClient struct {
auth.ClientI
server *auth.Server
}
func (m *mockAuthClient) GetDomainName(ctx context.Context) (string, error) {
return m.server.GetDomainName()
}
func (m *mockAuthClient) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) {
return m.server.GetCertAuthorities(ctx, caType, loadKeys, opts...)
}
func (m *mockAuthClient) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) {
return m.server.GetCertAuthority(ctx, id, loadKeys, opts...)
}
func TestExportAuthorities(t *testing.T) {
ctx := context.Background()
const localClusterName = "localcluster"
testAuth, err := auth.NewTestAuthServer(auth.TestAuthServerConfig{
ClusterName: localClusterName,
Dir: t.TempDir(),
})
require.NoError(t, err, "failed to create auth.NewTestAuthServer")
validateTLSCertificateDERFunc := func(t *testing.T, s string) {
cert, err := x509.ParseCertificate([]byte(s))
require.NoError(t, err, "failed to x509.ParseCertificate")
require.NotNil(t, cert, "x509.ParseCertificate returned a nil certificate")
require.Equal(t, localClusterName, cert.Subject.CommonName, "unexpected certificate subject CN")
}
validateTLSCertificatePEMFunc := func(t *testing.T, s string) {
pemBlock, _ := pem.Decode([]byte(s))
require.NotNil(t, pemBlock, "pem.Decode failed")
validateTLSCertificateDERFunc(t, string(pemBlock.Bytes))
}
validatePrivateKeyPEMFunc := func(t *testing.T, s string) {
pemBlock, _ := pem.Decode([]byte(s))
require.NotNil(t, pemBlock, "pem.Decode failed")
require.Equal(t, "RSA PRIVATE KEY", pemBlock.Type, "unexpected private key type")
privKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
require.NoError(t, err, "x509.ParsePKCS1PrivateKey failed")
require.NotNil(t, privKey, "x509.ParsePKCS1PrivateKey returned a nil certificate")
}
validatePrivateKeyDERFunc := func(t *testing.T, s string) {
res := strings.Split(s, "\n\n")
require.Len(t, res, 2, "expected private key and certificate separated by one empty line")
privKey, err := x509.ParsePKCS1PrivateKey([]byte(res[0]))
require.NoError(t, err, "x509.ParsePKCS1PrivateKey failed")
require.NotNil(t, privKey, "x509.ParsePKCS1PrivateKey returned a nil certificate")
validateTLSCertificateDERFunc(t, res[1])
}
for _, exportSecrets := range []bool{false, true} {
for _, tt := range []struct {
name string
req ExportAuthoritiesRequest
errorCheck require.ErrorAssertionFunc
assertOutput func(t *testing.T, output string)
assertSecrets func(t *testing.T, output string)
}{
{
name: "ssh host and user ca",
req: ExportAuthoritiesRequest{
AuthType: "",
},
errorCheck: require.NoError,
assertOutput: func(t *testing.T, output string) {
require.Contains(t, output, "@cert-authority localcluster,*.localcluster ssh-rsa")
require.Contains(t, output, "cert-authority ssh-rsa")
},
assertSecrets: func(t *testing.T, output string) {},
},
{
name: "user",
req: ExportAuthoritiesRequest{
AuthType: "user",
},
errorCheck: require.NoError,
assertOutput: func(t *testing.T, output string) {
require.Contains(t, output, "cert-authority ssh-rsa")
},
assertSecrets: validatePrivateKeyPEMFunc,
},
{
name: "host",
req: ExportAuthoritiesRequest{
AuthType: "host",
},
errorCheck: require.NoError,
assertOutput: func(t *testing.T, output string) {
require.Contains(t, output, "@cert-authority localcluster,*.localcluster ssh-rsa")
},
assertSecrets: validatePrivateKeyPEMFunc,
},
{
name: "tls",
req: ExportAuthoritiesRequest{
AuthType: "tls",
},
errorCheck: require.NoError,
assertOutput: validateTLSCertificatePEMFunc,
assertSecrets: validatePrivateKeyPEMFunc,
},
{
name: "windows",
req: ExportAuthoritiesRequest{
AuthType: "windows",
},
errorCheck: require.NoError,
assertOutput: validateTLSCertificateDERFunc,
assertSecrets: validatePrivateKeyDERFunc,
},
{
name: "invalid",
req: ExportAuthoritiesRequest{
AuthType: "invalid",
},
errorCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.ErrorContains(tt, err, `"invalid" authority type is not supported`)
},
},
{
name: "fingerprint not found",
req: ExportAuthoritiesRequest{
AuthType: "user",
ExportAuthorityFingerprint: "not found fingerprint",
},
errorCheck: require.NoError,
assertOutput: func(t *testing.T, output string) {
require.Empty(t, output)
},
assertSecrets: func(t *testing.T, output string) {
require.Empty(t, output)
},
},
{
name: "fingerprint not found",
req: ExportAuthoritiesRequest{
AuthType: "user",
ExportAuthorityFingerprint: "fake fingerprint",
},
errorCheck: require.NoError,
assertOutput: func(t *testing.T, output string) {
require.Empty(t, output)
},
assertSecrets: func(t *testing.T, output string) {
require.Empty(t, output)
},
},
{
name: "using compat version",
req: ExportAuthoritiesRequest{
AuthType: "user",
UseCompatVersion: true,
},
errorCheck: require.NoError,
assertOutput: func(t *testing.T, output string) {
// compat version (using 1.0) returns cert-authority to be used in the server
// even when asking for ssh authorized hosts / known hosts
require.Contains(t, output, "@cert-authority localcluster,*.localcluster ssh-rsa")
},
assertSecrets: validatePrivateKeyPEMFunc,
},
} {
testName := tt.name
if exportSecrets {
testName = tt.name + "_exportSecrets"
}
t.Run(testName, func(t *testing.T) {
mockedClient := &mockAuthClient{
server: testAuth.AuthServer,
}
var (
err error
exported string
)
if exportSecrets {
exported, err = ExportAuthoritiesWithSecrets(ctx, mockedClient, tt.req)
tt.errorCheck(t, err)
} else {
exported, err = ExportAuthorities(ctx, mockedClient, tt.req)
tt.errorCheck(t, err)
}
if err != nil {
return
}
if exportSecrets {
tt.assertSecrets(t, exported)
} else {
tt.assertOutput(t, exported)
}
})
}
}
}