diff --git a/constants.go b/constants.go index fa36f3bddab19..855ec10530fa2 100644 --- a/constants.go +++ b/constants.go @@ -821,3 +821,9 @@ const ( // used for connection upgrades. WebAPIConnUpgradeConnectionType = "Upgrade" ) + +const ( + // KubeLegacyProxySuffix is the suffix used for legacy proxy services when + // generating their names Server names. + KubeLegacyProxySuffix = "-proxy_service" +) diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 6b3e4f2055c84..96c8807ce0564 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -1274,12 +1274,22 @@ func (a *ServerWithRoles) KeepAliveServer(ctx context.Context, handle types.Keep return trace.AccessDenied("access denied") } } else { // DELETE IN 13.0. Legacy kubeservice server is heartbeating back. - if serverName != handle.Name { + name := handle.Name + // legacy kube proxy server is heartbeating kubernetes clusters + // with the server name suffixed with "-proxy_service". + // To compare the server name with the name in the heartbeat + // we need to remove the suffix. + if a.hasBuiltinRole(types.RoleProxy) { + name = strings.TrimSuffix(name, teleport.KubeLegacyProxySuffix) + } + if name != serverName { return trace.AccessDenied("access denied") } } - if !a.hasBuiltinRole(types.RoleKube) { + // Legacy kube proxy can heartbeat kube servers from the proxy itself so + // we need to check if the host has the Kube or Proxy role. + if !a.hasBuiltinRole(types.RoleKube, types.RoleProxy) { return trace.AccessDenied("access denied") } if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbUpdate); err != nil { diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index d57866da1b861..ee03c2575746f 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -5285,3 +5285,93 @@ func createSessionTestUsers(t *testing.T, authServer *Server) (string, string, s require.NoError(t, err) return "alice", "bob", "admin" } + +func TestKubeKeepAliveServer(t *testing.T) { + t.Parallel() + srv := newTestTLSServer(t) + domainName, err := srv.Auth().GetDomainName() + require.NoError(t, err) + + tests := map[string]struct { + builtInRole types.SystemRole + assertErr require.ErrorAssertionFunc + }{ + "as kube service": { + builtInRole: types.RoleKube, + assertErr: require.NoError, + }, + "as legacy proxy service": { + builtInRole: types.RoleProxy, + assertErr: require.NoError, + }, + "as database service": { + builtInRole: types.RoleDatabase, + assertErr: require.Error, + }, + } + for name, test := range tests { + test := test + t.Run(name, func(t *testing.T) { + t.Parallel() + hostID := uuid.New().String() + // Create a kubernetes cluster. + kube, err := types.NewKubernetesClusterV3( + types.Metadata{ + Name: "kube", + Namespace: defaults.Namespace, + }, + types.KubernetesClusterSpecV3{}, + ) + require.NoError(t, err) + // Create a kubernetes server. + // If the built-in role is proxy, the server name should be + // kube-proxy_service + serverName := "kube" + if test.builtInRole == types.RoleProxy { + serverName += teleport.KubeLegacyProxySuffix + } + kubeServer, err := types.NewKubernetesServerV3( + types.Metadata{ + Name: serverName, + Namespace: defaults.Namespace, + }, + types.KubernetesServerSpecV3{ + Cluster: kube, + HostID: hostID, + }, + ) + require.NoError(t, err) + // Upsert the kubernetes server into the backend. + _, err = srv.Auth().UpsertKubernetesServer(context.Background(), kubeServer) + require.NoError(t, err) + + // Create a built-in role. + authContext, err := authz.ContextForBuiltinRole( + authz.BuiltinRole{ + Role: test.builtInRole, + Username: fmt.Sprintf("%s.%s", hostID, domainName), + }, + types.DefaultSessionRecordingConfig(), + ) + require.NoError(t, err) + + // Create a server with the built-in role. + srv := ServerWithRoles{ + authServer: srv.Auth(), + context: *authContext, + } + // Keep alive the server. + err = srv.KeepAliveServer(context.Background(), + types.KeepAlive{ + Type: types.KeepAlive_KUBERNETES, + Expires: time.Now().Add(5 * time.Minute), + Name: serverName, + Namespace: defaults.Namespace, + HostID: hostID, + }, + ) + test.assertErr(t, err) + }, + ) + } +} diff --git a/lib/kube/proxy/server.go b/lib/kube/proxy/server.go index 13c58e217249e..da6b4afee2b1f 100644 --- a/lib/kube/proxy/server.go +++ b/lib/kube/proxy/server.go @@ -366,7 +366,7 @@ func (t *TLSServer) getServerInfo(name string) (types.Resource, error) { // Note: we *don't* want to add suffix for kubernetes_service! // This breaks reverse tunnel routing, which uses server.Name. if t.KubeServiceType != KubeService { - name += "-proxy_service" + name += teleport.KubeLegacyProxySuffix } srv, err := types.NewKubernetesServerV3(