Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UpsertTrustedClusterV2 RPC #49789

Merged
merged 24 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fb760dc
Add UpsertTrustedClusterV2 rpc
bernardjkim Dec 4, 2024
bc9e0fb
Replace confusing UpsertValidationTrustedCluster name
bernardjkim Dec 5, 2024
415d72e
Use UpsertTrustedClusterV2 in tests
bernardjkim Dec 5, 2024
7049271
Address feedback
bernardjkim Dec 5, 2024
7ebe5ce
Merge branch 'master' into bernard/upsert-trusted-cluster-v2
bernardjkim Dec 5, 2024
1933ba6
Use webclient.Find
bernardjkim Dec 5, 2024
cb1f827
Fix test/lint
bernardjkim Dec 5, 2024
6243ff2
Allow label updates
bernardjkim Dec 6, 2024
4142256
Fix test
bernardjkim Dec 6, 2024
5db82b4
Merge branch 'master' into bernard/upsert-trusted-cluster-v2
bernardjkim Dec 6, 2024
330a663
Fix error handling
bernardjkim Dec 7, 2024
456a2fe
Implement CreateTrustedClusterV2 and UpdateTrustedClusterV2
bernardjkim Dec 10, 2024
d3118e2
Address feedback
bernardjkim Dec 10, 2024
aa82d22
Minor fixes
bernardjkim Dec 10, 2024
96eea47
Merge branch 'master' into bernard/upsert-trusted-cluster-v2
bernardjkim Dec 10, 2024
3036745
Move V2 RPCs to the trust service
bernardjkim Dec 11, 2024
57fc180
Merge branch 'master' into bernard/upsert-trusted-cluster-v2
bernardjkim Dec 12, 2024
4a4bb8d
Update comment
bernardjkim Dec 12, 2024
6470291
Drop V2 suffix
bernardjkim Dec 12, 2024
f8bc242
Require matching revision
bernardjkim Dec 12, 2024
fba6930
Fix upsert/update revision
bernardjkim Dec 20, 2024
519c124
Drop V2 from Create and Update APIs
bernardjkim Dec 20, 2024
3f46c89
Merge branch 'master' into bernard/upsert-trusted-cluster-v2
bernardjkim Dec 20, 2024
a290e38
Lint: Fix typo
bernardjkim Dec 20, 2024
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
27 changes: 20 additions & 7 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2277,16 +2277,29 @@ func (c *Client) GetTrustedClusters(ctx context.Context) ([]types.TrustedCluster
}

