diff --git a/api/types/resource.go b/api/types/resource.go index 34bf5d2e422..5b86f819fc6 100644 --- a/api/types/resource.go +++ b/api/types/resource.go @@ -59,7 +59,8 @@ type Resource interface { // ResourceDetails includes details about the resource type ResourceDetails struct { - Hostname string + Hostname string + FriendlyName string } // ResourceWithSecrets includes additional properties which must diff --git a/gen/proto/go/teleport/lib/teleterm/v1/access_request.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/access_request.pb.go index 7a7a5be0c59..8dbe0b4d4e3 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/access_request.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/access_request.pb.go @@ -343,7 +343,8 @@ type ResourceDetails struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` + Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` + FriendlyName string `protobuf:"bytes,2,opt,name=friendly_name,json=friendlyName,proto3" json:"friendly_name,omitempty"` } func (x *ResourceDetails) Reset() { @@ -385,6 +386,13 @@ func (x *ResourceDetails) GetHostname() string { return "" } +func (x *ResourceDetails) GetFriendlyName() string { + if x != nil { + return x.FriendlyName + } + return "" +} + type Resource struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -505,24 +513,26 @@ var file_teleport_lib_teleterm_v1_access_request_proto_rawDesc = []byte{ 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x75, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x52, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, - 0x61, 0x6d, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x34, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, + 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x72, 0x69, 0x65, + 0x6e, 0x64, 0x6c, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x07, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x49, 0x44, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x42, 0x54, 0x5a, 0x52, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x72, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x42, 0x54, 0x5a, 0x52, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6c, 0x69, 0x62, 0x2f, + 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x72, 0x6d, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.d.ts b/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.d.ts index d478a30e0a9..2d9e8b1f3d8 100644 --- a/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.d.ts +++ b/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.d.ts @@ -173,6 +173,9 @@ export class ResourceDetails extends jspb.Message { getHostname(): string; setHostname(value: string): ResourceDetails; + getFriendlyName(): string; + setFriendlyName(value: string): ResourceDetails; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ResourceDetails.AsObject; @@ -187,6 +190,7 @@ export class ResourceDetails extends jspb.Message { export namespace ResourceDetails { export type AsObject = { hostname: string, + friendlyName: string, } } diff --git a/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.js b/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.js index ff0982d41f0..b9289d9ec8a 100644 --- a/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.js +++ b/gen/proto/js/teleport/lib/teleterm/v1/access_request_pb.js @@ -1341,7 +1341,8 @@ proto.teleport.lib.teleterm.v1.ResourceDetails.prototype.toObject = function(opt */ proto.teleport.lib.teleterm.v1.ResourceDetails.toObject = function(includeInstance, msg) { var f, obj = { - hostname: jspb.Message.getFieldWithDefault(msg, 1, "") + hostname: jspb.Message.getFieldWithDefault(msg, 1, ""), + friendlyName: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -1382,6 +1383,10 @@ proto.teleport.lib.teleterm.v1.ResourceDetails.deserializeBinaryFromReader = fun var value = /** @type {string} */ (reader.readString()); msg.setHostname(value); break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFriendlyName(value); + break; default: reader.skipField(); break; @@ -1418,6 +1423,13 @@ proto.teleport.lib.teleterm.v1.ResourceDetails.serializeBinaryToWriter = functio f ); } + f = message.getFriendlyName(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } }; @@ -1439,6 +1451,24 @@ proto.teleport.lib.teleterm.v1.ResourceDetails.prototype.setHostname = function( }; +/** + * optional string friendly_name = 2; + * @return {string} + */ +proto.teleport.lib.teleterm.v1.ResourceDetails.prototype.getFriendlyName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.lib.teleterm.v1.ResourceDetails} returns this + */ +proto.teleport.lib.teleterm.v1.ResourceDetails.prototype.setFriendlyName = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + diff --git a/lib/services/access_request.go b/lib/services/access_request.go index b4594ef5d80..45c2ff05229 100644 --- a/lib/services/access_request.go +++ b/lib/services/access_request.go @@ -1668,14 +1668,14 @@ func (m *RequestValidator) roleAllowsResource( type ListResourcesRequestOption func(*proto.ListResourcesRequest) func GetResourceDetails(ctx context.Context, clusterName string, lister ResourceLister, ids []types.ResourceID) (map[string]types.ResourceDetails, error) { - var nodeIDs []types.ResourceID + var resourceIDs []types.ResourceID for _, resourceID := range ids { - if resourceID.Kind != types.KindNode { - // The only detail we want, for now, is the server hostname, so we - // can skip all other resource kinds as a minor optimization. - continue + // We're interested in hostname or friendly name details. These apply to + // nodes, app servers, and user groups. + switch resourceID.Kind { + case types.KindNode, types.KindApp, types.KindUserGroup: + resourceIDs = append(resourceIDs, resourceID) } - nodeIDs = append(nodeIDs, resourceID) } withExtraRoles := func(req *proto.ListResourcesRequest) { @@ -1683,36 +1683,37 @@ func GetResourceDetails(ctx context.Context, clusterName string, lister Resource req.UsePreviewAsRoles = true } - resources, err := GetResourcesByResourceIDs(ctx, lister, nodeIDs, withExtraRoles) + resources, err := GetResourcesByResourceIDs(ctx, lister, resourceIDs, withExtraRoles) if err != nil { return nil, trace.Wrap(err) } result := make(map[string]types.ResourceDetails) for _, resource := range resources { - hn, ok := resource.(interface{ GetHostname() string }) - if !ok { + friendlyName := FriendlyName(resource) + + // No friendly name was found, so skip to the next resource. + if friendlyName == "" { continue } + id := types.ResourceID{ ClusterName: clusterName, Kind: resource.GetKind(), Name: resource.GetName(), } result[types.ResourceIDToString(id)] = types.ResourceDetails{ - Hostname: hn.GetHostname(), + FriendlyName: friendlyName, } } return result, nil } -func GetNodeResourceIDsByCluster(r types.AccessRequest) map[string][]types.ResourceID { +// GetResourceIDsByCluster will return resource IDs grouped by cluster. +func GetResourceIDsByCluster(r types.AccessRequest) map[string][]types.ResourceID { resourceIDsByCluster := make(map[string][]types.ResourceID) for _, resourceID := range r.GetRequestedResourceIDs() { - if resourceID.Kind != types.KindNode { - continue - } resourceIDsByCluster[resourceID.ClusterName] = append(resourceIDsByCluster[resourceID.ClusterName], resourceID) } return resourceIDsByCluster diff --git a/lib/services/access_request_test.go b/lib/services/access_request_test.go index fcf9cf6c722..5c6e14204d2 100644 --- a/lib/services/access_request_test.go +++ b/lib/services/access_request_test.go @@ -1816,3 +1816,100 @@ func TestValidateAccessRequestClusterNames(t *testing.T) { }) } } + +type mockResourceLister struct { + resources []types.ResourceWithLabels +} + +func (m *mockResourceLister) ListResources(ctx context.Context, _ proto.ListResourcesRequest) (*types.ListResourcesResponse, error) { + return &types.ListResourcesResponse{ + Resources: m.resources, + }, nil +} + +func TestGetResourceDetails(t *testing.T) { + clusterName := "cluster" + + presence := &mockResourceLister{ + resources: []types.ResourceWithLabels{ + newNode(t, "node1", "hostname 1"), + newApp(t, "app1", "friendly app 1", types.OriginDynamic), + newApp(t, "app2", "friendly app 2", types.OriginDynamic), + newApp(t, "app3", "friendly app 3", types.OriginOkta), + newUserGroup(t, "group1", "friendly group 1", types.OriginOkta), + }, + } + resourceIDs := []types.ResourceID{ + newResourceID(clusterName, types.KindNode, "node1"), + newResourceID(clusterName, types.KindApp, "app1"), + newResourceID(clusterName, types.KindApp, "app2"), + newResourceID(clusterName, types.KindApp, "app3"), + newResourceID(clusterName, types.KindUserGroup, "group1"), + } + + ctx := context.Background() + + details, err := GetResourceDetails(ctx, clusterName, presence, resourceIDs) + require.NoError(t, err) + + // Check the resource details to see if friendly names properly propagated. + + // Node should be named for its hostname. + require.Equal(t, "hostname 1", details[types.ResourceIDToString(resourceIDs[0])].FriendlyName) + + // app1 and app2 are expected to be empty because they're not Okta sourced resources. + require.Empty(t, details[types.ResourceIDToString(resourceIDs[1])].FriendlyName) + + require.Empty(t, details[types.ResourceIDToString(resourceIDs[2])].FriendlyName) + + // This Okta sourced app should have a friendly name. + require.Equal(t, "friendly app 3", details[types.ResourceIDToString(resourceIDs[3])].FriendlyName) + + // This Okta sourced user group should have a friendly name. + require.Equal(t, "friendly group 1", details[types.ResourceIDToString(resourceIDs[4])].FriendlyName) +} + +func newNode(t *testing.T, name, hostname string) types.Server { + node, err := types.NewServer(name, types.KindNode, + types.ServerSpecV2{ + Hostname: hostname, + }) + require.NoError(t, err) + return node +} + +func newApp(t *testing.T, name, description, origin string) types.Application { + app, err := types.NewAppV3(types.Metadata{ + Name: name, + Description: description, + Labels: map[string]string{ + types.OriginLabel: origin, + }, + }, + types.AppSpecV3{ + URI: "https://some-addr.com", + PublicAddr: "https://some-addr.com", + }) + require.NoError(t, err) + return app +} + +func newUserGroup(t *testing.T, name, description, origin string) types.UserGroup { + userGroup, err := types.NewUserGroup(types.Metadata{ + Name: name, + Description: description, + Labels: map[string]string{ + types.OriginLabel: origin, + }, + }) + require.NoError(t, err) + return userGroup +} + +func newResourceID(clusterName, kind, name string) types.ResourceID { + return types.ResourceID{ + ClusterName: clusterName, + Kind: kind, + Name: name, + } +} diff --git a/lib/services/friendlyname.go b/lib/services/friendlyname.go new file mode 100644 index 00000000000..99c6a41b5c2 --- /dev/null +++ b/lib/services/friendlyname.go @@ -0,0 +1,34 @@ +/* +Copyright 2023 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 services + +import "github.com/gravitational/teleport/api/types" + +// FriendlyName will return the friendly name for a resource if it has one. Otherwise, it +// will return an empty string. +func FriendlyName(resource types.ResourceWithLabels) string { + // Right now, only resources sourced from Okta and nodes have friendly names. + if resource.Origin() == types.OriginOkta { + return resource.GetMetadata().Description + } + + if hn, ok := resource.(interface{ GetHostname() string }); ok { + return hn.GetHostname() + } + + return "" +} diff --git a/lib/services/friendlyname_test.go b/lib/services/friendlyname_test.go new file mode 100644 index 00000000000..f5c7f3affb0 --- /dev/null +++ b/lib/services/friendlyname_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2023 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 services + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +func TestFriendlyName(t *testing.T) { + appNoFriendly, err := types.NewAppV3(types.Metadata{ + Name: "no friendly", + }, types.AppSpecV3{ + URI: "https://some-uri.com", + }, + ) + require.NoError(t, err) + + appFriendly, err := types.NewAppV3(types.Metadata{ + Name: "no friendly", + Description: "friendly name", + Labels: map[string]string{ + types.OriginLabel: types.OriginOkta, + }, + }, types.AppSpecV3{ + URI: "https://some-uri.com", + }, + ) + require.NoError(t, err) + + node, err := types.NewServer("node", types.KindNode, types.ServerSpecV2{ + Hostname: "friendly hostname", + }) + require.NoError(t, err) + + tests := []struct { + name string + resource types.ResourceWithLabels + expected string + }{ + { + name: "no friendly name", + resource: appNoFriendly, + expected: "", + }, + { + name: "friendly app name", + resource: appFriendly, + expected: "friendly name", + }, + { + name: "friendly node name", + resource: node, + expected: "friendly hostname", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, FriendlyName(test.resource)) + }) + } +} diff --git a/lib/teleterm/apiserver/handler/handler_access_requests.go b/lib/teleterm/apiserver/handler/handler_access_requests.go index d7ca1a26fef..d1b3e053e10 100644 --- a/lib/teleterm/apiserver/handler/handler_access_requests.go +++ b/lib/teleterm/apiserver/handler/handler_access_requests.go @@ -179,6 +179,7 @@ func resourceIDToString(id *api.ResourceID) string { func newAPIResourceDetails(details clusters.ResourceDetails) *api.ResourceDetails { return &api.ResourceDetails{ - Hostname: details.Hostname, + Hostname: details.Hostname, + FriendlyName: details.FriendlyName, } } diff --git a/lib/teleterm/clusters/cluster_access_requests.go b/lib/teleterm/clusters/cluster_access_requests.go index c90365d8845..0dee74f6811 100644 --- a/lib/teleterm/clusters/cluster_access_requests.go +++ b/lib/teleterm/clusters/cluster_access_requests.go @@ -30,7 +30,8 @@ import ( ) type ResourceDetails struct { - Hostname string + Hostname string + FriendlyName string } type AccessRequest struct { @@ -269,7 +270,7 @@ func (c *Cluster) AssumeRole(ctx context.Context, req *api.AssumeRoleRequest) er } func getResourceDetails(ctx context.Context, req types.AccessRequest, clt auth.ClientI) (map[string]ResourceDetails, error) { - resourceIDsByCluster := services.GetNodeResourceIDsByCluster(req) + resourceIDsByCluster := services.GetResourceIDsByCluster(req) resourceDetails := make(map[string]ResourceDetails) for clusterName, resourceIDs := range resourceIDsByCluster { @@ -279,7 +280,7 @@ func getResourceDetails(ctx context.Context, req types.AccessRequest, clt auth.C } for id, d := range details { resourceDetails[id] = ResourceDetails{ - Hostname: d.Hostname, + FriendlyName: d.FriendlyName, } } } diff --git a/lib/web/ui/app.go b/lib/web/ui/app.go index 9e5efa9a37e..f327a104e28 100644 --- a/lib/web/ui/app.go +++ b/lib/web/ui/app.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils/aws" ) @@ -44,6 +45,8 @@ type App struct { AWSConsole bool `json:"awsConsole"` // AWSRoles is a list of AWS IAM roles for the application representing AWS console. AWSRoles []aws.Role `json:"awsRoles,omitempty"` + // FriendlyName is a friendly name for the app. + FriendlyName string `json:"friendlyName,omitempty"` } // MakeAppsConfig contains parameters for converting apps to UI representation. @@ -68,14 +71,15 @@ func MakeApps(c MakeAppsConfig) []App { labels := makeLabels(teleApp.GetAllLabels()) app := App{ - Name: teleApp.GetName(), - Description: teleApp.GetDescription(), - URI: teleApp.GetURI(), - PublicAddr: teleApp.GetPublicAddr(), - Labels: labels, - ClusterID: c.AppClusterName, - FQDN: fqdn, - AWSConsole: teleApp.IsAWSConsole(), + Name: teleApp.GetName(), + Description: teleApp.GetDescription(), + URI: teleApp.GetURI(), + PublicAddr: teleApp.GetPublicAddr(), + Labels: labels, + ClusterID: c.AppClusterName, + FQDN: fqdn, + AWSConsole: teleApp.IsAWSConsole(), + FriendlyName: services.FriendlyName(teleApp), } if teleApp.IsAWSConsole() { diff --git a/lib/web/ui/user_groups.go b/lib/web/ui/user_groups.go index 8c44f69e31a..b5656ae3815 100644 --- a/lib/web/ui/user_groups.go +++ b/lib/web/ui/user_groups.go @@ -29,6 +29,8 @@ type UserGroup struct { Description string `json:"description"` // Labels is the user group list of labels Labels []Label `json:"labels"` + // FriendlyName is a friendly name for the user group. + FriendlyName string `json:"friendlyName,omitempty"` } // MakeUserGroups creates user group objects for the UI. @@ -38,9 +40,10 @@ func MakeUserGroups(clusterName string, userGroups []types.UserGroup, userRoles uiLabels := makeLabels(userGroup.GetStaticLabels()) uiUserGroups = append(uiUserGroups, UserGroup{ - Name: userGroup.GetName(), - Description: userGroup.GetMetadata().Description, - Labels: uiLabels, + Name: userGroup.GetName(), + Description: userGroup.GetMetadata().Description, + Labels: uiLabels, + FriendlyName: services.FriendlyName(userGroup), }) } diff --git a/proto/teleport/lib/teleterm/v1/access_request.proto b/proto/teleport/lib/teleterm/v1/access_request.proto index 974a6cc4957..2b89461ed28 100644 --- a/proto/teleport/lib/teleterm/v1/access_request.proto +++ b/proto/teleport/lib/teleterm/v1/access_request.proto @@ -63,6 +63,7 @@ message ResourceID { message ResourceDetails { string hostname = 1; + string friendly_name = 2; } message Resource { diff --git a/web/packages/teleport/src/services/apps/apps.test.ts b/web/packages/teleport/src/services/apps/apps.test.ts index df80cab260d..cd2884d8511 100644 --- a/web/packages/teleport/src/services/apps/apps.test.ts +++ b/web/packages/teleport/src/services/apps/apps.test.ts @@ -34,6 +34,7 @@ test('correct formatting of apps fetch response', async () => { labels: [{ name: 'env', value: 'dev' }], clusterId: 'cluster-id', fqdn: 'app-name.example.com', + friendlyName: '', launchUrl: '/web/launch/app-name.example.com/cluster-id/app-name.example.com', awsRoles: [], @@ -50,6 +51,7 @@ test('correct formatting of apps fetch response', async () => { labels: [], clusterId: 'cluster-id', fqdn: '', + friendlyName: '', launchUrl: '', awsRoles: [], awsConsole: false, @@ -65,6 +67,7 @@ test('correct formatting of apps fetch response', async () => { labels: [], clusterId: 'cluster-id', fqdn: '', + friendlyName: '', launchUrl: '', awsRoles: [], awsConsole: false, diff --git a/web/packages/teleport/src/services/apps/makeApps.ts b/web/packages/teleport/src/services/apps/makeApps.ts index fb20dbd3a36..80fb339f577 100644 --- a/web/packages/teleport/src/services/apps/makeApps.ts +++ b/web/packages/teleport/src/services/apps/makeApps.ts @@ -28,6 +28,7 @@ export default function makeApp(json: any): App { clusterId = '', fqdn = '', awsConsole = false, + friendlyName = '', } = json; const canCreateUrl = fqdn && clusterId && publicAddr; @@ -66,5 +67,6 @@ export default function makeApp(json: any): App { awsConsole, isCloudOrTcpEndpoint: isTcp || isCloud, addrWithProtocol, + friendlyName, }; } diff --git a/web/packages/teleport/src/services/apps/types.ts b/web/packages/teleport/src/services/apps/types.ts index 581d4a68242..5ee114a34ec 100644 --- a/web/packages/teleport/src/services/apps/types.ts +++ b/web/packages/teleport/src/services/apps/types.ts @@ -32,6 +32,7 @@ export interface App { // addrWithProtocol can either be a public address or // if public address wasn't defined, fallback to uri addrWithProtocol?: string; + friendlyName?: string; } export type AwsRole = { diff --git a/web/packages/teleport/src/services/userGroups/makeUserGroup.ts b/web/packages/teleport/src/services/userGroups/makeUserGroup.ts index ddd41aad03c..7ca7c55163c 100644 --- a/web/packages/teleport/src/services/userGroups/makeUserGroup.ts +++ b/web/packages/teleport/src/services/userGroups/makeUserGroup.ts @@ -17,7 +17,7 @@ import type { UserGroup } from './types'; export function makeUserGroup(json): UserGroup { - const { name, description } = json; + const { name, description, friendlyName } = json; const labels = json.labels || []; @@ -25,5 +25,6 @@ export function makeUserGroup(json): UserGroup { name, description, labels, + friendlyName, }; } diff --git a/web/packages/teleport/src/services/userGroups/types.ts b/web/packages/teleport/src/services/userGroups/types.ts index 4678e53eb15..816dae39e2b 100644 --- a/web/packages/teleport/src/services/userGroups/types.ts +++ b/web/packages/teleport/src/services/userGroups/types.ts @@ -24,6 +24,8 @@ export type UserGroup = { description: string; // Labels for the user group. labels: AgentLabel[]; + // FriendlyName for the user group. + friendlyName?: string; }; export type UserGroupsResponse = {