lib/adapter/adapters.go (175 lines of code) (raw):

// Copyright 2019 Google LLC // // 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 adapter allows the DAM to take actions. package adapter import ( "context" "fmt" "regexp" "strings" "time" "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/aws" /* copybara-comment: aws */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/clouds" /* copybara-comment: clouds */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/ga4gh" /* copybara-comment: ga4gh */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/globalflags" /* copybara-comment: globalflags */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/kms" /* copybara-comment: kms */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */ pb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/dam/v1" /* copybara-comment: go_proto */ ) const ( // AdapterDataType is the name of adapter file types. AdapterDataType = "adapter" ) // AggregateView defines an aggregated view. type AggregateView struct { Index int Res *pb.Resource View *pb.View } // Action provides inputs to action methods on adapters. type Action struct { Aggregates []*AggregateView ClientID string Config *pb.DamConfig GrantRole string Identity *ga4gh.Identity Issuer string MaxTTL time.Duration ResourceID string Resource *pb.Resource ServiceRole *pb.ServiceRole ServiceTemplate *pb.ServiceTemplate TTL time.Duration ViewID string View *pb.View Interface string TokenFormat string } // MintTokenResult is returned by the MintToken() method. type MintTokenResult struct { // A set of credential information like "account" and "access_token", or whatever // may apply for the given target service. Credentials map[string]string // A set of metadata labels about the result to provide context to the client application. Labels map[string]string // The type of token, if applicable, that was able to be generated, which may vary from // the TokenFormat requested in the Action depending on service requirements. TokenFormat string } // ServiceAdapter defines the interface for all DAM adapters that take access actions. type ServiceAdapter interface { // Name returns the name identifier of the adapter as used in configurations. Name() string // Platform returns the name identifier of the platform on which this adapter operates. Platform() string // Descriptors returns a map of service descriptors. Descriptors() map[string]*pb.ServiceDescriptor // IsAggregator returns true if this adapter requires TokenAction.Aggregates. IsAggregator() bool // CheckConfig validates that a new configuration is compatible with this adapter. CheckConfig(templateName string, template *pb.ServiceTemplate, resName, viewName string, view *pb.View, cfg *pb.DamConfig, adapters *ServiceAdapters) (string, error) // MintToken has the adapter mint a token. MintToken(ctx context.Context, input *Action) (*MintTokenResult, error) } // ServiceAdapters includes all adapters that are registered with the system. type ServiceAdapters struct { ByAdapterName map[string]ServiceAdapter ByServiceName map[string]ServiceAdapter Descriptors map[string]*pb.ServiceDescriptor VariableREs map[string]map[string]*regexp.Regexp // serviceName.variableName.regexp errors []error } // Options contains parameters to adapters. type Options struct { // Store: data storage and configuration storage Store storage.Store // Warehouse: resource token creator service Warehouse clouds.ResourceTokenCreator // AWSClient: a client for interacting with the AWS API AWSClient aws.APIClient // Signer: the signer use for signing jwt. Signer kms.Signer } // CreateAdapters registers and collects all adapters with the system. func CreateAdapters(opts *Options) (*ServiceAdapters, error) { adapters := &ServiceAdapters{ ByAdapterName: make(map[string]ServiceAdapter), ByServiceName: make(map[string]ServiceAdapter), Descriptors: make(map[string]*pb.ServiceDescriptor), errors: []error{}, } registerAdapter(adapters, func(adapters *ServiceAdapters) (ServiceAdapter, error) { return NewSawAdapter(opts.Warehouse) }) registerAdapter(adapters, func(adapters *ServiceAdapters) (ServiceAdapter, error) { return NewGatekeeperAdapter(opts.Signer) }) if opts.AWSClient != nil { registerAdapter(adapters, func(adapters *ServiceAdapters) (ServiceAdapter, error) { return NewAwsAdapter(opts.Store, opts.AWSClient) }) } registerAdapter(adapters, func(adapters *ServiceAdapters) (ServiceAdapter, error) { return NewAggregatorAdapter(adapters) }) if len(adapters.errors) > 0 { return nil, adapters.errors[0] } adapters.VariableREs = createVariableREs(adapters.Descriptors) return adapters, nil } // GetItemVariables returns a map of variables and their values for a given view item. func GetItemVariables(adapters *ServiceAdapters, adapterName string, item *pb.View_Item) (map[string]string, string, error) { desc, ok := adapters.Descriptors[adapterName] if !ok { return nil, httputils.StatusPath("ServiceAdapter"), fmt.Errorf("target adapter %q is undefined", adapterName) } for varname, val := range item.Args { v, ok := desc.ItemVariables[varname] if !ok { return nil, httputils.StatusPath("vars", varname), fmt.Errorf("target service %q variable %q is undefined", adapterName, varname) } if !globalflags.Experimental && v.Experimental { return nil, httputils.StatusPath("vars", varname), fmt.Errorf("target service %q variable %q is for experimental use only, not for use in this environment", adapterName, varname) } if len(val) == 0 { // Treat empty input the same as not provided so long as the variable name is valid. delete(item.Args, varname) continue } re, ok := adapters.VariableREs[adapterName][varname] if !ok { continue } if !re.Match([]byte(val)) { return nil, httputils.StatusPath("vars", varname), fmt.Errorf("target adapter %q variable %q value %q does not match expected regexp", adapterName, varname, val) } } return item.Args, "", nil } // ResolveServiceRole is a helper function that returns a ServiceRole structure from a role name on a view. func ResolveServiceRole(roleName string, view *pb.View, res *pb.Resource, cfg *pb.DamConfig) (*pb.ServiceRole, error) { st, ok := cfg.ServiceTemplates[view.ServiceTemplate] if !ok { return nil, fmt.Errorf("internal reference to service template %q not found", view.ServiceTemplate) } sRole, ok := st.ServiceRoles[roleName] if !ok { return nil, fmt.Errorf("internal reference to service template %q role %q not found", view.ServiceTemplate, roleName) } return sRole, nil } func registerAdapter(adapters *ServiceAdapters, init func(adapters *ServiceAdapters) (ServiceAdapter, error)) { adapt, err := init(adapters) if err != nil { adapters.errors = append(adapters.errors, err) return } adapters.ByAdapterName[adapt.Name()] = adapt for k, v := range adapt.Descriptors() { adapters.ByServiceName[k] = adapt adapters.Descriptors[k] = v } } func createVariableREs(descriptors map[string]*pb.ServiceDescriptor) map[string]map[string]*regexp.Regexp { // Create a compiled set of regular expressions for service variable formats // of the form: map[<serviceName>]map[<variableName>]*regexp.Regexp. varRE := make(map[string]map[string]*regexp.Regexp) for k, v := range descriptors { vEntry := make(map[string]*regexp.Regexp) varRE[k] = vEntry for vk, vv := range v.ItemVariables { if len(vv.Regexp) > 0 { restr := vv.Regexp if vv.Type == "split_pattern" { frag := stripAnchors(restr) restr = "^" + frag + "(;" + frag + ")*$" } vEntry[vk] = regexp.MustCompile(restr) } } } return varRE } func stripAnchors(restr string) string { if strings.HasPrefix(restr, "^") { restr = restr[1:] } if strings.HasSuffix(restr, "$") { restr = restr[0 : len(restr)-1] } return restr } func adapterFilePath(name string) string { return "deploy/metadata/adapter_" + name + ".json" }