// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package targets import ( "context" stderrors "errors" "fmt" "math/rand" "net" "net/url" "strconv" "strings" "sync/atomic" "time" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/alias" talias "github.com/hashicorp/boundary/internal/alias/target" "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/common" "github.com/hashicorp/boundary/internal/daemon/controller/downstream" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/event" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" intglobals "github.com/hashicorp/boundary/internal/globals" "github.com/hashicorp/boundary/internal/host" "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/listtoken" "github.com/hashicorp/boundary/internal/pagination" "github.com/hashicorp/boundary/internal/perms" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/internal/types/subtypes" "github.com/hashicorp/boundary/internal/util" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" fm "github.com/hashicorp/boundary/version" "github.com/hashicorp/go-bexpr" "github.com/hashicorp/go-secure-stdlib/strutil" "github.com/mr-tron/base58" "google.golang.org/grpc/codes" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) var ( // IdActions contains the set of actions that can be performed on // individual resources IdActions = action.NewActionSet( action.NoOp, action.Read, action.Update, action.Delete, action.AddHostSources, action.SetHostSources, action.RemoveHostSources, action.AddCredentialSources, action.SetCredentialSources, action.RemoveCredentialSources, action.AuthorizeSession, ) // CollectionActions contains the set of actions that can be performed on // this collection CollectionActions = action.NewActionSet( action.Create, action.List, ) validateCredentialSourcesFn = func(context.Context, globals.Subtype, []target.CredentialSource) error { return nil } ValidateIngressWorkerFilterFn = IngressWorkerFilterUnsupported SessionRecordingFn = NoSessionRecording WorkerFilterDeprecationMessage = fmt.Sprintf("This field is deprecated. Use %s instead.", globals.EgressWorkerFilterField) StorageBucketFilterCredIdFn = noStorageBucket ValidateLicenseFn = noOpValidateLicense ) func init() { // TODO: refactor to remove IdActions and CollectionActions package variables action.RegisterResource(resource.Target, IdActions, CollectionActions) } func IngressWorkerFilterUnsupported(string) error { return fmt.Errorf("Ingress Worker Filter field is not supported in OSS") } // Service handles request as described by the pbs.TargetServiceServer interface. type Service struct { pbs.UnsafeTargetServiceServer repoFn target.RepositoryFactory aliasRepoFn common.TargetAliasRepoFactory iamRepoFn common.IamRepoFactory serversRepoFn common.ServersRepoFactory sessionRepoFn session.RepositoryFactory pluginHostRepoFn common.PluginHostRepoFactory staticHostRepoFn common.StaticRepoFactory vaultCredRepoFn common.VaultCredentialRepoFactory staticCredRepoFn common.StaticCredentialRepoFactory downstreams downstream.Graph kmsCache *kms.Kms workerRPCGracePeriod *atomic.Int64 maxPageSize uint controllerExt intglobals.ControllerExtension } var _ pbs.TargetServiceServer = (*Service)(nil) // NewService returns a target service which handles target related requests to boundary. func NewService( ctx context.Context, kmsCache *kms.Kms, repoFn target.RepositoryFactory, iamRepoFn common.IamRepoFactory, serversRepoFn common.ServersRepoFactory, sessionRepoFn session.RepositoryFactory, pluginHostRepoFn common.PluginHostRepoFactory, staticHostRepoFn common.StaticRepoFactory, vaultCredRepoFn common.VaultCredentialRepoFactory, staticCredRepoFn common.StaticCredentialRepoFactory, aliasRepoFn common.TargetAliasRepoFactory, downstreams downstream.Graph, workerRPCGracePeriod *atomic.Int64, maxPageSize uint, controllerExt intglobals.ControllerExtension, ) (Service, error) { const op = "targets.NewService" switch { case kmsCache == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing kms repo") case repoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing target repository") case iamRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository") case serversRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing server repository") case sessionRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing session repository") case pluginHostRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing plugin host repository") case staticHostRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static host repository") case vaultCredRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing vault credential repository") case staticCredRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static credential repository") case aliasRepoFn == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing target alias repository") } if maxPageSize == 0 { maxPageSize = uint(globals.DefaultMaxPageSize) } return Service{ repoFn: repoFn, iamRepoFn: iamRepoFn, serversRepoFn: serversRepoFn, sessionRepoFn: sessionRepoFn, pluginHostRepoFn: pluginHostRepoFn, staticHostRepoFn: staticHostRepoFn, vaultCredRepoFn: vaultCredRepoFn, staticCredRepoFn: staticCredRepoFn, aliasRepoFn: aliasRepoFn, downstreams: downstreams, kmsCache: kmsCache, workerRPCGracePeriod: workerRPCGracePeriod, maxPageSize: maxPageSize, controllerExt: controllerExt, }, nil } // ListTargets implements the interface pbs.TargetServiceServer. func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (*pbs.ListTargetsResponse, error) { const op = "targets.(Service).ListTargets" if err := validateListRequest(ctx, req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetScopeId(), action.List) if authResults.Error != nil { // If it's forbidden, and it's a recursive request, and they're // successfully authenticated but just not authorized, keep going as we // may have authorization on downstream scopes. Or, if they've not // authenticated, still process in case u_anon has permissions. if (authResults.Error == handlers.ForbiddenError() || authResults.Error == handlers.UnauthenticatedError()) && req.GetRecursive() && authResults.AuthenticationFinished { } else { return nil, authResults.Error } } var err error var authzScopes map[string]*scopes.ScopeInfo if req.GetRecursive() { authzScopes, err = authResults.ScopesAuthorizedForList(ctx, req.GetScopeId(), resource.Target) } else { authzScopes = map[string]*scopes.ScopeInfo{authResults.Scope.Id: authResults.Scope} } if err != nil { return nil, err } // Get all user permissions for the requested scope(s). userPerms := authResults.ACL().ListPermissions(authzScopes, resource.Target, IdActions, authResults.UserId) if len(userPerms) == 0 { return &pbs.ListTargetsResponse{ ResponseType: "complete", SortBy: "created_time", SortDir: "desc", }, nil } pageSize := int(s.maxPageSize) // Use the requested page size only if it is smaller than // the configured max. if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize { pageSize = int(req.GetPageSize()) } var filterItemFn func(ctx context.Context, item target.Target) (bool, error) switch { case req.GetFilter() != "": // Only use a filter if we need to filter, err := handlers.NewFilter(ctx, req.GetFilter()) if err != nil { return nil, err } // TODO: replace the need for this function with some way to convert the `filter` // to a domain type. This would allow filtering to happen in the domain, and we could // remove this callback altogether. filterItemFn = func(ctx context.Context, item target.Target) (bool, error) { pbItem, err := toProto(ctx, item, newOutputOpts(ctx, item, authResults, authzScopes)...) if err != nil { return false, err } filterable, err := subtypes.Filterable(ctx, pbItem) if err != nil { return false, err } return filter.Match(filterable), nil } default: filterItemFn = func(ctx context.Context, item target.Target) (bool, error) { return true, nil } } repo, err := s.repoFn(target.WithPermissions(userPerms)) if err != nil { return nil, err } grantsHash, err := authResults.GrantsHash(ctx) if err != nil { return nil, err } var listResp *pagination.ListResponse[target.Target] var sortBy string if req.GetListToken() == "" { sortBy = "created_time" listResp, err = target.List(ctx, grantsHash, pageSize, filterItemFn, repo) if err != nil { return nil, err } } else { listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.Target, grantsHash) if err != nil { return nil, err } switch st := listToken.Subtype.(type) { case *listtoken.PaginationToken: sortBy = "created_time" listResp, err = target.ListPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo) if err != nil { return nil, err } case *listtoken.StartRefreshToken: sortBy = "updated_time" listResp, err = target.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo) if err != nil { return nil, err } case *listtoken.RefreshToken: sortBy = "updated_time" listResp, err = target.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo) if err != nil { return nil, err } default: return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st) } } finalItems := make([]*pb.Target, 0, len(listResp.Items)) for _, item := range listResp.Items { item, err := toProto(ctx, item, newOutputOpts(ctx, item, authResults, authzScopes)...) if err != nil { return nil, err } finalItems = append(finalItems, item) } respType := "delta" if listResp.CompleteListing { respType = "complete" } resp := &pbs.ListTargetsResponse{ Items: finalItems, EstItemCount: uint32(listResp.EstimatedItemCount), RemovedIds: listResp.DeletedIds, ResponseType: respType, SortBy: sortBy, SortDir: "desc", } if listResp.ListToken != nil { resp.ListToken, err = handlers.MarshalListToken(ctx, listResp.ListToken, pbs.ResourceType_RESOURCE_TYPE_TARGET) if err != nil { return nil, err } } return resp, nil } // GetTarget implements the interface pbs.TargetServiceServer. func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs.GetTargetResponse, error) { const op = "targets.(Service).GetTarget" if err := validateGetRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.Read) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.GetTargetResponse{Item: item}, nil } // CreateTarget implements the interface pbs.TargetServiceServer. func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest) (*pbs.CreateTargetResponse, error) { const op = "targets.(Service).CreateTarget" if err := validateCreateRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetItem().GetScopeId(), action.Create) if authResults.Error != nil { return nil, authResults.Error } for _, a := range req.GetItem().WithAliases { authResults := s.aliasCreateAuthResult(ctx, a.GetScopeId()) if authResults.Error != nil { return nil, authResults.Error } } t, ts, cl, err := s.createInRepo(ctx, req.GetItem()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.CreateTargetResponse{Item: item, Uri: fmt.Sprintf("targets/%s", item.GetId())}, nil } // UpdateTarget implements the interface pbs.TargetServiceServer. func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest) (*pbs.UpdateTargetResponse, error) { const op = "targets.(Service).UpdateTarget" if err := validateUpdateRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.Update) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.UpdateTargetResponse{Item: item}, nil } // DeleteTarget implements the interface pbs.TargetServiceServer. func (s Service) DeleteTarget(ctx context.Context, req *pbs.DeleteTargetRequest) (*pbs.DeleteTargetResponse, error) { const op = "targets.(Service).DeleteTarget" if err := validateDeleteRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.Delete) if authResults.Error != nil { return nil, authResults.Error } _, err := s.deleteFromRepo(ctx, req.GetId()) if err != nil { return nil, err } return nil, nil } // AddTargetHostSources implements the interface pbs.TargetServiceServer. func (s Service) AddTargetHostSources(ctx context.Context, req *pbs.AddTargetHostSourcesRequest) (*pbs.AddTargetHostSourcesResponse, error) { const op = "targets.(Service).AddTargetHostSources" if err := validateAddHostSourcesRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.AddHostSources) if authResults.Error != nil { return nil, authResults.Error } t, err := s.addHostSourcesInRepo(ctx, req.GetId(), req.GetHostSourceIds(), req.GetVersion()) if err != nil { return nil, err } ts := t.GetHostSources() cl := t.GetCredentialSources() outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.AddTargetHostSourcesResponse{Item: item}, nil } // SetTargetHostSources implements the interface pbs.TargetServiceServer. func (s Service) SetTargetHostSources(ctx context.Context, req *pbs.SetTargetHostSourcesRequest) (*pbs.SetTargetHostSourcesResponse, error) { const op = "targets.(Service).SetTargetHostSources" if err := validateSetHostSourcesRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.SetHostSources) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.setHostSourcesInRepo(ctx, req.GetId(), req.GetHostSourceIds(), req.GetVersion()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.SetTargetHostSourcesResponse{Item: item}, nil } // RemoveTargetHostSources implements the interface pbs.TargetServiceServer. func (s Service) RemoveTargetHostSources(ctx context.Context, req *pbs.RemoveTargetHostSourcesRequest) (*pbs.RemoveTargetHostSourcesResponse, error) { const op = "targets.(Service).RemoveTargetHostSources" if err := validateRemoveHostSourcesRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.RemoveHostSources) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.removeHostSourcesInRepo(ctx, req.GetId(), req.GetHostSourceIds(), req.GetVersion()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.RemoveTargetHostSourcesResponse{Item: item}, nil } // AddTargetCredentialSources implements the interface pbs.TargetServiceServer. func (s Service) AddTargetCredentialSources(ctx context.Context, req *pbs.AddTargetCredentialSourcesRequest) (*pbs.AddTargetCredentialSourcesResponse, error) { const op = "targets.(Service).AddTargetCredentialSources" if err := validateAddCredentialSourcesRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.AddCredentialSources) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.addCredentialSourcesInRepo(ctx, req.GetId(), req.GetBrokeredCredentialSourceIds(), req.GetInjectedApplicationCredentialSourceIds(), req.GetVersion()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.AddTargetCredentialSourcesResponse{Item: item}, nil } // SetTargetCredentialSources implements the interface pbs.TargetServiceServer. func (s Service) SetTargetCredentialSources(ctx context.Context, req *pbs.SetTargetCredentialSourcesRequest) (*pbs.SetTargetCredentialSourcesResponse, error) { const op = "targets.(Service).SetTargetCredentialSources" if err := validateSetCredentialSourcesRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.SetCredentialSources) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.setCredentialSourcesInRepo(ctx, req.GetId(), req.GetBrokeredCredentialSourceIds(), req.GetInjectedApplicationCredentialSourceIds(), req.GetVersion()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.SetTargetCredentialSourcesResponse{Item: item}, nil } // RemoveTargetCredentialSources implements the interface pbs.TargetServiceServer. func (s Service) RemoveTargetCredentialSources(ctx context.Context, req *pbs.RemoveTargetCredentialSourcesRequest) (*pbs.RemoveTargetCredentialSourcesResponse, error) { const op = "targets.(Service).RemoveTargetCredentialSources" if err := validateRemoveCredentialSourcesRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.RemoveCredentialSources) if authResults.Error != nil { return nil, authResults.Error } t, ts, cl, err := s.removeCredentialSourcesInRepo(ctx, req.GetId(), req.GetBrokeredCredentialSourceIds(), req.GetInjectedApplicationCredentialSourceIds(), req.GetVersion()) if err != nil { return nil, err } outputFields, ok := requests.OutputFields(ctx) if !ok { return nil, errors.New(ctx, errors.Internal, op, "no request context found") } outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } t.SetHostSources(ts) t.SetCredentialSources(cl) item, err := toProto(ctx, t, outputOpts...) if err != nil { return nil, err } return &pbs.RemoveTargetCredentialSourcesResponse{Item: item}, nil } func NoSessionRecording(context.Context, intglobals.ControllerExtension, *kms.Kms, target.Target, *session.Session, string) (string, error) { return "", nil } func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSessionRequest) (_ *pbs.AuthorizeSessionResponse, retErr error) { const op = "targets.(Service).AuthorizeSession" if err := ValidateLicenseFn(ctx); err != nil { return nil, err } if ctxAlias := alias.FromContext(ctx); ctxAlias != nil { a, err := s.resolveAlias(ctx, ctxAlias.PublicId) if err != nil { return nil, errors.Wrap(ctx, err, op) } if a.HostId != "" { if req.GetHostId() != "" && req.GetHostId() != a.HostId { return nil, handlers.InvalidArgumentErrorf("Errors in provided fields.", map[string]string{ "host_id": "The host id specified in the request does not match the one provided by the alias. Consider omitting the host id in the request.", }) } req.HostId = a.HostId } } if err := validateAuthorizeSessionRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.AuthorizeSession, target.WithName(req.GetName()), target.WithProjectId(req.GetScopeId()), target.WithProjectName(req.GetScopeName()), ) if authResults.Error != nil { return nil, authResults.Error } correlationId, ok := event.CorrelationIdFromContext(ctx) if !ok { return nil, stderrors.New("authorize session: missing correlation id") } if authResults.RoundTripValue == nil { return nil, stderrors.New("authorize session: expected to get a target back from auth results") } roundTripTarget, ok := authResults.RoundTripValue.(target.Target) if !ok { return nil, stderrors.New("authorize session: round tripped auth results value is not a target") } if roundTripTarget == nil { return nil, stderrors.New("authorize session: round tripped target is nil") } // This could happen if, say, u_recovery was used or u_anon was granted. But // don't allow it. It's one thing if grants give access to resources within // Boundary, even if those could eventually be used to provide an unintended // user access to a remote system. It's quite another to enable anonymous // access directly to a remote system. // // Note that even if u_anon or u_auth are given grants we can still validate // a token! So this is just checking that a valid token was provided. The // actual reality of this works out to excluding: // // * True anonymous access (no token provided and u_anon) // // * u_recovery access (which is fine, recovery is meant for recovering // system state, no real reason to allow it to then connect to systems) if authResults.AuthTokenId == "" { return nil, handlers.ForbiddenError() } if roundTripTarget.GetDefaultPort() == 0 { return nil, handlers.ConflictErrorf("Target does not have default port defined.") } // Get the target information repo, err := s.repoFn() if err != nil { return nil, err } t, err := repo.LookupTarget(ctx, roundTripTarget.GetPublicId()) if err != nil { if errors.IsNotFoundError(err) { return nil, handlers.NotFoundErrorf("Target %q not found.", roundTripTarget.GetPublicId()) } return nil, err } if t == nil { return nil, handlers.NotFoundErrorf("Target %q not found.", roundTripTarget.GetPublicId()) } hostSources := t.GetHostSources() credSources := t.GetCredentialSources() if len(credSources) > 0 { if err := validateCredentialSourcesFn(ctx, t.GetType(), credSources); err != nil { return nil, err } } // Instantiate some repos sessionRepo, err := s.sessionRepoFn() if err != nil { return nil, err } serversRepo, err := s.serversRepoFn() if err != nil { return nil, err } p := strconv.FormatUint(uint64(t.GetDefaultPort()), 10) var h, hostId, hostSetId string switch { case t.GetAddress() != "": h = t.GetAddress() default: requestedId := req.GetHostId() staticHostRepo, err := s.staticHostRepoFn() if err != nil { return nil, err } pluginHostRepo, err := s.pluginHostRepoFn() if err != nil { return nil, err } var pluginHostSetIds []string var endpoints []*host.Endpoint for _, hSource := range hostSources { hsId := hSource.Id() switch globals.ResourceInfoFromPrefix(hsId).Subtype { case static.Subtype: eps, err := staticHostRepo.Endpoints(ctx, hsId) if err != nil { return nil, err } endpoints = append(endpoints, eps...) default: // Batch the plugin host set ids since each round trip to the plugin // has the potential to be expensive. pluginHostSetIds = append(pluginHostSetIds, hsId) } } if len(pluginHostSetIds) > 0 { eps, err := pluginHostRepo.Endpoints(ctx, pluginHostSetIds) if err != nil { return nil, err } endpoints = append(endpoints, eps...) } if len(endpoints) == 0 { return nil, handlers.NotFoundErrorf("No host sources or address found for given target.") } var chosenEndpoint *host.Endpoint if requestedId != "" { for _, ep := range endpoints { if ep.HostId == requestedId { chosenEndpoint = ep } } if chosenEndpoint == nil { // We didn't find it return nil, handlers.InvalidArgumentErrorf( "Errors in provided fields.", map[string]string{ "host_id": "The requested host id is not available.", }) } } if chosenEndpoint == nil { chosenEndpoint = endpoints[rand.Intn(len(endpoints))] } hostId = chosenEndpoint.HostId hostSetId = chosenEndpoint.SetId h = chosenEndpoint.Address } if h == "" { return nil, handlers.ApiErrorWithCodeAndMessage( codes.FailedPrecondition, "No host was discovered after checking target address and host sources.") } // Ensure we don't have a port from the address _, err = util.ParseAddress(ctx, h) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error when parsing the chosen endpoint host address")) } // Generate the endpoint URL endpointUrl := &url.URL{ Scheme: t.GetType().String(), Host: net.JoinHostPort(h, p), } // Get workers and filter down to ones that can service this request selectedWorkers, protoWorkerId, err := serversRepo.SelectSessionWorkers( ctx, time.Duration(s.workerRPCGracePeriod.Load()), t, h, s.controllerExt, StorageBucketFilterCredIdFn, s.downstreams, ) if err != nil { return nil, err } if len(selectedWorkers) == 0 { return nil, handlers.ApiErrorWithCodeAndMessage( codes.FailedPrecondition, "No workers are available to handle this session.") } // Randomize the workers rand.Shuffle(len(selectedWorkers), func(i, j int) { selectedWorkers[i], selectedWorkers[j] = selectedWorkers[j], selectedWorkers[i] }) var vaultReqs []credential.Request var staticIds []string var dynCreds []*session.DynamicCredential var staticCreds []*session.StaticCredential for _, cs := range credSources { switch cs.Type() { case target.LibraryCredentialSourceType: vaultReqs = append(vaultReqs, credential.Request{ SourceId: cs.Id(), Purpose: cs.CredentialPurpose(), }) dynCreds = append(dynCreds, session.NewDynamicCredential(cs.Id(), cs.CredentialPurpose())) case target.StaticCredentialSourceType: staticIds = append(staticIds, cs.Id()) staticCreds = append(staticCreds, session.NewStaticCredential(cs.Id(), cs.CredentialPurpose())) } } expTime := timestamppb.Now() expTime.Seconds += int64(t.GetSessionMaxSeconds()) sessionComposition := session.ComposedOf{ UserId: authResults.UserId, HostId: hostId, TargetId: t.GetPublicId(), HostSetId: hostSetId, AuthTokenId: authResults.AuthTokenId, ProjectId: authResults.Scope.Id, Endpoint: endpointUrl.String(), ExpirationTime: ×tamp.Timestamp{Timestamp: expTime}, ConnectionLimit: t.GetSessionConnectionLimit(), WorkerFilter: t.GetWorkerFilter(), EgressWorkerFilter: t.GetEgressWorkerFilter(), IngressWorkerFilter: t.GetIngressWorkerFilter(), DynamicCredentials: dynCreds, StaticCredentials: staticCreds, CorrelationId: correlationId, ProtocolWorkerId: protoWorkerId, } sess, err := session.New(ctx, sessionComposition) if err != nil { return nil, err } wrapper, err := s.kmsCache.GetWrapper(ctx, authResults.Scope.Id, kms.KeyPurposeSessions) if err != nil { return nil, err } workerAddresses := make([]string, 0, len(selectedWorkers)) for _, sw := range selectedWorkers { workerAddresses = append(workerAddresses, sw.Address) } sess, err = sessionRepo.CreateSession(ctx, wrapper, sess, workerAddresses) if err != nil { return nil, err } defer func() { if retErr != nil { // Delete created session in case of errors. // Use new context for deletion in case error is because of context cancellation. deleteCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := sessionRepo.DeleteSession(deleteCtx, sess.PublicId) retErr = stderrors.Join(retErr, err) } }() subtype := target.SubtypeFromId(t.GetPublicId()) subtypeEntry, err := subtypeRegistry.get(subtype) if err != nil { return nil, errors.Wrap(ctx, err, op) } if err := subtypeEntry.validateSessionStateFunc(ctx, sess); err != nil { return nil, errors.Wrap(ctx, err, op) } var dynamic []credential.Dynamic var staticCredsById map[string]credential.Static if len(vaultReqs) > 0 { credRepo, err := s.vaultCredRepoFn() if err != nil { return nil, errors.Wrap(ctx, err, op) } dynamic, err = credRepo.Issue(ctx, sess.GetPublicId(), vaultReqs, credential.WithTemplateData(authResults.UserData)) if err != nil { return nil, errors.Wrap(ctx, err, op) } defer func() { if retErr != nil { // Revoke issued credentials in case of errors. // Use new context for deletion in case error is because of context cancellation. deleteCtx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() err := credRepo.Revoke(deleteCtx, sess.PublicId) retErr = stderrors.Join(retErr, err) // This leaves the credential in a state which will allow it to be cleaned up // by the periodic credential cleanup job. } }() } if len(staticIds) > 0 { credRepo, err := s.staticCredRepoFn() if err != nil { return nil, errors.Wrap(ctx, err, op) } // Remove duplicate requests staticIds = strutil.RemoveDuplicates(staticIds, false) creds, err := credRepo.Retrieve(ctx, t.GetProjectId(), staticIds) if err != nil { return nil, errors.Wrap(ctx, err, op) } staticCredsById = make(map[string]credential.Static) for _, c := range creds { staticCredsById[c.GetPublicId()] = c } } var creds []*pb.SessionCredential var workerCreds []session.Credential for _, cred := range dynamic { switch cred.Purpose() { case credential.InjectedApplicationPurpose: c, err := dynamicToWorkerCredential(ctx, cred) if err != nil { return nil, errors.Wrap(ctx, err, op) } workerCreds = append(workerCreds, c) case credential.BrokeredPurpose: c, err := dynamicToSessionCredential(ctx, cred) if err != nil { return nil, errors.Wrap(ctx, err, op) } creds = append(creds, c) default: return nil, errors.New(ctx, errors.Unknown, op, fmt.Sprintf("unsupported credential purpose %s", cred.Purpose())) } } for _, sc := range staticCreds { switch sc.CredentialPurpose { case string(credential.InjectedApplicationPurpose): c, err := staticToWorkerCredential(ctx, staticCredsById[sc.CredentialStaticId]) if err != nil { return nil, errors.Wrap(ctx, err, op) } workerCreds = append(workerCreds, c) case string(credential.BrokeredPurpose): c, err := staticToSessionCredential(ctx, staticCredsById[sc.CredentialStaticId]) if err != nil { return nil, errors.Wrap(ctx, err, op) } creds = append(creds, c) default: return nil, errors.New(ctx, errors.Unknown, op, fmt.Sprintf("unsupported credential purpose %s", sc.CredentialPurpose)) } } if len(workerCreds) > 0 { // store credentials in repo, worker will request creds when a connection is established // These credentials are deleted with the session, so nothing extra to cleanup in case of errors. err = sessionRepo.AddSessionCredentials(ctx, sess.ProjectId, sess.PublicId, workerCreds) if err != nil { return nil, errors.Wrap(ctx, err, op) } } // this is an edge case issue where the hostId cannot be empty when trying to execute an ssh connection // on a tcp target type. By setting the hostId to the targetId value, this will enable support of previous // boundary cli versions. if fm.SupportsFeature(fm.Binary, fm.UseTargetIdForHostId) && t.GetAddress() != "" { hostId = t.GetPublicId() } workerInfos := make([]*pb.WorkerInfo, 0, len(selectedWorkers)) for _, sw := range selectedWorkers { workerInfos = append(workerInfos, &pb.WorkerInfo{Address: sw.Address}) } sad := &pb.SessionAuthorizationData{ SessionId: sess.PublicId, TargetId: t.GetPublicId(), Scope: authResults.Scope, CreatedTime: sess.CreateTime.GetTimestamp(), Expiration: sess.ExpirationTime.GetTimestamp(), EndpointPort: t.GetDefaultPort(), Type: t.GetType().String(), Certificate: sess.Certificate, PrivateKey: sess.CertificatePrivateKey, HostId: hostId, Endpoint: endpointUrl.String(), WorkerInfo: workerInfos, ConnectionLimit: t.GetSessionConnectionLimit(), DefaultClientPort: t.GetDefaultClientPort(), } marshaledSad, err := proto.Marshal(sad) if err != nil { return nil, err } encodedMarshaledSad := base58.FastBase58Encoding(marshaledSad) ret := &pb.SessionAuthorization{ SessionId: sess.PublicId, TargetId: t.GetPublicId(), Scope: authResults.Scope, CreatedTime: sess.CreateTime.GetTimestamp(), Expiration: sess.ExpirationTime.GetTimestamp(), EndpointPort: t.GetDefaultPort(), Type: t.GetType().String(), AuthorizationToken: encodedMarshaledSad, UserId: authResults.UserId, HostId: hostId, HostSetId: hostSetId, Endpoint: endpointUrl.String(), Credentials: creds, ConnectionLimit: t.GetSessionConnectionLimit(), } ret.SessionRecordingId, err = SessionRecordingFn( ctx, s.controllerExt, s.kmsCache, t, sess, protoWorkerId, ) if err != nil { // Errors here will automatically delete the session and associated resources // using deferred statements above. return nil, errors.Wrap(ctx, err, op) } return &pbs.AuthorizeSessionResponse{Item: ret}, nil } func (s Service) getFromRepo(ctx context.Context, id string) (target.Target, []target.HostSource, []target.CredentialSource, error) { repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } u, err := repo.LookupTarget(ctx, id) if err != nil { if errors.IsNotFoundError(err) { return nil, nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id) } return nil, nil, nil, err } hs := u.GetHostSources() cl := u.GetCredentialSources() if u == nil { return nil, nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id) } return u, hs, cl, nil } // resolveAlias returns the alias resource with the specified public id. func (s Service) resolveAlias(ctx context.Context, id string) (*talias.Alias, error) { const op = "targets.(Service).resolveAlias" if id == "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "alias id is empty") } r, err := s.aliasRepoFn() if err != nil { return nil, errors.Wrap(ctx, err, op) } a, err := r.LookupAlias(ctx, id) if err != nil { return nil, errors.Wrap(ctx, err, op) } if a == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.NotFound, "alias with id %q not found", id) } return a, nil } func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).createInRepo" opts := []target.Option{target.WithName(item.GetName().GetValue())} if item.GetDescription() != nil { opts = append(opts, target.WithDescription(item.GetDescription().GetValue())) } if item.GetSessionMaxSeconds() != nil { opts = append(opts, target.WithSessionMaxSeconds(item.GetSessionMaxSeconds().GetValue())) } if item.GetSessionConnectionLimit() != nil { opts = append(opts, target.WithSessionConnectionLimit(item.GetSessionConnectionLimit().GetValue())) } if item.GetEgressWorkerFilter() != nil { opts = append(opts, target.WithEgressWorkerFilter(item.GetEgressWorkerFilter().GetValue())) } if item.GetIngressWorkerFilter() != nil { opts = append(opts, target.WithIngressWorkerFilter(item.GetIngressWorkerFilter().GetValue())) } if item.GetAddress() != nil { opts = append(opts, target.WithAddress(strings.TrimSpace(item.GetAddress().GetValue()))) } attr, err := subtypeRegistry.newAttribute(target.SubtypeFromType(item.GetType()), item.GetAttrs()) if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, err.Error()) } opts = append(opts, attr.Options()...) u, err := target.New(ctx, target.SubtypeFromType(item.GetType()), item.GetScopeId(), opts...) if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err) } repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } createOptions := []target.Option{} if len(item.GetWithAliases()) > 0 { writeAliases := make([]*talias.Alias, 0, len(item.GetWithAliases())) for _, a := range item.GetWithAliases() { na, err := talias.NewAlias(ctx, a.GetScopeId(), a.GetValue(), talias.WithHostId(a.GetAttributes().GetAuthorizeSessionArguments().GetHostId())) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("converting from proto to storage alias")) } writeAliases = append(writeAliases, na) } createOptions = append(createOptions, target.WithAliases(writeAliases)) } out, err := repo.CreateTarget(ctx, u, createOptions...) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target")) } hs := out.GetHostSources() cl := out.GetCredentialSources() if out == nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.") } return out, hs, cl, nil } func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).updateInRepo" var dbMask []string var opts []target.Option if desc := item.GetDescription(); desc != nil { opts = append(opts, target.WithDescription(desc.GetValue())) } if name := item.GetName(); name != nil { opts = append(opts, target.WithName(name.GetValue())) } if item.GetSessionMaxSeconds() != nil { opts = append(opts, target.WithSessionMaxSeconds(item.GetSessionMaxSeconds().GetValue())) } if item.GetSessionConnectionLimit() != nil { opts = append(opts, target.WithSessionConnectionLimit(item.GetSessionConnectionLimit().GetValue())) } // worker_filter is deprecated, but we allow users who have migrated with a worker_filter value to update it. if workerFilter := item.GetWorkerFilter(); workerFilter != nil { opts = append(opts, target.WithWorkerFilter(item.GetWorkerFilter().GetValue())) } if egressFilter := item.GetEgressWorkerFilter(); egressFilter != nil { opts = append(opts, target.WithEgressWorkerFilter(item.GetEgressWorkerFilter().GetValue())) } if ingressFilter := item.GetIngressWorkerFilter(); ingressFilter != nil { opts = append(opts, target.WithIngressWorkerFilter(item.GetIngressWorkerFilter().GetValue())) } if item.GetAddress() != nil { dbMask = append(dbMask, "Address") opts = append(opts, target.WithAddress(strings.TrimSpace(item.GetAddress().GetValue()))) } subtype := target.SubtypeFromId(id) attr, err := subtypeRegistry.newAttribute(subtype, item.GetAttrs()) if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, err.Error()) } opts = append(opts, attr.Options()...) version := item.GetVersion() u, err := target.New(ctx, subtype, scopeId, opts...) if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err) } if err := u.SetPublicId(ctx, id); err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set target id: %v.", err) } maskManager, err := subtypeRegistry.maskManager(subtype) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update target")) } dbMask = append(dbMask, maskManager.Translate(mask)...) if len(dbMask) == 0 { return nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."}) } repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } out, rowsUpdated, err := repo.UpdateTarget(ctx, u, version, dbMask, opts...) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update target")) } hs := out.GetHostSources() cl := out.GetCredentialSources() if rowsUpdated == 0 { return nil, nil, nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id) } return out, hs, cl, nil } func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { const op = "targets.(Service).deleteFromRepo" repo, err := s.repoFn() if err != nil { return false, err } rows, err := repo.DeleteTarget(ctx, id) if err != nil { if errors.IsNotFoundError(err) { return false, nil } return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target")) } return rows > 0, nil } func (s Service) addHostSourcesInRepo(ctx context.Context, targetId string, hostSourceIds []string, version uint32) (target.Target, error) { repo, err := s.repoFn() if err != nil { return nil, err } out, err := repo.AddTargetHostSources(ctx, targetId, version, strutil.RemoveDuplicates(hostSourceIds, false)) if err != nil { var internalErr *errors.Err if stderrors.As(err, &internalErr) && internalErr.Code == errors.Conflict { // The conflict error is surfaced directly as it's correctly // converted all the way down to the HTTP status. return nil, err } // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Failed to add target host sources") } if out == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sources to it.") } return out, nil } func (s Service) setHostSourcesInRepo(ctx context.Context, targetId string, hostSourceIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).setSourcesInRepo" repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } _, _, _, err = repo.SetTargetHostSources(ctx, targetId, version, strutil.RemoveDuplicates(hostSourceIds, false)) if err != nil { var internalErr *errors.Err if stderrors.As(err, &internalErr) && internalErr.Code == errors.Conflict { // The conflict error is surfaced directly as it's correctly // converted all the way down to the HTTP status. return nil, nil, nil, err } // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Failed to set target host sources") } out, err := repo.LookupTarget(ctx, targetId) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up target after setting host sources")) } hs := out.GetHostSources() cl := out.GetCredentialSources() if out == nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sources for it.") } return out, hs, cl, nil } func (s Service) removeHostSourcesInRepo(ctx context.Context, targetId string, hostSourceIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).removeHostSourcesInRepo" repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } _, err = repo.DeleteTargetHostSources(ctx, targetId, version, strutil.RemoveDuplicates(hostSourceIds, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sources from target: %v.", err) } out, err := repo.LookupTarget(ctx, targetId) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up target after removing host sources")) } hs := out.GetHostSources() cl := out.GetCredentialSources() if out == nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sources from it.") } return out, hs, cl, nil } func (s Service) addCredentialSourcesInRepo(ctx context.Context, targetId string, brokeredIds []string, injectedAppIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } var creds target.CredentialSources if len(brokeredIds) > 0 { creds.BrokeredCredentialIds = strutil.RemoveDuplicates(brokeredIds, false) } if len(injectedAppIds) > 0 { creds.InjectedApplicationCredentialIds = strutil.RemoveDuplicates(injectedAppIds, false) } out, err := repo.AddTargetCredentialSources(ctx, targetId, version, creds) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add credential sources to target: %v.", err) } hs := out.GetHostSources() credSources := out.GetCredentialSources() if out == nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding credential sources to it.") } return out, hs, credSources, nil } func (s Service) setCredentialSourcesInRepo(ctx context.Context, targetId string, brokeredIds []string, injectedAppIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).setCredentialSourcesInRepo" repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } var ids target.CredentialSources if len(brokeredIds) > 0 { ids.BrokeredCredentialIds = strutil.RemoveDuplicates(brokeredIds, false) } if len(injectedAppIds) > 0 { ids.InjectedApplicationCredentialIds = strutil.RemoveDuplicates(injectedAppIds, false) } _, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, ids) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) } out, err := repo.LookupTarget(ctx, targetId) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up target after setting credential sources")) } hs := out.GetHostSources() credSources := out.GetCredentialSources() if out == nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting credential sources for it.") } return out, hs, credSources, nil } func (s Service) removeCredentialSourcesInRepo(ctx context.Context, targetId string, brokeredIds []string, injectedAppIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).removeCredentialSourcesInRepo" repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } var ids target.CredentialSources if len(brokeredIds) > 0 { ids.BrokeredCredentialIds = strutil.RemoveDuplicates(brokeredIds, false) } if len(injectedAppIds) > 0 { ids.InjectedApplicationCredentialIds = strutil.RemoveDuplicates(injectedAppIds, false) } _, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, ids) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove credential sources from target: %v.", err) } out, err := repo.LookupTarget(ctx, targetId) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up target after removing credential sources")) } hs := out.GetHostSources() credSources := out.GetCredentialSources() if out == nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing credential sources from it.") } return out, hs, credSources, nil } // aliasCreateAuthResult verifies authorization for creating an alias func (s Service) aliasCreateAuthResult(ctx context.Context, parentId string) auth.VerifyResults { res := auth.VerifyResults{} a := action.Create opts := []auth.Option{auth.WithType(resource.Alias), auth.WithAction(a)} iamRepo, err := s.iamRepoFn() if err != nil { res.Error = err return res } scp, err := iamRepo.LookupScope(ctx, parentId) if err != nil { res.Error = err return res } if scp == nil { res.Error = handlers.NotFoundError() return res } opts = append(opts, auth.WithScopeId(parentId)) ret := auth.Verify(ctx, opts...) return ret } func (s Service) authResult(ctx context.Context, id string, a action.Type, lookupOpt ...target.Option) auth.VerifyResults { res := auth.VerifyResults{} var parentId string var t target.Target opts := []auth.Option{auth.WithType(resource.Target), auth.WithAction(a)} switch a { case action.List, action.Create: parentId = id iamRepo, err := s.iamRepoFn() if err != nil { res.Error = err return res } scp, err := iamRepo.LookupScope(ctx, parentId) if err != nil { res.Error = err return res } if scp == nil { res.Error = handlers.NotFoundError() return res } default: repo, err := s.repoFn() if err != nil { res.Error = err return res } t, err = repo.LookupTarget(ctx, id, lookupOpt...) if err != nil { // TODO: Fix this with new/better error handling if strings.Contains(err.Error(), "more than one row returned by a subquery") { res.Error = handlers.ApiErrorWithCodeAndMessage(codes.FailedPrecondition, "Scope name is ambiguous (matches more than one scope), use scope ID with target name instead, or use target ID.") } else { res.Error = err } return res } if t == nil { res.Error = handlers.NotFoundError() return res } id = t.GetPublicId() parentId = t.GetProjectId() opts = append(opts, auth.WithId(id)) } opts = append(opts, auth.WithScopeId(parentId)) ret := auth.Verify(ctx, opts...) ret.RoundTripValue = t return ret } func toProto(ctx context.Context, in target.Target, opt ...handlers.Option) (*pb.Target, error) { const op = "target_service.toProto" opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building target proto") } outputFields := *opts.WithOutputFields hostSources := in.GetHostSources() credSources := in.GetCredentialSources() aliases := in.GetAliases() out := pb.Target{} if outputFields.Has(globals.IdField) { out.Id = in.GetPublicId() } if outputFields.Has(globals.ScopeIdField) { out.ScopeId = in.GetProjectId() } if outputFields.Has(globals.TypeField) { out.Type = in.GetType().String() } if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { out.Description = wrapperspb.String(in.GetDescription()) } if outputFields.Has(globals.NameField) && in.GetName() != "" { out.Name = wrapperspb.String(in.GetName()) } if outputFields.Has(globals.CreatedTimeField) { out.CreatedTime = in.GetCreateTime().GetTimestamp() } if outputFields.Has(globals.UpdatedTimeField) { out.UpdatedTime = in.GetUpdateTime().GetTimestamp() } if outputFields.Has(globals.VersionField) { out.Version = in.GetVersion() } if outputFields.Has(globals.SessionMaxSecondsField) { out.SessionMaxSeconds = wrapperspb.UInt32(in.GetSessionMaxSeconds()) } if outputFields.Has(globals.SessionConnectionLimitField) { out.SessionConnectionLimit = wrapperspb.Int32(in.GetSessionConnectionLimit()) } if outputFields.Has(globals.WorkerFilterField) && in.GetWorkerFilter() != "" { out.WorkerFilter = wrapperspb.String(in.GetWorkerFilter()) } if outputFields.Has(globals.EgressWorkerFilterField) && in.GetEgressWorkerFilter() != "" { out.EgressWorkerFilter = wrapperspb.String(in.GetEgressWorkerFilter()) } if outputFields.Has(globals.IngressWorkerFilterField) && in.GetIngressWorkerFilter() != "" { out.IngressWorkerFilter = wrapperspb.String(in.GetIngressWorkerFilter()) } if outputFields.Has(globals.ScopeField) { out.Scope = opts.WithScope } if outputFields.Has(globals.AuthorizedActionsField) { out.AuthorizedActions = opts.WithAuthorizedActions } if outputFields.Has(globals.HostSourceIdsField) { for _, hs := range hostSources { out.HostSourceIds = append(out.HostSourceIds, hs.Id()) } } if outputFields.Has(globals.HostSourcesField) { for _, hs := range hostSources { out.HostSources = append(out.HostSources, &pb.HostSource{ Id: hs.Id(), HostCatalogId: hs.HostCatalogId(), }) } } if outputFields.Has(globals.AliasesField) { for _, a := range aliases { // Even though pb.Alias has more than just these 2 fields, we only // want to return these 2 fields to the client. Any more information // may be sharing more than the client should know. out.Aliases = append(out.Aliases, &pb.Alias{ Id: a.PublicId, Value: a.Value, }) } } if outputFields.Has(globals.AddressField) { out.Address = wrapperspb.String(in.GetAddress()) } var brokeredSources, injectedAppSources []*pb.CredentialSource var brokeredSourceIds, injectedAppSourceIds []string for _, cs := range credSources { switch cs.CredentialPurpose() { case credential.BrokeredPurpose: brokeredSourceIds = append(brokeredSourceIds, cs.Id()) brokeredSources = append(brokeredSources, &pb.CredentialSource{ Id: cs.Id(), CredentialStoreId: cs.CredentialStoreId(), }) case credential.InjectedApplicationPurpose: injectedAppSources = append(injectedAppSources, &pb.CredentialSource{ Id: cs.Id(), CredentialStoreId: cs.CredentialStoreId(), }) injectedAppSourceIds = append(injectedAppSourceIds, cs.Id()) default: return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unrecognized purpose %q for credential source on target", cs.CredentialPurpose())) } } if outputFields.Has(globals.BrokeredCredentialSourceIdsField) { out.BrokeredCredentialSourceIds = brokeredSourceIds } if outputFields.Has(globals.BrokeredCredentialSourcesField) { out.BrokeredCredentialSources = brokeredSources } if outputFields.Has(globals.InjectedApplicationCredentialSourceIdsField) { out.InjectedApplicationCredentialSourceIds = injectedAppSourceIds } if outputFields.Has(globals.InjectedApplicationCredentialSourcesField) { out.InjectedApplicationCredentialSources = injectedAppSources } if outputFields.Has(globals.AttributesField) { if err := subtypeRegistry.setAttributes(in.GetType(), in, &out); err != nil { return nil, errors.Wrap(ctx, err, op) } } return &out, nil } // A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce // requirements on the structure of the request. They verify that: // - The path passed in is correctly formatted // - All required parameters are set // - There are no conflicting parameters provided func validateGetRequest(req *pbs.GetTargetRequest) error { return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, target.Prefixes()...) } func validateCreateRequest(req *pbs.CreateTargetRequest) error { item := req.GetItem() return handlers.ValidateCreateRequest(item, func() map[string]string { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(item.GetScopeId()), scope.Project.Prefix()) { badFields[globals.ScopeIdField] = "This field is required to have a properly formatted project scope id." } if item.GetName() == nil || item.GetName().GetValue() == "" { badFields[globals.NameField] = "This field is required." } for _, a := range item.GetWithAliases() { pa := proto.Clone(a).(*pb.Alias) pa.Value = "" pa.ScopeId = "" pa.Attributes = nil if !proto.Equal(pa, &pb.Alias{}) { badFields[globals.WithAliasesField] = "Included aliases can only specify a value, a scope id, and attributes." } } if item.GetSessionConnectionLimit() != nil { val := item.GetSessionConnectionLimit().GetValue() switch { case val == -1: case val > 0: default: badFields[globals.SessionConnectionLimitField] = "This must be -1 (unlimited) or greater than zero." } } if item.GetSessionMaxSeconds() != nil && item.GetSessionMaxSeconds().GetValue() == 0 { badFields[globals.SessionMaxSecondsField] = "This must be greater than zero." } if item.GetType() == "" { badFields[globals.TypeField] = "This is a required field." } else if target.SubtypeFromType(item.GetType()) == "" { badFields[globals.TypeField] = "Unknown type provided." } if workerFilter := item.GetWorkerFilter(); workerFilter != nil { badFields[globals.WorkerFilterField] = WorkerFilterDeprecationMessage } if egressFilter := item.GetEgressWorkerFilter(); egressFilter != nil { if _, err := bexpr.CreateEvaluator(egressFilter.GetValue()); err != nil { badFields[globals.EgressWorkerFilterField] = "Unable to successfully parse egress filter expression." } } if ingressFilter := item.GetIngressWorkerFilter(); ingressFilter != nil { err := ValidateIngressWorkerFilterFn(ingressFilter.GetValue()) if err != nil { badFields[globals.IngressWorkerFilterField] = err.Error() } } if address := item.GetAddress(); address != nil { if len(address.GetValue()) < static.MinHostAddressLength || len(address.GetValue()) > static.MaxHostAddressLength { badFields[globals.AddressField] = fmt.Sprintf("Address length must be between %d and %d characters.", static.MinHostAddressLength, static.MaxHostAddressLength) } _, _, err := net.SplitHostPort(address.GetValue()) switch { case err == nil: badFields[globals.AddressField] = "Address does not support a port." case strings.Contains(err.Error(), globals.MissingPortErrStr): default: badFields[globals.AddressField] = fmt.Sprintf("Error parsing address: %v.", err) } } subtype := target.SubtypeFromType(item.GetType()) _, err := subtypeRegistry.get(subtype) if err != nil { badFields[globals.TypeField] = "Unknown type provided." } else { a, err := subtypeRegistry.newAttribute(subtype, item.GetAttrs()) if err != nil { badFields[globals.AttributesField] = "Attribute fields do not match the expected format." } else { for k, v := range a.Vet() { badFields[k] = v } } } return badFields }) } func validateUpdateRequest(req *pbs.UpdateTargetRequest) error { item := req.GetItem() return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string { badFields := map[string]string{} paths := req.GetUpdateMask().GetPaths() if handlers.MaskContains(paths, globals.NameField) && item.GetName().GetValue() == "" { badFields[globals.NameField] = "This field cannot be set to empty." } if item.GetSessionConnectionLimit() != nil { val := item.GetSessionConnectionLimit().GetValue() switch { case val == -1: case val > 0: default: badFields[globals.SessionConnectionLimitField] = "This must be -1 (unlimited) or greater than zero." } } if item.GetSessionMaxSeconds() != nil && item.GetSessionMaxSeconds().GetValue() == 0 { badFields[globals.SessionMaxSecondsField] = "This must be greater than zero." } if len(item.GetWithAliases()) > 0 { badFields[globals.WithAliasesField] = "This field can only be set at target creation time." } // worker_filter is mutually exclusive from ingress and egress filter workerFilterFound := false if workerFilter := item.GetWorkerFilter(); workerFilter != nil { if _, err := bexpr.CreateEvaluator(workerFilter.GetValue()); err != nil { badFields[globals.WorkerFilterField] = "Unable to successfully parse filter expression." } workerFilterFound = true } if egressFilter := item.GetEgressWorkerFilter(); egressFilter != nil { if workerFilterFound { badFields[globals.EgressWorkerFilterField] = fmt.Sprintf("Cannot set %s and %s; they are mutually exclusive fields.", globals.WorkerFilterField, globals.EgressWorkerFilterField) } if _, err := bexpr.CreateEvaluator(egressFilter.GetValue()); err != nil { badFields[globals.EgressWorkerFilterField] = "Unable to successfully parse egress filter expression." } } if ingressFilter := item.GetIngressWorkerFilter(); ingressFilter != nil { if workerFilterFound { badFields[globals.IngressWorkerFilterField] = fmt.Sprintf("Cannot set %s and %s; they are mutually exclusive fields.", globals.WorkerFilterField, globals.IngressWorkerFilterField) } err := ValidateIngressWorkerFilterFn(ingressFilter.GetValue()) if err != nil { badFields[globals.IngressWorkerFilterField] = err.Error() } } if address := item.GetAddress(); address != nil { if len(address.GetValue()) < static.MinHostAddressLength || len(address.GetValue()) > static.MaxHostAddressLength { badFields[globals.AddressField] = fmt.Sprintf("Address length must be between %d and %d characters.", static.MinHostAddressLength, static.MaxHostAddressLength) } _, _, err := net.SplitHostPort(address.GetValue()) switch { case err == nil: badFields[globals.AddressField] = "Address does not support a port." case strings.Contains(err.Error(), globals.MissingPortErrStr): default: badFields[globals.AddressField] = fmt.Sprintf("Error parsing address: %v.", err) } } subtype := target.SubtypeFromId(req.GetId()) _, err := subtypeRegistry.get(subtype) if err != nil { badFields[globals.TypeField] = "Unknown type provided." } else { if item.GetType() != "" && target.SubtypeFromType(item.GetType()) != subtype { badFields[globals.TypeField] = "Cannot modify the resource type." } a, err := subtypeRegistry.newAttribute(subtype, item.GetAttrs()) if err != nil { badFields[globals.AttributesField] = "Attribute fields do not match the expected format." } else { for k, v := range a.VetForUpdate(paths) { badFields[k] = v } } } return badFields }, target.Prefixes()...) } func validateDeleteRequest(req *pbs.DeleteTargetRequest) error { return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, target.Prefixes()...) } func validateListRequest(ctx context.Context, req *pbs.ListTargetsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) && !req.GetRecursive() { badFields[globals.ScopeIdField] = "This field must be a valid project scope ID or the list operation must be recursive." } if _, err := handlers.NewFilter(ctx, req.GetFilter()); err != nil { badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err) } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) } return nil } func newOutputOpts(ctx context.Context, item target.Target, authResults auth.VerifyResults, authzScopes map[string]*scopes.ScopeInfo) []handlers.Option { pr := perms.Resource{Id: item.GetPublicId(), ScopeId: item.GetProjectId(), Type: resource.Target, ParentScopeId: authzScopes[item.GetProjectId()].GetParentScopeId()} outputFields := authResults.FetchOutputFields(pr, action.List).SelfOrDefaults(authResults.UserId) outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authzScopes[item.GetProjectId()])) } pr.ParentScopeId = authzScopes[item.GetProjectId()].GetParentScopeId() if outputFields.Has(globals.AuthorizedActionsField) { authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&pr)).Strings() outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } return outputOpts } func validateAddHostSourcesRequest(req *pbs.AddTargetHostSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { badFields[globals.VersionField] = "Required field." } if len(req.GetHostSourceIds()) == 0 { badFields[globals.HostSourceIdsField] = "Must be non-empty." } for _, id := range req.GetHostSourceIds() { if !handlers.ValidId(handlers.Id(id), globals.StaticHostSetPrefix, globals.PluginHostSetPrefix, globals.PluginHostSetPreviousPrefix) { badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id) break } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func validateSetHostSourcesRequest(req *pbs.SetTargetHostSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { badFields[globals.VersionField] = "Required field." } for _, id := range req.GetHostSourceIds() { if !handlers.ValidId(handlers.Id(id), globals.StaticHostSetPrefix, globals.PluginHostSetPrefix, globals.PluginHostSetPreviousPrefix) { badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id) break } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func validateRemoveHostSourcesRequest(req *pbs.RemoveTargetHostSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { badFields[globals.VersionField] = "Required field." } if len(req.GetHostSourceIds()) == 0 { badFields[globals.HostSourceIdsField] = "Must be non-empty." } for _, id := range req.GetHostSourceIds() { if !handlers.ValidId(handlers.Id(id), globals.StaticHostSetPrefix, globals.PluginHostSetPrefix, globals.PluginHostSetPreviousPrefix) { badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id) break } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { badFields[globals.VersionField] = "Required field." } if len(req.GetBrokeredCredentialSourceIds())+len(req.GetInjectedApplicationCredentialSourceIds()) == 0 { badFields[globals.BrokeredCredentialSourceIdsField] = "Brokered or Injected Application Credential Source IDs must be provided." badFields[globals.InjectedApplicationCredentialSourceIdsField] = "Brokered or Injected Application Credential Source IDs must be provided." } for _, cl := range req.GetBrokeredCredentialSourceIds() { if !handlers.ValidId(handlers.Id(cl), globals.VaultCredentialLibraryPrefix, globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix) { badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } for _, cl := range req.GetInjectedApplicationCredentialSourceIds() { if !handlers.ValidId(handlers.Id(cl), globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix, globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.SshPrivateKeyCredentialPrefix) { badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { badFields[globals.VersionField] = "Required field." } for _, cl := range req.GetBrokeredCredentialSourceIds() { if !handlers.ValidId(handlers.Id(cl), globals.VaultCredentialLibraryPrefix, globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix) { badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } for _, cl := range req.GetInjectedApplicationCredentialSourceIds() { if !handlers.ValidId(handlers.Id(cl), globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix, globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.SshPrivateKeyCredentialPrefix) { badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { badFields[globals.VersionField] = "Required field." } if len(req.GetBrokeredCredentialSourceIds())+len(req.GetInjectedApplicationCredentialSourceIds()) == 0 { badFields[globals.BrokeredCredentialSourceIdsField] = "Brokered or Injected Application Credential Source IDs must be provided." badFields[globals.InjectedApplicationCredentialSourceIdsField] = "Brokered or Injected Application Credential Source IDs must be provided." } for _, cl := range req.GetBrokeredCredentialSourceIds() { if !handlers.ValidId(handlers.Id(cl), globals.VaultCredentialLibraryPrefix, globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix) { badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } for _, cl := range req.GetInjectedApplicationCredentialSourceIds() { if !handlers.ValidId(handlers.Id(cl), globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix, globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix) { badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error { badFields := map[string]string{} nameEmpty := req.GetName() == "" scopeIdEmpty := req.GetScopeId() == "" scopeNameEmpty := req.GetScopeName() == "" if nameEmpty { if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if !scopeIdEmpty { badFields[globals.ScopeIdField] = "Scope ID provided when target name was empty." } if !scopeNameEmpty { badFields[globals.ScopeIdField] = "Scope name provided when target name was empty." } } else { if req.GetName() != req.GetId() { badFields[globals.NameField] = "Target name provided but does not match the given ID value from the URL." } switch { case scopeIdEmpty && scopeNameEmpty: badFields[globals.ScopeIdField] = "Scope ID or scope name must be provided when target name is used." badFields["scope_name"] = "Scope ID or scope name must be provided when target name is used." case !scopeIdEmpty && !scopeNameEmpty: badFields[globals.ScopeIdField] = "Scope ID and scope name cannot both be provided when target name is used." badFields["scope_name"] = "Scope ID and scope name cannot both be provided when target name is used." } } if req.GetHostId() != "" { switch globals.ResourceInfoFromPrefix(req.GetHostId()).Subtype { case static.Subtype, plugin.Subtype: default: badFields[globals.HostIdField] = "Incorrectly formatted identifier." } } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) } return nil } func noStorageBucket(_ context.Context, _ intglobals.ControllerExtension, _ string) (string, string, error) { return "", "", fmt.Errorf("not supported") } func noOpValidateLicense(ctx context.Context) error { return nil }