// UpsertTrustedCluster creates or updates a Trusted Cluster.
func (c *Client) UpsertTrustedCluster(ctx context.Context, trusedCluster types.TrustedCluster) (types.TrustedCluster, error) {
trustedCluster, ok := trusedCluster.(*types.TrustedClusterV2)
func (c *Client) UpsertTrustedCluster(ctx context.Context, trustedCluster types.TrustedCluster) (types.TrustedCluster, error) {
trustedClusterV2, ok := trustedCluster.(*types.TrustedClusterV2)
if !ok {
return nil, trace.BadParameter("invalid type %T", trusedCluster)
return nil, trace.BadParameter("invalid type %T", trustedCluster)
}
resp, err := c.grpc.UpsertTrustedCluster(ctx, trustedCluster)
if err != nil {
return nil, trace.Wrap(err)
if trustedCluster.Origin() != types.OriginKubernetes {
resp, err := c.grpc.UpsertTrustedCluster(ctx, trustedClusterV2)
return resp, trace.Wrap(err)
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
}
return resp, nil
// The Kubernetes Operator must use UpsertTrustedClusterV2 to ensure that
// the trusted_cluster resource name is valid before it is created.
resp, err := c.grpc.UpsertTrustedClusterV2(ctx, trustedClusterV2)
if trace.IsNotImplemented(err) {
// Try to print a nicer error message when newer clients connect to
// older auth servers that don't recognize the new gRPC.
authVersion := "unknown"
if pingResp, err := c.Ping(ctx); err == nil && pingResp.ServerVersion != "" {
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
authVersion = pingResp.ServerVersion
}
return resp, trace.Wrap(err, "client version (%s) is likely newer than your auth server version (%s), "+
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
"consider upgrading your auth server", api.Version, authVersion)
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
}
return resp, trace.Wrap(err)
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
}

// DeleteTrustedCluster deletes a Trusted Cluster by name.
Expand Down
1,934 changes: 989 additions & 945 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3218,7 +3218,13 @@ service AuthService {
// GetTrustedClusters gets all current Trusted Cluster resources.
rpc GetTrustedClusters(google.protobuf.Empty) returns (types.TrustedClusterV2List);
// UpsertTrustedCluster upserts a Trusted Cluster in a backend.
rpc UpsertTrustedCluster(types.TrustedClusterV2) returns (types.TrustedClusterV2);
//
// Deprecated: Use UpsertTrustedClusterV2 instead.
rpc UpsertTrustedCluster(types.TrustedClusterV2) returns (types.TrustedClusterV2) {
option deprecated = true;
}
// UpsertTrustedClusterV2 upserts a Trusted Cluster in a backend.
rpc UpsertTrustedClusterV2(types.TrustedClusterV2) returns (types.TrustedClusterV2);
// DeleteTrustedCluster deletes an existing Trusted Cluster in a backend by name.
rpc DeleteTrustedCluster(types.ResourceRequest) returns (google.protobuf.Empty);

Expand Down
14 changes: 12 additions & 2 deletions api/types/trustedcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import (
// TrustedCluster holds information needed for a cluster that can not be directly
// accessed (maybe be behind firewall without any open ports) to join a parent cluster.
type TrustedCluster interface {
// Resource provides common resource properties
Resource
// ResourceWithOrigin provides common resource properties
ResourceWithOrigin
// SetMetadata sets object metadata
SetMetadata(meta Metadata)
// GetEnabled returns the state of the TrustedCluster.
Expand Down Expand Up @@ -184,6 +184,16 @@ func (c *TrustedClusterV2) SetName(e string) {
c.Metadata.Name = e
}

// Origin returns the origin value of the resource.
func (c *TrustedClusterV2) Origin() string {
return c.Metadata.Origin()
}

// SetOrigin sets the origin value of the resource.
func (c *TrustedClusterV2) SetOrigin(origin string) {
c.Metadata.SetOrigin(origin)
}

bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
// GetEnabled returns the state of the TrustedCluster.
func (c *TrustedClusterV2) GetEnabled() bool {
return c.Spec.Enabled
Expand Down
30 changes: 28 additions & 2 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func TestIntegrations(t *testing.T) {
t.Run("TrustedDisabledClusters", suite.bind(testDisabledTrustedClusters))
t.Run("TrustedClustersRoleMapChanges", suite.bind(testTrustedClustersRoleMapChanges))
t.Run("TrustedClustersWithLabels", suite.bind(testTrustedClustersWithLabels))
t.Run("ValidatedTrustedClusters", suite.bind(testValidatedTrustedClusters))
t.Run("TrustedTunnelNode", suite.bind(testTrustedTunnelNode))
t.Run("TwoClustersProxy", suite.bind(testTwoClustersProxy))
t.Run("TwoClustersTunnel", suite.bind(testTwoClustersTunnel))
Expand Down Expand Up @@ -2946,6 +2947,9 @@ type trustedClusterTest struct {
// useLabels turns on trusted cluster labels and
// verifies RBAC
useLabels bool
// validateName uses UpsertValidatedTrustedCluster and ensures that a
// valid cluster name is provided before upserting the trusted cluster.
validateName bool
}

// TestTrustedClusters tests remote clusters scenarios
Expand Down Expand Up @@ -3009,6 +3013,15 @@ func testMultiplexingTrustedClusters(t *testing.T, suite *integrationTestSuite)
trustedClusters(t, suite, trustedClusterTest{multiplex: true})
}

// TestValidatedTrustedClusters tests remote clusters scenarios
// using validated trusted clusters
func testValidatedTrustedClusters(t *testing.T, suite *integrationTestSuite) {
tr := utils.NewTracer(utils.ThisFunction()).Start()
defer tr.Stop()

trustedClusters(t, suite, trustedClusterTest{validateName: true})
}

func standardPortsOrMuxSetup(t *testing.T, mux bool, fds *[]*servicecfg.FileDescriptor) *helpers.InstanceListeners {
if mux {
return helpers.WebReverseTunnelMuxPortSetup(t, fds)
Expand Down Expand Up @@ -3212,8 +3225,21 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus
require.Error(t, err, "expected tunnel to close and SSH client to start failing")

// recreating the trusted cluster should re-establish connection
_, err = aux.Process.GetAuthServer().UpsertTrustedCluster(ctx, trustedCluster)
require.NoError(t, err)
if test.validateName {
// Note that the trusted cluster resource name must match the cluster name.
// Modify the trusted cluster resource name and expect the upsert to fail.
trustedCluster.SetName(main.Secrets.SiteName + "-cluster")
_, err = aux.Process.GetAuthServer().UpsertValidatedTrustedCluster(ctx, trustedCluster)
require.Error(t, err, "expected failure due to tc name mismatch")
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved

// Modify the trusted cluster resource name back to what it was orignally.
trustedCluster.SetName(main.Secrets.SiteName)
_, err = aux.Process.GetAuthServer().UpsertValidatedTrustedCluster(ctx, trustedCluster)
require.NoError(t, err)
} else {
_, err = aux.Process.GetAuthServer().UpsertTrustedCluster(ctx, trustedCluster)
require.NoError(t, err)
}

// check that remote cluster has been re-provisioned
remoteClusters, err = main.Process.GetAuthServer().GetRemoteClusters(ctx)
Expand Down
18 changes: 18 additions & 0 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -4925,6 +4925,24 @@ func (a *ServerWithRoles) UpsertTrustedCluster(ctx context.Context, tc types.Tru
return a.authServer.UpsertTrustedCluster(ctx, tc)
}

// UpsertValidatedTrustedCluster creates or updates a trusted cluster.
func (a *ServerWithRoles) UpsertValidatedTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) {
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
// Don't allow a Cloud tenant to be a leaf cluster.
if modules.GetModules().Features().Cloud {
return nil, trace.NotImplemented("cloud tenants cannot be leaf clusters")
}

if err := a.action(types.KindTrustedCluster, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}

if err := a.context.AuthorizeAdminActionAllowReusedMFA(); err != nil {
return nil, trace.Wrap(err)
}

return a.authServer.UpsertValidatedTrustedCluster(ctx, tc)
}

func (a *ServerWithRoles) ValidateTrustedCluster(ctx context.Context, validateRequest *authclient.ValidateTrustedClusterRequest) (*authclient.ValidateTrustedClusterResponse, error) {
// Don't allow a leaf cluster to be added to a Cloud tenant.
if modules.GetModules().Features().Cloud {
Expand Down
22 changes: 22 additions & 0 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2935,6 +2935,8 @@ func (g *GRPCServer) GetTrustedClusters(ctx context.Context, _ *emptypb.Empty) (
}

// UpsertTrustedCluster upserts a Trusted Cluster.
//
// Deprecated: Use [GRPCServer.UpsertTrustedClusterV2] instead.
func (g *GRPCServer) UpsertTrustedCluster(ctx context.Context, cluster *types.TrustedClusterV2) (*types.TrustedClusterV2, error) {
auth, err := g.authenticate(ctx)
if err != nil {
Expand All @@ -2955,6 +2957,26 @@ func (g *GRPCServer) UpsertTrustedCluster(ctx context.Context, cluster *types.Tr
return trustedClusterV2, nil
}

// UpsertTrustedClusterV2 upserts a Trusted Cluster.
func (g *GRPCServer) UpsertTrustedClusterV2(ctx context.Context, cluster *types.TrustedClusterV2) (*types.TrustedClusterV2, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err = services.ValidateTrustedCluster(cluster); err != nil {
return nil, trace.Wrap(err)
}
tc, err := auth.ServerWithRoles.UpsertValidatedTrustedCluster(ctx, cluster)
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, trace.Wrap(err)
}
trustedClusterV2, ok := tc.(*types.TrustedClusterV2)
if !ok {
return nil, trace.Errorf("encountered unexpected Trusted Cluster type: %T", tc)
}
return trustedClusterV2, nil
}

// DeleteTrustedCluster deletes a Trusted Cluster by name.
func (g *GRPCServer) DeleteTrustedCluster(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
auth, err := g.authenticate(ctx)
Expand Down
28 changes: 21 additions & 7 deletions lib/auth/trustedcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ import (

// UpsertTrustedCluster creates or toggles a Trusted Cluster relationship.
func (a *Server) UpsertTrustedCluster(ctx context.Context, tc types.TrustedCluster) (newTrustedCluster types.TrustedCluster, returnErr error) {
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
upserted, err := a.upsertTrustedCluster(ctx, tc, false)
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
return upserted, trace.Wrap(err)
}

// UpsertValidatedTrustedCluster creates or toggles a Trusted Cluster relationship.
// The trusted cluster resource name must match the cluster name.
func (a *Server) UpsertValidatedTrustedCluster(ctx context.Context, tc types.TrustedCluster) (newTrustedCluster types.TrustedCluster, returnErr error) {
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
upserted, err := a.upsertTrustedCluster(ctx, tc, true)
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
return upserted, trace.Wrap(err)
}

// upsertTrustedCluster creates or toggles a Trusted Cluster relationship.
func (a *Server) upsertTrustedCluster(ctx context.Context, tc types.TrustedCluster, validateName bool) (newTrustedCluster types.TrustedCluster, returnErr error) {
// verify that trusted cluster role map does not reference non-existent roles
if err := a.checkLocalRoles(ctx, tc.GetRoleMap()); err != nil {
return nil, trace.Wrap(err)
Expand All @@ -67,7 +80,7 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, tc types.TrustedClust

// if there is no existing cluster, switch to the create case
if existingCluster == nil {
return a.createTrustedCluster(ctx, tc)
return a.createTrustedCluster(ctx, tc, validateName)
}

if err := existingCluster.CanChangeStateTo(tc); err != nil {
Expand Down Expand Up @@ -103,8 +116,8 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, tc types.TrustedClust
return tc, nil
}

func (a *Server) createTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) {
remoteCAs, err := a.establishTrust(ctx, tc)
func (a *Server) createTrustedCluster(ctx context.Context, tc types.TrustedCluster, validateName bool) (types.TrustedCluster, error) {
remoteCAs, err := a.establishTrust(ctx, tc, validateName)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -267,7 +280,7 @@ func (a *Server) DeleteTrustedCluster(ctx context.Context, name string) error {
return nil
}

func (a *Server) establishTrust(ctx context.Context, trustedCluster types.TrustedCluster) ([]types.CertAuthority, error) {
func (a *Server) establishTrust(ctx context.Context, trustedCluster types.TrustedCluster, validateName bool) ([]types.CertAuthority, error) {
var localCertAuthorities []types.CertAuthority

domainName, err := a.GetDomainName()
Expand Down Expand Up @@ -322,9 +335,10 @@ func (a *Server) establishTrust(ctx context.Context, trustedCluster types.Truste
if remoteClusterName == domainName {
return nil, trace.BadParameter("remote cluster name can not be the same as local cluster name")
}
// TODO(klizhentas) in 2.5.0 prohibit adding trusted cluster resource name
// different from cluster name (we had no way of checking this before x509,
// because SSH CA was a public key, not a cert with metadata)
if validateName && trustedCluster.GetName() != remoteClusterName {
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
return nil, trace.CompareFailed("trusted cluster resource name must be the same as the remote cluster name. got: %q, expected: %q",
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
bernardjkim marked this conversation as resolved.
Show resolved Hide resolved
trustedCluster.GetName(), remoteClusterName)
}
}
}

Expand Down
Loading