github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/accessrequest/access_request.go (about) 1 /* 2 Copyright 2023 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package accessrequest 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "github.com/gravitational/trace" 25 26 "github.com/gravitational/teleport/api/client" 27 "github.com/gravitational/teleport/api/client/proto" 28 "github.com/gravitational/teleport/api/types" 29 ) 30 31 type ListResourcesRequestOption func(*proto.ListResourcesRequest) 32 33 // GetResourcesByKind is an alternative to client.GetResourcesWithFilters 34 // that searches with the resource kinds used in access requests instead of the 35 // resource types expected by ListResources. 36 // 37 // The ResourceType field in the request should not be set by the caller, as 38 // it will be overridden. 39 func GetResourcesByKind(ctx context.Context, clt client.ListResourcesClient, req proto.ListResourcesRequest, kind string) ([]types.ResourceWithLabels, error) { 40 req.ResourceType = mapResourceKindToListResourcesType(kind) 41 results, err := client.GetResourcesWithFilters(ctx, clt, req) 42 if err != nil { 43 return nil, trace.Wrap(err) 44 } 45 resources := make([]types.ResourceWithLabels, 0, len(results)) 46 for _, result := range results { 47 leafResource, err := mapListResourcesResultToLeafResource(result, kind) 48 if err != nil { 49 return nil, trace.Wrap(err) 50 } 51 resources = append(resources, leafResource) 52 } 53 return resources, nil 54 } 55 56 // GetResourceDetails gets extra details for a list of resources in a given cluster. 57 func GetResourceDetails(ctx context.Context, clusterName string, lister client.ListResourcesClient, ids []types.ResourceID) (map[string]types.ResourceDetails, error) { 58 var resourceIDs []types.ResourceID 59 for _, resourceID := range ids { 60 // We're interested in hostname or friendly name details. These apply to 61 // nodes, app servers, and user groups. 62 switch resourceID.Kind { 63 case types.KindNode, types.KindApp, types.KindUserGroup: 64 resourceIDs = append(resourceIDs, resourceID) 65 } 66 } 67 68 withExtraRoles := func(req *proto.ListResourcesRequest) { 69 req.UseSearchAsRoles = true 70 req.UsePreviewAsRoles = true 71 } 72 73 resources, err := GetResourcesByResourceIDs(ctx, lister, resourceIDs, withExtraRoles) 74 if err != nil { 75 return nil, trace.Wrap(err) 76 } 77 78 result := make(map[string]types.ResourceDetails) 79 for _, resource := range resources { 80 friendlyName := types.FriendlyName(resource) 81 82 // No friendly name was found, so skip to the next resource. 83 if friendlyName == "" { 84 continue 85 } 86 87 id := types.ResourceID{ 88 ClusterName: clusterName, 89 Kind: resource.GetKind(), 90 Name: resource.GetName(), 91 } 92 result[types.ResourceIDToString(id)] = types.ResourceDetails{ 93 FriendlyName: friendlyName, 94 } 95 } 96 97 return result, nil 98 } 99 100 // GetResourceIDsByCluster will return resource IDs grouped by cluster. 101 func GetResourceIDsByCluster(r types.AccessRequest) map[string][]types.ResourceID { 102 resourceIDsByCluster := make(map[string][]types.ResourceID) 103 for _, resourceID := range r.GetRequestedResourceIDs() { 104 resourceIDsByCluster[resourceID.ClusterName] = append(resourceIDsByCluster[resourceID.ClusterName], resourceID) 105 } 106 return resourceIDsByCluster 107 } 108 109 // GetResourcesByResourceID gets a list of resources by their resource IDs. 110 func GetResourcesByResourceIDs(ctx context.Context, lister client.ListResourcesClient, resourceIDs []types.ResourceID, opts ...ListResourcesRequestOption) ([]types.ResourceWithLabels, error) { 111 resourceNamesByKind := make(map[string][]string) 112 for _, resourceID := range resourceIDs { 113 resourceNamesByKind[resourceID.Kind] = append(resourceNamesByKind[resourceID.Kind], resourceID.Name) 114 } 115 var resources []types.ResourceWithLabels 116 for kind, resourceNames := range resourceNamesByKind { 117 req := proto.ListResourcesRequest{ 118 PredicateExpression: anyNameMatcher(resourceNames), 119 Limit: int32(len(resourceNames)), 120 } 121 for _, opt := range opts { 122 opt(&req) 123 } 124 resp, err := GetResourcesByKind(ctx, lister, req, kind) 125 if err != nil { 126 return nil, trace.Wrap(err) 127 } 128 resources = append(resources, resp...) 129 } 130 return resources, nil 131 } 132 133 // anyNameMatcher returns a PredicateExpression which matches any of a given list 134 // of names. Given names will be escaped and quoted when building the expression. 135 func anyNameMatcher(names []string) string { 136 matchers := make([]string, len(names)) 137 for i := range names { 138 matchers[i] = fmt.Sprintf(`resource.metadata.name == %q`, names[i]) 139 } 140 return strings.Join(matchers, " || ") 141 } 142 143 // mapResourceKindToListResourcesType returns the value to use for ResourceType in a 144 // ListResourcesRequest based on the kind of resource you're searching for. 145 // Necessary because some resource kinds don't support ListResources directly, 146 // so you have to list the parent kind. Use MapListResourcesResultToLeafResource to map back 147 // to the given kind. 148 func mapResourceKindToListResourcesType(kind string) string { 149 switch kind { 150 case types.KindApp: 151 return types.KindAppServer 152 case types.KindDatabase: 153 return types.KindDatabaseServer 154 case types.KindKubernetesCluster: 155 return types.KindKubeServer 156 default: 157 return kind 158 } 159 } 160 161 // mapListResourcesResultToLeafResource is the inverse of 162 // MapResourceKindToListResourcesType, after the ListResources call it maps the 163 // result back to the kind we really want. `hint` should be the name of the 164 // desired resource kind, used to disambiguate normal SSH nodes and kubernetes 165 // services which are both returned as `types.Server`. 166 func mapListResourcesResultToLeafResource(resource types.ResourceWithLabels, hint string) (types.ResourceWithLabels, error) { 167 switch r := resource.(type) { 168 case types.AppServer: 169 return r.GetApp(), nil 170 case types.KubeServer: 171 return r.GetCluster(), nil 172 case types.DatabaseServer: 173 return r.GetDatabase(), nil 174 case types.Server: 175 if hint == types.KindKubernetesCluster { 176 return nil, trace.BadParameter("expected kubernetes server, got server") 177 } 178 default: 179 } 180 return resource, nil 181 }