mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
Fail to join trusted cluster when leaf v9 and root v10 (#11877)
* Send Teleport version when adding remote cluster * Send Database CA only if remote cluster is v10+ * Fix existing test and new one for v9 trusted CA validation * Add DatabaseCAMinVersion constant and replace hardcoded versions.
This commit is contained in:
parent
ccd92cf4c2
commit
b186f3a666
|
@ -128,6 +128,9 @@ const (
|
|||
|
||||
// NoLoginPrefix is the prefix used for nologin certificate principals.
|
||||
NoLoginPrefix = "-teleport-nologin-"
|
||||
|
||||
// DatabaseCAMinVersion is the minimum Teleport version that supports Database Certificate Authority.
|
||||
DatabaseCAMinVersion = "10.0.0"
|
||||
)
|
||||
|
||||
// SystemConnectors lists the names of the system-reserved connectors.
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
"github.com/gravitational/teleport/lib"
|
||||
|
@ -249,8 +250,9 @@ func (a *Server) establishTrust(ctx context.Context, trustedCluster types.Truste
|
|||
|
||||
// create a request to validate a trusted cluster (token and local certificate authorities)
|
||||
validateRequest := ValidateTrustedClusterRequest{
|
||||
Token: trustedCluster.GetToken(),
|
||||
CAs: localCertAuthorities,
|
||||
Token: trustedCluster.GetToken(),
|
||||
CAs: localCertAuthorities,
|
||||
TeleportVersion: teleport.Version,
|
||||
}
|
||||
|
||||
// log the local certificate authorities that we are sending
|
||||
|
@ -520,18 +522,11 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *Va
|
|||
}
|
||||
|
||||
// export local cluster certificate authority and return it to the cluster
|
||||
validateResponse := ValidateTrustedClusterResponse{
|
||||
CAs: []types.CertAuthority{},
|
||||
}
|
||||
for _, caType := range []types.CertAuthType{types.HostCA, types.UserCA, types.DatabaseCA} {
|
||||
certAuthority, err := a.GetCertAuthority(
|
||||
ctx,
|
||||
types.CertAuthID{Type: caType, DomainName: domainName},
|
||||
false)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
validateResponse.CAs = append(validateResponse.CAs, certAuthority)
|
||||
validateResponse := ValidateTrustedClusterResponse{}
|
||||
|
||||
validateResponse.CAs, err = getLeafClusterCAs(ctx, a, domainName, validateRequest)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// log the local certificate authorities we are sending
|
||||
|
@ -540,6 +535,54 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *Va
|
|||
return &validateResponse, nil
|
||||
}
|
||||
|
||||
// getLeafClusterCAs returns a slice with Cert Authorities that should be returned in response to ValidateTrustedClusterRequest.
|
||||
func getLeafClusterCAs(ctx context.Context, srv *Server, domainName string, validateRequest *ValidateTrustedClusterRequest) ([]types.CertAuthority, error) {
|
||||
certTypes, err := getCATypesForLeaf(validateRequest)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
caCerts := make([]types.CertAuthority, 0, len(certTypes))
|
||||
|
||||
for _, caType := range certTypes {
|
||||
certAuthority, err := srv.GetCertAuthority(
|
||||
ctx,
|
||||
types.CertAuthID{Type: caType, DomainName: domainName},
|
||||
false)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
caCerts = append(caCerts, certAuthority)
|
||||
}
|
||||
|
||||
return caCerts, nil
|
||||
}
|
||||
|
||||
// getCATypesForLeaf returns the list of CA certificates that should be sync in response to ValidateTrustedClusterRequest.
|
||||
func getCATypesForLeaf(validateRequest *ValidateTrustedClusterRequest) ([]types.CertAuthType, error) {
|
||||
var err error
|
||||
|
||||
databaseCASupported := false
|
||||
if validateRequest.TeleportVersion != "" {
|
||||
// (*ValidateTrustedClusterRequest).TeleportVersion was added in Teleport 10.0. If the request comes from an older
|
||||
// cluster this field will be empty.
|
||||
databaseCASupported, err = utils.MinVerWithoutPreRelease(validateRequest.TeleportVersion, constants.DatabaseCAMinVersion)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "failed to parse Teleport version: %q", validateRequest.TeleportVersion)
|
||||
}
|
||||
}
|
||||
|
||||
certTypes := []types.CertAuthType{types.HostCA, types.UserCA}
|
||||
|
||||
if databaseCASupported {
|
||||
// Database CA was introduced in Teleport 10.0. Do not send it to older clusters
|
||||
// as they don't understand it.
|
||||
certTypes = append(certTypes, types.DatabaseCA)
|
||||
}
|
||||
|
||||
return certTypes, nil
|
||||
}
|
||||
|
||||
func (a *Server) validateTrustedClusterToken(ctx context.Context, tokenName string) (map[string]string, error) {
|
||||
provisionToken, err := a.ValidateToken(ctx, tokenName)
|
||||
if err != nil {
|
||||
|
@ -614,8 +657,9 @@ func (a *Server) sendValidateRequestToProxy(host string, validateRequest *Valida
|
|||
}
|
||||
|
||||
type ValidateTrustedClusterRequest struct {
|
||||
Token string `json:"token"`
|
||||
CAs []types.CertAuthority `json:"certificate_authorities"`
|
||||
Token string `json:"token"`
|
||||
CAs []types.CertAuthority `json:"certificate_authorities"`
|
||||
TeleportVersion string `json:"teleport_version"`
|
||||
}
|
||||
|
||||
func (v *ValidateTrustedClusterRequest) ToRaw() (*ValidateTrustedClusterRequestRaw, error) {
|
||||
|
@ -631,14 +675,16 @@ func (v *ValidateTrustedClusterRequest) ToRaw() (*ValidateTrustedClusterRequestR
|
|||
}
|
||||
|
||||
return &ValidateTrustedClusterRequestRaw{
|
||||
Token: v.Token,
|
||||
CAs: cas,
|
||||
Token: v.Token,
|
||||
CAs: cas,
|
||||
TeleportVersion: v.TeleportVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ValidateTrustedClusterRequestRaw struct {
|
||||
Token string `json:"token"`
|
||||
CAs [][]byte `json:"certificate_authorities"`
|
||||
Token string `json:"token"`
|
||||
CAs [][]byte `json:"certificate_authorities"`
|
||||
TeleportVersion string `json:"teleport_version"`
|
||||
}
|
||||
|
||||
func (v *ValidateTrustedClusterRequestRaw) ToNative() (*ValidateTrustedClusterRequest, error) {
|
||||
|
@ -654,8 +700,9 @@ func (v *ValidateTrustedClusterRequestRaw) ToNative() (*ValidateTrustedClusterRe
|
|||
}
|
||||
|
||||
return &ValidateTrustedClusterRequest{
|
||||
Token: v.Token,
|
||||
CAs: cas,
|
||||
Token: v.Token,
|
||||
CAs: cas,
|
||||
TeleportVersion: v.TeleportVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -123,113 +123,145 @@ func TestValidateTrustedCluster(t *testing.T) {
|
|||
err = a.SetStaticTokens(tks)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: "invalidtoken",
|
||||
CAs: []types.CertAuthority{},
|
||||
t.Run("invalid cluster token", func(t *testing.T) {
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: "invalidtoken",
|
||||
CAs: []types.CertAuthority{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid cluster token")
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid cluster token")
|
||||
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{},
|
||||
t.Run("missing CA", func(t *testing.T) {
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected exactly one")
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected exactly one")
|
||||
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.HostCA, "rc1"),
|
||||
suite.NewTestCA(types.HostCA, "rc2"),
|
||||
},
|
||||
t.Run("more than one CA", func(t *testing.T) {
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.HostCA, "rc1"),
|
||||
suite.NewTestCA(types.HostCA, "rc2"),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected exactly one")
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected exactly one")
|
||||
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.UserCA, "rc3"),
|
||||
},
|
||||
t.Run("wrong CA type", func(t *testing.T) {
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.UserCA, "rc3"),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected host certificate authority")
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected host certificate authority")
|
||||
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.HostCA, localClusterName),
|
||||
},
|
||||
t.Run("wrong CA name", func(t *testing.T) {
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.HostCA, localClusterName),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "same name as this cluster")
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "same name as this cluster")
|
||||
|
||||
trustedCluster, err := types.NewTrustedCluster("trustedcluster",
|
||||
types.TrustedClusterSpecV2{Roles: []string{"nonempty"}})
|
||||
require.NoError(t, err)
|
||||
// use the UpsertTrustedCluster in Presence as we just want the resource in
|
||||
// the backend, we don't want to actually connect
|
||||
_, err = a.Presence.UpsertTrustedCluster(ctx, trustedCluster)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.HostCA, trustedCluster.GetName()),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "same name as trusted cluster")
|
||||
|
||||
leafClusterCA := types.CertAuthority(suite.NewTestCA(types.HostCA, "leafcluster"))
|
||||
resp, err := a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{leafClusterCA},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, resp.CAs, 3)
|
||||
require.ElementsMatch(t,
|
||||
[]types.CertAuthType{types.HostCA, types.UserCA, types.DatabaseCA},
|
||||
[]types.CertAuthType{resp.CAs[0].GetType(), resp.CAs[1].GetType(), resp.CAs[2].GetType()},
|
||||
)
|
||||
|
||||
for _, returnedCA := range resp.CAs {
|
||||
localCA, err := a.GetCertAuthority(ctx, types.CertAuthID{
|
||||
Type: returnedCA.GetType(),
|
||||
DomainName: localClusterName,
|
||||
}, false)
|
||||
t.Run("wrong remote CA name", func(t *testing.T) {
|
||||
trustedCluster, err := types.NewTrustedCluster("trustedcluster",
|
||||
types.TrustedClusterSpecV2{Roles: []string{"nonempty"}})
|
||||
require.NoError(t, err)
|
||||
// use the UpsertTrustedCluster in Presence as we just want the resource in
|
||||
// the backend, we don't want to actually connect
|
||||
_, err = a.Presence.UpsertTrustedCluster(ctx, trustedCluster)
|
||||
require.NoError(t, err)
|
||||
require.True(t, services.CertAuthoritiesEquivalent(localCA, returnedCA))
|
||||
}
|
||||
|
||||
rcs, err := a.GetRemoteClusters()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rcs, 1)
|
||||
require.Equal(t, leafClusterCA.GetName(), rcs[0].GetName())
|
||||
_, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{
|
||||
suite.NewTestCA(types.HostCA, trustedCluster.GetName()),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "same name as trusted cluster")
|
||||
|
||||
hostCAs, err := a.GetCertAuthorities(ctx, types.HostCA, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostCAs, 2)
|
||||
require.ElementsMatch(t,
|
||||
[]string{localClusterName, leafClusterCA.GetName()},
|
||||
[]string{hostCAs[0].GetName(), hostCAs[1].GetName()},
|
||||
)
|
||||
require.Empty(t, hostCAs[0].GetRoles())
|
||||
require.Empty(t, hostCAs[0].GetRoleMap())
|
||||
require.Empty(t, hostCAs[1].GetRoles())
|
||||
require.Empty(t, hostCAs[1].GetRoleMap())
|
||||
})
|
||||
|
||||
userCAs, err := a.GetCertAuthorities(ctx, types.UserCA, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, userCAs, 1)
|
||||
require.Equal(t, localClusterName, userCAs[0].GetName())
|
||||
t.Run("all CAs are returned when v10+", func(t *testing.T) {
|
||||
leafClusterCA := types.CertAuthority(suite.NewTestCA(types.HostCA, "leafcluster"))
|
||||
resp, err := a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{leafClusterCA},
|
||||
TeleportVersion: teleport.Version,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dbCAs, err := a.GetCertAuthorities(ctx, types.DatabaseCA, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dbCAs, 1)
|
||||
require.Equal(t, localClusterName, dbCAs[0].GetName())
|
||||
require.Len(t, resp.CAs, 3)
|
||||
require.ElementsMatch(t,
|
||||
[]types.CertAuthType{types.HostCA, types.UserCA, types.DatabaseCA},
|
||||
[]types.CertAuthType{resp.CAs[0].GetType(), resp.CAs[1].GetType(), resp.CAs[2].GetType()},
|
||||
)
|
||||
|
||||
for _, returnedCA := range resp.CAs {
|
||||
localCA, err := a.GetCertAuthority(ctx, types.CertAuthID{
|
||||
Type: returnedCA.GetType(),
|
||||
DomainName: localClusterName,
|
||||
}, false)
|
||||
require.NoError(t, err)
|
||||
require.True(t, services.CertAuthoritiesEquivalent(localCA, returnedCA))
|
||||
}
|
||||
|
||||
rcs, err := a.GetRemoteClusters()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rcs, 1)
|
||||
require.Equal(t, leafClusterCA.GetName(), rcs[0].GetName())
|
||||
|
||||
hostCAs, err := a.GetCertAuthorities(ctx, types.HostCA, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostCAs, 2)
|
||||
require.ElementsMatch(t,
|
||||
[]string{localClusterName, leafClusterCA.GetName()},
|
||||
[]string{hostCAs[0].GetName(), hostCAs[1].GetName()},
|
||||
)
|
||||
require.Empty(t, hostCAs[0].GetRoles())
|
||||
require.Empty(t, hostCAs[0].GetRoleMap())
|
||||
require.Empty(t, hostCAs[1].GetRoles())
|
||||
require.Empty(t, hostCAs[1].GetRoleMap())
|
||||
|
||||
userCAs, err := a.GetCertAuthorities(ctx, types.UserCA, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, userCAs, 1)
|
||||
require.Equal(t, localClusterName, userCAs[0].GetName())
|
||||
|
||||
dbCAs, err := a.GetCertAuthorities(ctx, types.DatabaseCA, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dbCAs, 1)
|
||||
require.Equal(t, localClusterName, dbCAs[0].GetName())
|
||||
})
|
||||
|
||||
t.Run("only Host and User CA are returned for v9", func(t *testing.T) {
|
||||
leafClusterCA := types.CertAuthority(suite.NewTestCA(types.HostCA, "leafcluster"))
|
||||
resp, err := a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{
|
||||
Token: validToken,
|
||||
CAs: []types.CertAuthority{leafClusterCA},
|
||||
TeleportVersion: "",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, resp.CAs, 2)
|
||||
require.ElementsMatch(t,
|
||||
[]types.CertAuthType{types.HostCA, types.UserCA},
|
||||
[]types.CertAuthType{resp.CAs[0].GetType(), resp.CAs[1].GetType()},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func newTestAuthServer(ctx context.Context, t *testing.T, name ...string) *Server {
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
|
||||
"github.com/gravitational/teleport"
|
||||
"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"
|
||||
"github.com/gravitational/teleport/api/types/events"
|
||||
|
@ -646,7 +647,7 @@ func (s *ProxyServer) getConfigForServer(ctx context.Context, identity tlsca.Ide
|
|||
|
||||
// DatabaseCA was introduced in Teleport 10. Older versions require database certificate signed
|
||||
// with UserCA where Teleport 10+ uses DatabaseCA.
|
||||
ver10orAbove, err := utils.MinVerWithoutPreRelease(server.GetTeleportVersion(), "10.0.0")
|
||||
ver10orAbove, err := utils.MinVerWithoutPreRelease(server.GetTeleportVersion(), constants.DatabaseCAMinVersion)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "failed to parse Teleport version: %q", server.GetTeleportVersion())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue