@@ -33,9 +33,9 @@ import (
3333 "syscall"
3434 "time"
3535
36+ "cloud.google.com/go/compute/metadata"
3637 "contrib.go.opencensus.io/exporter/prometheus"
3738 "contrib.go.opencensus.io/exporter/stackdriver"
38- "cloud.google.com/go/compute/metadata"
3939 "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
4040 "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/healthcheck"
4141 "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
@@ -45,6 +45,10 @@ import (
4545 "github.com/spf13/pflag"
4646 "github.com/spf13/viper"
4747 "go.opencensus.io/trace"
48+ "golang.org/x/oauth2"
49+ "google.golang.org/api/impersonate"
50+ "google.golang.org/api/option"
51+ sqladmin "google.golang.org/api/sqladmin/v1"
4852)
4953
5054var (
@@ -659,6 +663,15 @@ func loadConfig(c *Command, args []string, opts []Option) error {
659663 args = append (args , mArgs ... )
660664 }
661665
666+ // If projects is set, fetch instances from those projects.
667+ if len (c .conf .Projects ) > 0 {
668+ pArgs , err := instanceFromProjects (c .Context (), c .conf )
669+ if err != nil {
670+ return err
671+ }
672+ args = append (args , pArgs ... )
673+ }
674+
662675 // Handle logger separately from config
663676 if c .conf .StructuredLogs {
664677 c .logger = log .NewStructuredLogger (c .conf .Quiet )
@@ -1304,3 +1317,110 @@ func instanceFromMetadata(path string) ([]string, error) {
13041317 }
13051318 return args , nil
13061319}
1320+
1321+ func instanceFromProjects (ctx context.Context , conf * proxy.Config ) ([]string , error ) {
1322+ var opts []option.ClientOption
1323+ if conf .APIEndpointURL != "" {
1324+ opts = append (opts , option .WithEndpoint (conf .APIEndpointURL ))
1325+ }
1326+ if conf .QuotaProject != "" {
1327+ opts = append (opts , option .WithQuotaProject (conf .QuotaProject ))
1328+ }
1329+
1330+ // Handle credentials
1331+ switch {
1332+ case conf .ImpersonationChain != "" :
1333+ target , delegates := parseImpersonationChain (conf .ImpersonationChain )
1334+ var iopts []option.ClientOption
1335+ switch {
1336+ case conf .Token != "" :
1337+ ts := oauth2 .StaticTokenSource (& oauth2.Token {AccessToken : conf .Token })
1338+ iopts = append (iopts , option .WithTokenSource (ts ))
1339+ case conf .CredentialsFile != "" :
1340+ iopts = append (iopts , option .WithCredentialsFile (conf .CredentialsFile ))
1341+ case conf .CredentialsJSON != "" :
1342+ iopts = append (iopts , option .WithCredentialsJSON ([]byte (conf .CredentialsJSON )))
1343+ }
1344+ ts , err := impersonate .CredentialsTokenSource (ctx , impersonate.CredentialsConfig {
1345+ TargetPrincipal : target ,
1346+ Delegates : delegates ,
1347+ Scopes : []string {sqladmin .SqlserviceAdminScope },
1348+ }, iopts ... )
1349+ if err != nil {
1350+ return nil , err
1351+ }
1352+ opts = append (opts , option .WithTokenSource (ts ))
1353+ case conf .Token != "" :
1354+ ts := oauth2 .StaticTokenSource (& oauth2.Token {AccessToken : conf .Token })
1355+ opts = append (opts , option .WithTokenSource (ts ))
1356+ case conf .CredentialsFile != "" :
1357+ opts = append (opts , option .WithCredentialsFile (conf .CredentialsFile ))
1358+ case conf .CredentialsJSON != "" :
1359+ opts = append (opts , option .WithCredentialsJSON ([]byte (conf .CredentialsJSON )))
1360+ }
1361+
1362+ sql , err := sqladmin .NewService (ctx , opts ... )
1363+ if err != nil {
1364+ return nil , err
1365+ }
1366+
1367+ ch := make (chan string )
1368+ errCh := make (chan error , len (conf .Projects ))
1369+ var wg sync.WaitGroup
1370+ wg .Add (len (conf .Projects ))
1371+ for _ , proj := range conf .Projects {
1372+ proj := proj
1373+ go func () {
1374+ defer wg .Done ()
1375+ err := sql .Instances .List (proj ).Pages (ctx , func (r * sqladmin.InstancesListResponse ) error {
1376+ for _ , in := range r .Items {
1377+ // The Proxy only supports Second Gen
1378+ if in .BackendType == "SECOND_GEN" {
1379+ ch <- in .ConnectionName
1380+ }
1381+ }
1382+ return nil
1383+ })
1384+ if err != nil {
1385+ errCh <- fmt .Errorf ("failed to list instances in %q: %v" , proj , err )
1386+ }
1387+ }()
1388+ }
1389+ go func () {
1390+ wg .Wait ()
1391+ close (ch )
1392+ close (errCh )
1393+ }()
1394+
1395+ var args []string
1396+ for x := range ch {
1397+ args = append (args , x )
1398+ }
1399+
1400+ // Check for any errors
1401+ for err := range errCh {
1402+ if err != nil {
1403+ return nil , err
1404+ }
1405+ }
1406+
1407+ if len (args ) == 0 {
1408+ return nil , fmt .Errorf ("no Cloud SQL Instances found in projects: %v" , conf .Projects )
1409+ }
1410+ return args , nil
1411+ }
1412+
1413+ func parseImpersonationChain (chain string ) (string , []string ) {
1414+ accts := strings .Split (chain , "," )
1415+ target := accts [0 ]
1416+ // Assign delegates if the chain is more than one account. Delegation
1417+ // goes from last back towards target, e.g., With sa1,sa2,sa3, sa3
1418+ // delegates to sa2, which impersonates the target sa1.
1419+ var delegates []string
1420+ if l := len (accts ); l > 1 {
1421+ for i := l - 1 ; i > 0 ; i -- {
1422+ delegates = append (delegates , accts [i ])
1423+ }
1424+ }
1425+ return target , delegates
1426+ }
0 commit comments