forked from GoogleCloudPlatform/nodejs-docs-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request GoogleCloudPlatform#1 from GoogleCloudPlatform/for…
…k-cloud-sql-proxy This is an import from the cloudsql-proxy v2 branch at: https://github.com/GoogleCloudPlatform/cloudsql-proxy/tree/dd7198d940518471166d7f91efa24ae2e50061bc This PR also removes all the v1 paths as well as the: - build - github - kokoro - examples directories.
- Loading branch information
Showing
16 changed files
with
2,486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export GOOGLE_CLOUD_PROJECT="project-name" | ||
|
||
export MYSQL_CONNECTION_NAME="project:region:instance" | ||
export MYSQL_USER="mysql-user" | ||
export MYSQL_PASS="mysql-password" | ||
export MYSQL_DB="mysql-db-name" | ||
|
||
export POSTGRES_CONNECTION_NAME="project:region:instance" | ||
export POSTGRES_USER="postgres-user" | ||
export POSTGRES_PASS="postgres-password" | ||
export POSTGRES_DB="postgres-db-name" | ||
export POSTGRES_USER_IAM="some-user-with-db-access@example.com" | ||
|
||
export SQLSERVER_CONNECTION_NAME="project:region:instance" | ||
export SQLSERVER_USER="sqlserver-user" | ||
export SQLSERVER_PASS="sqlserver-password" | ||
export SQLSERVER_DB="sqlserver-db-name" | ||
|
||
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# direnv | ||
.envrc | ||
|
||
# IDEs | ||
.idea/ | ||
.vscode/ | ||
|
||
# Compiled binary | ||
/cmd/cloud_sql_proxy/cloud_sql_proxy | ||
/cloud_sql_proxy | ||
# v2 binary | ||
/cloudsql-proxy | ||
|
||
/key.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright 2022 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 cloudsql | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"net" | ||
|
||
"cloud.google.com/go/cloudsqlconn" | ||
) | ||
|
||
// Dialer dials a Cloud SQL instance and returns its database engine version. | ||
type Dialer interface { | ||
// Dial returns a connection to the specified instance. | ||
Dial(ctx context.Context, inst string, opts ...cloudsqlconn.DialOption) (net.Conn, error) | ||
// EngineVersion retrieves the provided instance's database version (e.g., | ||
// POSTGRES_14) | ||
EngineVersion(ctx context.Context, inst string) (string, error) | ||
|
||
io.Closer | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright 2021 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 | ||
|
||
// https://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 cmd | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var ( | ||
errSigInt = &exitError{ | ||
Err: errors.New("SIGINT signal received"), | ||
Code: 130, | ||
} | ||
|
||
errSigTerm = &exitError{ | ||
Err: errors.New("SIGTERM signal received"), | ||
Code: 137, | ||
} | ||
) | ||
|
||
func newBadCommandError(msg string) error { | ||
return &exitError{ | ||
Err: errors.New(msg), | ||
Code: 1, | ||
} | ||
} | ||
|
||
// exitError is an error with an exit code, that's returned when the cmd exits. | ||
// When possible, try to match these conventions: https://tldp.org/LDP/abs/html/exitcodes.html | ||
type exitError struct { | ||
Code int | ||
Err error | ||
} | ||
|
||
func (e *exitError) Error() string { | ||
if e.Err == nil { | ||
return "<missing error>" | ||
} | ||
return e.Err.Error() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
// Copyright 2021 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 | ||
// | ||
// https://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 cmd | ||
|
||
import ( | ||
"context" | ||
_ "embed" | ||
"errors" | ||
"fmt" | ||
"net" | ||
"net/url" | ||
"os" | ||
"os/signal" | ||
"strconv" | ||
"strings" | ||
"syscall" | ||
|
||
"cloud.google.com/go/cloudsqlconn" | ||
"github.com/GoogleCloudPlatform/cloudsql-proxy/v2/cloudsql" | ||
"github.com/GoogleCloudPlatform/cloudsql-proxy/v2/internal/proxy" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
// versionString indicates the version of this library. | ||
//go:embed version.txt | ||
versionString string | ||
userAgent string | ||
) | ||
|
||
func init() { | ||
versionString = strings.TrimSpace(versionString) | ||
userAgent = "cloud-sql-auth-proxy/" + versionString | ||
} | ||
|
||
// Execute adds all child commands to the root command and sets flags appropriately. | ||
// This is called by main.main(). It only needs to happen once to the rootCmd. | ||
func Execute() { | ||
if err := NewCommand().Execute(); err != nil { | ||
exit := 1 | ||
if terr, ok := err.(*exitError); ok { | ||
exit = terr.Code | ||
} | ||
os.Exit(exit) | ||
} | ||
} | ||
|
||
// Command represents an invocation of the Cloud SQL Auth Proxy. | ||
type Command struct { | ||
*cobra.Command | ||
conf *proxy.Config | ||
} | ||
|
||
// Option is a function that configures a Command. | ||
type Option func(*proxy.Config) | ||
|
||
// WithDialer configures the Command to use the provided dialer to connect to | ||
// Cloud SQL instances. | ||
func WithDialer(d cloudsql.Dialer) Option { | ||
return func(c *proxy.Config) { | ||
c.Dialer = d | ||
} | ||
} | ||
|
||
// NewCommand returns a Command object representing an invocation of the proxy. | ||
func NewCommand(opts ...Option) *Command { | ||
c := &Command{ | ||
conf: &proxy.Config{}, | ||
} | ||
for _, o := range opts { | ||
o(c.conf) | ||
} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "cloud_sql_proxy instance_connection_name...", | ||
Version: versionString, | ||
Short: "cloud_sql_proxy provides a secure way to authorize connections to Cloud SQL.", | ||
Long: `The Cloud SQL Auth proxy provides IAM-based authorization and encryption when | ||
connecting to Cloud SQL instances. It listens on a local port and forwards connections | ||
to your instance's IP address, providing a secure connection without having to manage | ||
any client SSL certificates.`, | ||
Args: func(cmd *cobra.Command, args []string) error { | ||
err := parseConfig(cmd, c.conf, args) | ||
if err != nil { | ||
return err | ||
} | ||
// The arguments are parsed. Usage is no longer needed. | ||
cmd.SilenceUsage = true | ||
return nil | ||
}, | ||
RunE: func(*cobra.Command, []string) error { | ||
return runSignalWrapper(c) | ||
}, | ||
} | ||
|
||
// Global-only flags | ||
cmd.PersistentFlags().StringVarP(&c.conf.Token, "token", "t", "", | ||
"Bearer token used for authorization.") | ||
cmd.PersistentFlags().StringVarP(&c.conf.CredentialsFile, "credentials-file", "c", "", | ||
"Path to a service account key to use for authentication.") | ||
|
||
// Global and per instance flags | ||
cmd.PersistentFlags().StringVarP(&c.conf.Addr, "address", "a", "127.0.0.1", | ||
"Address on which to bind Cloud SQL instance listeners.") | ||
cmd.PersistentFlags().IntVarP(&c.conf.Port, "port", "p", 0, | ||
"Initial port to use for listeners. Subsequent listeners increment from this value.") | ||
|
||
c.Command = cmd | ||
return c | ||
} | ||
|
||
func parseConfig(cmd *cobra.Command, conf *proxy.Config, args []string) error { | ||
// If no instance connection names were provided, error. | ||
if len(args) == 0 { | ||
return newBadCommandError("missing instance_connection_name (e.g., project:region:instance)") | ||
} | ||
// First, validate global config. | ||
if ip := net.ParseIP(conf.Addr); ip == nil { | ||
return newBadCommandError(fmt.Sprintf("not a valid IP address: %q", conf.Addr)) | ||
} | ||
|
||
// If both token and credentials file were set, error. | ||
if conf.Token != "" && conf.CredentialsFile != "" { | ||
return newBadCommandError("Cannot specify --token and --credentials-file flags at the same time") | ||
} | ||
|
||
switch { | ||
case conf.Token != "": | ||
cmd.Printf("Authorizing with the -token flag\n") | ||
case conf.CredentialsFile != "": | ||
cmd.Printf("Authorizing with the credentials file at %q\n", conf.CredentialsFile) | ||
default: | ||
cmd.Printf("Authorizing with Application Default Credentials") | ||
} | ||
|
||
var ics []proxy.InstanceConnConfig | ||
for _, a := range args { | ||
// Assume no query params initially | ||
ic := proxy.InstanceConnConfig{ | ||
Name: a, | ||
} | ||
// If there are query params, update instance config. | ||
if res := strings.SplitN(a, "?", 2); len(res) > 1 { | ||
ic.Name = res[0] | ||
q, err := url.ParseQuery(res[1]) | ||
if err != nil { | ||
return newBadCommandError(fmt.Sprintf("could not parse query: %q", res[1])) | ||
} | ||
|
||
if a, ok := q["address"]; ok { | ||
if len(a) != 1 { | ||
return newBadCommandError(fmt.Sprintf("address query param should be only one value: %q", a)) | ||
} | ||
if ip := net.ParseIP(a[0]); ip == nil { | ||
return newBadCommandError( | ||
fmt.Sprintf("address query param is not a valid IP address: %q", | ||
a[0], | ||
)) | ||
} | ||
ic.Addr = a[0] | ||
} | ||
|
||
if p, ok := q["port"]; ok { | ||
if len(p) != 1 { | ||
return newBadCommandError(fmt.Sprintf("port query param should be only one value: %q", a)) | ||
} | ||
pp, err := strconv.Atoi(p[0]) | ||
if err != nil { | ||
return newBadCommandError( | ||
fmt.Sprintf("port query param is not a valid integer: %q", | ||
p[0], | ||
)) | ||
} | ||
ic.Port = pp | ||
} | ||
} | ||
ics = append(ics, ic) | ||
} | ||
|
||
conf.Instances = ics | ||
return nil | ||
} | ||
|
||
// runSignalWrapper watches for SIGTERM and SIGINT and interupts execution if necessary. | ||
func runSignalWrapper(cmd *Command) error { | ||
ctx, cancel := context.WithCancel(cmd.Context()) | ||
defer cancel() | ||
|
||
shutdownCh := make(chan error) | ||
|
||
// watch for sigterm / sigint signals | ||
signals := make(chan os.Signal, 1) | ||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) | ||
go func() { | ||
var s os.Signal | ||
select { | ||
case s = <-signals: | ||
case <-cmd.Context().Done(): | ||
// this should only happen when the context supplied in tests in canceled | ||
s = syscall.SIGINT | ||
} | ||
switch s { | ||
case syscall.SIGINT: | ||
shutdownCh <- errSigInt | ||
case syscall.SIGTERM: | ||
shutdownCh <- errSigTerm | ||
} | ||
}() | ||
|
||
// Start the proxy asynchronously, so we can exit early if a shutdown signal is sent | ||
startCh := make(chan *proxy.Client) | ||
go func() { | ||
defer close(startCh) | ||
// Check if the caller has configured a dialer. | ||
// Otherwise, initialize a new one. | ||
d := cmd.conf.Dialer | ||
if d == nil { | ||
opts := append(cmd.conf.DialerOpts(), cloudsqlconn.WithUserAgent(userAgent)) | ||
var err error | ||
d, err = cloudsqlconn.NewDialer(ctx, opts...) | ||
if err != nil { | ||
shutdownCh <- fmt.Errorf("error initializing dialer: %v", err) | ||
return | ||
} | ||
} | ||
p, err := proxy.NewClient(ctx, d, cmd.Command, cmd.conf) | ||
if err != nil { | ||
shutdownCh <- fmt.Errorf("unable to start: %v", err) | ||
return | ||
} | ||
startCh <- p | ||
}() | ||
// Wait for either startup to finish or a signal to interupt | ||
var p *proxy.Client | ||
select { | ||
case err := <-shutdownCh: | ||
return err | ||
case p = <-startCh: | ||
} | ||
cmd.Println("The proxy has started successfully and is ready for new connections!") | ||
defer p.Close() | ||
|
||
go func() { | ||
shutdownCh <- p.Serve(ctx) | ||
}() | ||
|
||
err := <-shutdownCh | ||
switch { | ||
case errors.Is(err, errSigInt): | ||
cmd.PrintErrln("SIGINT signal received. Shuting down...") | ||
case errors.Is(err, errSigTerm): | ||
cmd.PrintErrln("SIGTERM signal received. Shuting down...") | ||
default: | ||
cmd.PrintErrf("The proxy has encountered a terminal error: %v\n", err) | ||
} | ||
return err | ||
} |
Oops, something went wrong.