gcp/dam/main.go (152 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. // This package provides a single-host reverse proxy that rewrites bearer // tokens in Authorization headers to be Google Cloud Platform access tokens. // For configuration information see app.yaml. package main import ( "context" "flag" "net/http" "os" "os/signal" "strings" "time" "cloud.google.com/go/kms/apiv1" /* copybara-comment */ "cloud.google.com/go/logging" /* copybara-comment */ "github.com/gorilla/mux" /* copybara-comment */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/aws" /* copybara-comment: aws */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/dam" /* copybara-comment: dam */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/dsstore" /* copybara-comment: dsstore */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/globalflags" /* copybara-comment: globalflags */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/grpcutil" /* copybara-comment: grpcutil */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/hydraproxy" /* copybara-comment: hydraproxy */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/kms/gcpcrypt" /* copybara-comment: gcpcrypt */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/kms/gcpsign" /* copybara-comment: gcpsign */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/lro" /* copybara-comment: lro */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/osenv" /* copybara-comment: osenv */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/saw" /* copybara-comment: saw */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/server" /* copybara-comment: server */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/serviceinfo" /* copybara-comment: serviceinfo */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */ glog "github.com/golang/glog" /* copybara-comment */ lgrpcpb "google.golang.org/genproto/googleapis/logging/v2" /* copybara-comment: logging_go_grpc */ ) var ( // srvName is the name of this service. srvName = osenv.VarWithDefault("SERVICE_NAME", "dam") // srvAddr is the service URL in GA4GH passports targetting this service. srvAddr = osenv.MustVar("DAM_URL") // cfgPath is the path to the config file. cfgPath = osenv.MustVar("CONFIG_PATH") // project is default GCP project for hosting storage and service accounts, // config options can override this. project = osenv.MustVar("PROJECT") // storageType determines we should be using in-mem storage or not. storageType = osenv.MustVar("STORAGE") // defaultBroker is the default Identity Broker. defaultBroker = osenv.MustVar("DEFAULT_BROKER") // hidePolicyBasis when set to true will not send policy basis via non-admin endpoints. hidePolicyBasis = os.Getenv("HIDE_POLICY_BASIS") != "" // hideRejectDetail when set to true will not send visa rejection detail to clients. hideRejectDetail = os.Getenv("HIDE_REJECTION_DETAILS") != "" // skipInformationReleasePage is useful if IC and DAM provided by same org. // Use env var "SKIP_INFORMATION_RELEASE_PAGE" = true to set. skipInformationReleasePage = os.Getenv("SKIP_INFORMATION_RELEASE_PAGE") == "true" // consentDashboardURL is url to frontend consent dashboard, will replace // ${USER_ID} with userID. If it is not set point to howto doc on github // repo. consentDashboardURL = osenv.VarWithDefault("CONSENT_DASHBOARD_URL", "https://github.com/GoogleCloudPlatform/healthcare-federated-access-services/blob/0f366e73284377571bc314da7666e4b14233c3fa/howto.md#how-do-i-revoke-a-remembered-consent") useHydra = os.Getenv("USE_HYDRA") != "" // hydraAdminAddr is the address for the Hydra admin endpoints. hydraAdminAddr = "" // hydraPublicAddr is the address for the Hydra public endpoints. hydraPublicAddr = "" // hydraPublicAddrInternal is the address for the Hydra public endpoint in the internal traffic. hydraPublicAddrInternal = "" port = osenv.VarWithDefault("DAM_PORT", "8081") cfgVars = map[string]string{ "${YOUR_PROJECT_ID}": project, "${YOUR_ENVIRONMENT}": envPrefix(srvName), } // sldAddr is the address for Stackdriver Logging API. sdlAddr = osenv.VarWithDefault("SDL_ADDR", "logging.googleapis.com:443") ) func main() { flag.Parse() ctx := context.Background() serviceinfo.Project = project serviceinfo.Type = "dam" serviceinfo.Name = srvName sdlcc := grpcutil.NewGRPCClient(ctx, sdlAddr) defer sdlcc.Close() sdlc := lgrpcpb.NewLoggingServiceV2Client(sdlcc) var store storage.Store switch storageType { case "datastore": store = dsstore.NewStore(ctx, project, srvName, cfgPath) case "memory": store = storage.NewMemoryStorage(srvName, cfgPath) // Import and resolve template variables, if any. if err := dam.ImportConfig(store, srvName, nil, cfgVars, true, true, true); err != nil { glog.Exitf("dam.ImportConfig(_, %q, _) failed: %v", srvName, err) } default: glog.Exitf("Unknown storage type %q", storageType) } wh := saw.MustNew(ctx, store) kmsClient, err := kms.NewKeyManagementClient(ctx) if err != nil { glog.Exitf("kms.NewKeyManagementClient(ctx) failed: %v", err) } gcpSigner, err := gcpsign.New(ctx, project, "global", srvName+"_sign_ring", srvName+"_key", kmsClient) if err != nil { glog.Exitf("gcpcrypt.New(ctx, %q, %q, %q, %q, kmsClient) failed: %v", project, "global", srvName+"_sign_ring", srvName+"_key", err) } gcpEncryption, err := gcpcrypt.New(ctx, project, "global", srvName+"_ring", srvName+"_key", kmsClient) if err != nil { glog.Exitf("gcpcrypt.New(ctx, %q, %q, %q, %q, kmsClient) failed: %v", project, "global", srvName+"_ring", srvName+"_key", err) } logger, err := logging.NewClient(ctx, project) if err != nil { glog.Fatalf("logging.NewClient() failed: %v", err) } logger.OnError = func(err error) { glog.Warningf("StackdriverLogging.Client.OnError: %v", err) } var hyproxy *hydraproxy.Service if useHydra { hydraAdminAddr = osenv.MustVar("HYDRA_ADMIN_URL") hydraPublicAddr = osenv.MustVar("HYDRA_PUBLIC_URL") hydraPublicAddrInternal := osenv.MustVar("HYDRA_PUBLIC_URL_INTERNAL") hyproxy, err = hydraproxy.New(http.DefaultClient, hydraAdminAddr, hydraPublicAddrInternal, store) if err != nil { glog.Exitf("hydraproxy.New failed: %v", err) } } var awsClient aws.APIClient = nil if globalflags.EnableAWSAdapter { awsClient, err = aws.NewAPIClient() if err != nil { glog.Exitf("aws.NewAPIClient failed: %v", err) } } lros, err := lro.New("lro", 60*time.Second, 60*time.Second, store, nil) if err != nil { glog.Exitf("lro.New failed: %v", err) } r := mux.NewRouter() s := dam.New(r, &dam.Options{ Domain: srvAddr, ServiceName: srvName, DefaultBroker: defaultBroker, Store: store, Warehouse: wh, AWSClient: awsClient, ServiceAccountManager: wh, Logger: logger, SDLC: sdlc, AuditLogProject: project, HidePolicyBasis: hidePolicyBasis, HideRejectDetail: hideRejectDetail, SkipInformationReleasePage: skipInformationReleasePage, ConsentDashboardURL: consentDashboardURL, UseHydra: true, HydraAdminURL: hydraAdminAddr, HydraPublicURL: hydraPublicAddr, HydraPublicProxy: hyproxy, Signer: gcpSigner, Encryption: gcpEncryption, LRO: lros, }) r.HandleFunc("/liveness_check", httputils.LivenessCheckHandler) srv := server.New("dam", port, s.Handler) srv.ServeUnblock() c := make(chan os.Signal, 1) // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. signal.Notify(c, os.Interrupt) // Block until we receive our signal. <-c srv.Shutdown() } func envPrefix(name string) string { if strings.Contains(name, "-") { return "-" + strings.SplitN(name, "-", 2)[1] } return "" }