mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-30 18:29:21 +08:00
Add the notion of friendly names to access request details. (#27495)
* At the notion of friendly names to access request details. Access request details can now return friendly names. This will help display access request resources in a way that allows easier reviewing of Okta apps and user groups, where the names are not human readable. * Use friendly name for nodes, tune variable names. * Add in DetailsID helper function for use by the enterprise UI. * Request apps instead of app servers, remove DetailsID as its no longer needed. * Push friendly name into app and usergroup UI objects. * Fix apps test. * Linting. * [Connect] Add FriendlyName to AccessRequest (#27694) --------- Co-authored-by: Michael <michael.myers@goteleport.com>
This commit is contained in:
co-authored by
Michael
parent
3433ad7f67
commit
1b3201a97c
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
+4
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-1
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -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() {
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ message ResourceID {
|
||||
|
||||
message ResourceDetails {
|
||||
string hostname = 1;
|
||||
string friendly_name = 2;
|
||||
}
|
||||
|
||||
message Resource {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user