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  }