├── .ci └── cloudbuild.yaml ├── .envrc.example ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── documentation_issue.yaml │ ├── feature_request.yaml │ └── question.yaml ├── auto-label.yaml ├── blunderbuss.yml ├── flakybot.yaml ├── header-checker-lint.yml ├── labels.yml ├── release-please.yml ├── renovate.json5 ├── trusted-contribution.yml └── workflows │ ├── codeql.yml │ ├── coverage.yaml │ ├── docs.yaml │ ├── govulncheck.yaml │ ├── labels.yaml │ ├── lint.yaml │ ├── scorecard.yml │ ├── tests.yaml │ └── v1-periodic.yaml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.alpine ├── Dockerfile.bookworm ├── Dockerfile.bullseye ├── LICENSE ├── README.md ├── SECURITY.md ├── cloudsql └── cloudsql.go ├── cmd ├── config_test.go ├── errors.go ├── gendocs │ └── gen_cloud-sql-proxy_docs.go ├── options.go ├── options_test.go ├── root.go ├── root_linux_test.go ├── root_test.go ├── root_windows_test.go ├── testdata │ ├── config-json.json │ ├── config-toml.toml │ ├── config-yaml.yaml │ └── two-instances.toml ├── version.txt └── wait_test.go ├── docs └── cmd │ ├── cloud-sql-proxy.md │ ├── cloud-sql-proxy_completion.md │ ├── cloud-sql-proxy_completion_bash.md │ ├── cloud-sql-proxy_completion_fish.md │ ├── cloud-sql-proxy_completion_powershell.md │ ├── cloud-sql-proxy_completion_zsh.md │ └── cloud-sql-proxy_wait.md ├── examples ├── disaster-recovery │ ├── README.md │ └── failover.sh ├── k8s-health-check │ ├── README.md │ └── proxy_with_http_health_check.yaml ├── k8s-service │ ├── README.md │ ├── ca_csr.json │ ├── deployment.yaml │ ├── pgbouncer_deployment.yaml │ ├── pgbouncer_service.yaml │ └── server_csr.json ├── k8s-sidecar │ ├── README.md │ ├── job_with_shutdown_hook.yaml │ ├── job_with_sidecar.yaml │ ├── no_proxy_private_ip.yaml │ ├── proxy_with_sa_key.yaml │ ├── proxy_with_workload_identity.yaml │ └── service_account.yaml └── multi-container │ └── ruby │ ├── Dockerfile │ ├── Gemfile │ ├── README.md │ ├── app.rb │ ├── config.ru │ └── multicontainer.yaml ├── go.mod ├── go.sum ├── internal ├── gcloud │ ├── gcloud.go │ └── gcloud_test.go ├── healthcheck │ ├── healthcheck.go │ └── healthcheck_test.go ├── log │ └── log.go └── proxy │ ├── fuse.go │ ├── fuse_darwin.go │ ├── fuse_freebsd.go │ ├── fuse_linux.go │ ├── fuse_linux_test.go │ ├── fuse_openbsd.go │ ├── fuse_test.go │ ├── fuse_windows.go │ ├── internal_test.go │ ├── proxy.go │ ├── proxy_other.go │ ├── proxy_other_test.go │ ├── proxy_test.go │ ├── proxy_windows_test.go │ └── unix.go ├── main.go ├── main_windows.go ├── migration-guide.md ├── tests ├── common_test.go ├── connection_test.go ├── fuse_test.go ├── mysql_test.go ├── other_test.go ├── postgres_test.go └── sqlserver_test.go ├── windows-service-guide.md ├── windows_install_service.bat └── windows_remove_service.bat /.ci/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | steps: 16 | 17 | - id: run integration tests 18 | name: golang:${_VERSION} 19 | env: 20 | [ 21 | "IP_TYPE=${_IP_TYPE}", 22 | "GOOGLE_CLOUD_PROJECT=${PROJECT_ID}", 23 | "TMPDIR=/tmp" 24 | ] 25 | secretEnv: 26 | [ 27 | "MYSQL_CONNECTION_NAME", 28 | "MYSQL_USER", 29 | "MYSQL_PASS", 30 | "MYSQL_DB", 31 | "MYSQL_MCP_CONNECTION_NAME", 32 | "MYSQL_MCP_PASS", 33 | "POSTGRES_CONNECTION_NAME", 34 | "POSTGRES_USER", 35 | "POSTGRES_USER_IAM", 36 | "POSTGRES_PASS", 37 | "POSTGRES_DB", 38 | "POSTGRES_CAS_CONNECTION_NAME", 39 | "POSTGRES_CAS_PASS", 40 | "POSTGRES_CUSTOMER_CAS_CONNECTION_NAME", 41 | "POSTGRES_CUSTOMER_CAS_PASS", 42 | "POSTGRES_CUSTOMER_CAS_DOMAIN_NAME", 43 | "POSTGRES_MCP_CONNECTION_NAME", 44 | "POSTGRES_MCP_PASS", 45 | "SQLSERVER_CONNECTION_NAME", 46 | "SQLSERVER_USER", 47 | "SQLSERVER_PASS", 48 | "SQLSERVER_DB", 49 | "IMPERSONATED_USER", 50 | ] 51 | entrypoint: bash 52 | args: 53 | - -c 54 | - | 55 | go test -race -v ./tests/... 56 | 57 | substitutions: 58 | _VERSION: ${_VERSION} 59 | _IP_TYPE: ${_IP_TYPE} 60 | 61 | availableSecrets: 62 | secretManager: 63 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_CONNECTION_NAME/versions/latest" 64 | env: "MYSQL_CONNECTION_NAME" 65 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_USER/versions/latest" 66 | env: "MYSQL_USER" 67 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_PASS/versions/latest" 68 | env: "MYSQL_PASS" 69 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_DB/versions/latest" 70 | env: "MYSQL_DB" 71 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_MCP_CONNECTION_NAME/versions/latest" 72 | env: "MYSQL_MCP_CONNECTION_NAME" 73 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_MCP_PASS/versions/latest" 74 | env: "MYSQL_MCP_PASS" 75 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CONNECTION_NAME/versions/latest" 76 | env: "POSTGRES_CONNECTION_NAME" 77 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_USER/versions/latest" 78 | env: "POSTGRES_USER" 79 | - versionName: "projects/$PROJECT_ID/secrets/CLOUD_BUILD_POSTGRES_IAM_USER/versions/latest" 80 | env: "POSTGRES_USER_IAM" 81 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_PASS/versions/latest" 82 | env: "POSTGRES_PASS" 83 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_DB/versions/latest" 84 | env: "POSTGRES_DB" 85 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CAS_CONNECTION_NAME/versions/latest" 86 | env: "POSTGRES_CAS_CONNECTION_NAME" 87 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CAS_PASS/versions/latest" 88 | env: "POSTGRES_CAS_PASS" 89 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CUSTOMER_CAS_CONNECTION_NAME/versions/latest" 90 | env: "POSTGRES_CUSTOMER_CAS_CONNECTION_NAME" 91 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CUSTOMER_CAS_PASS/versions/latest" 92 | env: "POSTGRES_CUSTOMER_CAS_PASS" 93 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CUSTOMER_CAS_DOMAIN_NAME/versions/latest" 94 | env: "POSTGRES_CUSTOMER_CAS_DOMAIN_NAME" 95 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_MCP_CONNECTION_NAME/versions/latest" 96 | env: "POSTGRES_MCP_CONNECTION_NAME" 97 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_MCP_PASS/versions/latest" 98 | env: "POSTGRES_MCP_PASS" 99 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_CONNECTION_NAME/versions/latest" 100 | env: "SQLSERVER_CONNECTION_NAME" 101 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_USER/versions/latest" 102 | env: "SQLSERVER_USER" 103 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_PASS/versions/latest" 104 | env: "SQLSERVER_PASS" 105 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_DB/versions/latest" 106 | env: "SQLSERVER_DB" 107 | - versionName: "projects/$PROJECT_ID/secrets/CLOUD_BUILD_SA/versions/latest" 108 | env: "IMPERSONATED_USER" 109 | 110 | options: 111 | dynamicSubstitutions: true 112 | pool: 113 | name: ${_POOL_NAME} 114 | logging: CLOUD_LOGGING_ONLY 115 | -------------------------------------------------------------------------------- /.envrc.example: -------------------------------------------------------------------------------- 1 | export GOOGLE_CLOUD_PROJECT="project-name" 2 | 3 | export MYSQL_CONNECTION_NAME="project:region:instance" 4 | export MYSQL_USER="mysql-user" 5 | export MYSQL_PASS="mysql-password" 6 | export MYSQL_DB="mysql-db-name" 7 | 8 | export POSTGRES_CONNECTION_NAME="project:region:instance" 9 | export POSTGRES_USER="postgres-user" 10 | export POSTGRES_PASS="postgres-password" 11 | export POSTGRES_DB="postgres-db-name" 12 | export POSTGRES_USER_IAM="some-user-with-db-access@example.com" 13 | 14 | export SQLSERVER_CONNECTION_NAME="project:region:instance" 15 | export SQLSERVER_USER="sqlserver-user" 16 | export SQLSERVER_PASS="sqlserver-password" 17 | export SQLSERVER_DB="sqlserver-db-name" 18 | 19 | export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json 20 | 21 | # Requires the impersonating IAM principal to have 22 | # roles/iam.serviceAccountTokenCreator 23 | export IMPERSONATED_USER="some-user-with-db-access@example.com" 24 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @GoogleCloudPlatform/cloud-sql-connectors 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: 🐞 Bug Report 16 | description: File a bug report 17 | title: "Brief summary of what bug or error was observed" 18 | labels: ["type: bug"] 19 | body: 20 | - type: markdown 21 | attributes: 22 | value: | 23 | Thanks for stopping by to let us know something could be better! 24 | Please run down the following list and make sure you've tried the usual "quick fixes": 25 | - Search the [current open issues](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/issues) 26 | - Check for answers on [StackOverflow](https://stackoverflow.com/questions/tagged/google-cloud-sql) (under the 'google-cloud-sql' tag) 27 | 28 | If you are still having issues, please include as much information as possible below! :smile: 29 | - type: textarea 30 | id: bug-description 31 | attributes: 32 | label: Bug Description 33 | description: "Please enter a detailed description of the bug, and any information about what behavior you noticed and why it is defective or unintentional." 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: example-code 38 | attributes: 39 | label: Example code (or command) 40 | description: "Please paste any useful application code related to the bug below. (if your code is in a public repo, feel free to paste a link!)" 41 | value: | 42 | ``` 43 | // paste your code or command here 44 | ``` 45 | - type: textarea 46 | id: stacktrace 47 | attributes: 48 | label: Stacktrace 49 | description: "Paste any relevant stacktrace or error you are running into here. Be sure to filter sensitive information!" 50 | render: bash 51 | - type: textarea 52 | id: repro 53 | attributes: 54 | label: Steps to reproduce? 55 | description: "How do you trigger this bug? Please walk us through it step by step." 56 | value: | 57 | 1. ? 58 | 2. ? 59 | 3. ? 60 | ... 61 | validations: 62 | required: true 63 | - type: textarea 64 | id: environment 65 | attributes: 66 | label: Environment 67 | description: "Let us know some details about the environment in which you are seeing the bug!" 68 | value: | 69 | 1. OS type and version: 70 | 2. Cloud SQL Proxy version (`./cloud-sql-proxy --version`): 71 | 3. Proxy invocation command (for example, `./cloud-sql-proxy --port 5432 INSTANCE_CONNECTION_NAME`): 72 | validations: 73 | required: true 74 | - type: textarea 75 | id: additional-details 76 | attributes: 77 | label: Additional Details 78 | description: "Any other information you want us to know?" 79 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_issue.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: 📝 Documentation Issue 16 | description: Report wrong or missing information with the documentation in this repo. 17 | title: "Brief summary of what is missing or incorrect" 18 | labels: ["type: docs"] 19 | body: 20 | - type: markdown 21 | attributes: 22 | value: | 23 | Thanks for stopping by to let us know something could be better! :smile: 24 | 25 | Please explain below how we can improve our documentation. 26 | - type: textarea 27 | id: description 28 | attributes: 29 | label: Description 30 | description: "Provide a short description of what is missing or incorrect, as well as a link to the specific location of the issue." 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: potential-solution 35 | attributes: 36 | label: Potential Solution 37 | description: "What would you prefer the documentation say? Why would this information be more accurate or helpful?" 38 | - type: textarea 39 | id: additional-details 40 | attributes: 41 | label: Additional Details 42 | description: "Please reference any other relevant issues, PRs, descriptions, or screenshots here." 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: ✨ Feature Request 16 | description: Suggest an idea for new or improved behavior. 17 | title: "Brief summary of the proposed feature" 18 | labels: ["type: feature request"] 19 | body: 20 | - type: markdown 21 | attributes: 22 | value: | 23 | Thanks for stopping by to let us know something could be better! 24 | 25 | Please run down the following list before proceeding with your feature request: 26 | - Search the [current open issues](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/issues) to prevent creating a duplicate. 27 | 28 | Please include as much information as possible below! :smile: 29 | - type: textarea 30 | id: feature-description 31 | attributes: 32 | label: Feature Description 33 | description: "A clear and concise description of what feature you would like to see, and why it would be useful to have added." 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: sample-code 38 | attributes: 39 | label: Sample code 40 | description: "If you already have an idea of what the implementation of this feature would like in code please provide it. (pseudo code is okay!)" 41 | value: | 42 | ``` 43 | // sample code here 44 | ``` 45 | - type: textarea 46 | id: alternatives-considered 47 | attributes: 48 | label: Alternatives Considered 49 | description: "Are there any workaround or third party tools to replicate this behavior? Why would adding this feature be preferred over them?" 50 | - type: textarea 51 | id: additional-details 52 | attributes: 53 | label: Additional Details 54 | description: "Any additional information we should know? Please reference it here (issues, PRs, descriptions, or screenshots)" 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: 💬 Question 16 | description: Questions on how something works or the best way to do something? 17 | title: "Brief summary of your question" 18 | labels: ["type: question"] 19 | body: 20 | - type: markdown 21 | attributes: 22 | value: | 23 | Thanks for stopping by to let us know something could be better!
24 | 25 | Please run down the following list and make sure you've tried the usual "quick fixes": 26 | - Search the [current open issues](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/issues) for a similar question 27 | - Check for answers on [StackOverflow](https://stackoverflow.com/questions/tagged/google-cloud-sql) (under the 'google-cloud-sql' tag) 28 | 29 | If you still have a question, please include as much information as possible below! :smile: 30 | - type: textarea 31 | id: question 32 | attributes: 33 | label: Question 34 | description: "What's your question? Please provide as much relevant information as possible to reduce turnaround time." 35 | placeholder: "Example: How do I connect using this connector with Private IP from Cloud Run?" 36 | validations: 37 | required: true 38 | - type: textarea 39 | id: code 40 | attributes: 41 | label: Code 42 | description: "Please paste any useful application code that might be relevant to your question. (if your code is in a public repo, feel free to paste a link!)" 43 | render: Go 44 | - type: textarea 45 | id: additional-details 46 | attributes: 47 | label: Additional Details 48 | description: "Any other information you want us to know that might be helpful in answering your question? (link issues, PRs, descriptions, or screenshots)" 49 | -------------------------------------------------------------------------------- /.github/auto-label.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | enabled: false 16 | -------------------------------------------------------------------------------- /.github/blunderbuss.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | assign_issues: 16 | - hessjcg 17 | - kgala2 18 | 19 | assign_prs: 20 | - hessjcg 21 | - kgala2 22 | -------------------------------------------------------------------------------- /.github/flakybot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | issuePriority: p2 16 | -------------------------------------------------------------------------------- /.github/header-checker-lint.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | allowedCopyrightHolders: 16 | - 'Google LLC' 17 | allowedLicenses: 18 | - 'Apache-2.0' 19 | sourceFileExtensions: 20 | - 'go' 21 | - 'yaml' 22 | - 'yml' 23 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | - name: duplicate 16 | color: ededed 17 | description: "" 18 | 19 | - name: 'type: bug' 20 | color: db4437 21 | description: Error or flaw in code with unintended results or allowing sub-optimal 22 | usage patterns. 23 | - name: 'type: cleanup' 24 | color: c5def5 25 | description: An internal cleanup or hygiene concern. 26 | - name: 'type: docs' 27 | color: 0000A0 28 | description: Improvement to the documentation for an API. 29 | - name: 'type: feature request' 30 | color: c5def5 31 | description: ‘Nice-to-have’ improvement, new feature or different behavior or design. 32 | - name: 'type: process' 33 | color: c5def5 34 | description: A process-related concern. May include testing, release, or the like. 35 | - name: 'type: question' 36 | color: c5def5 37 | description: Request for information or clarification. 38 | 39 | - name: 'priority: p0' 40 | color: b60205 41 | description: Highest priority. Critical issue. P0 implies highest priority. 42 | - name: 'priority: p1' 43 | color: ffa03e 44 | description: Important issue which blocks shipping the next release. Will be fixed 45 | prior to next release. 46 | - name: 'priority: p2' 47 | color: fef2c0 48 | description: Moderately-important priority. Fix may not be included in next release. 49 | - name: 'priority: p3' 50 | color: ffffc7 51 | description: Desirable enhancement or fix. May not be included in next release. 52 | 53 | - name: 'v1' 54 | color: d93f0b 55 | description: Functionality for v1 of the proxy. 56 | 57 | - name: automerge 58 | color: 00ff00 59 | description: Merge the pull request once unit tests and other checks pass. 60 | - name: 'automerge: exact' 61 | color: 8dd517 62 | description: Summon MOG for automerging, but approvals need to be against the latest 63 | commit 64 | - name: do not merge 65 | color: d93f0b 66 | description: Indicates a pull request not ready for merge, due to either quality 67 | or timing. 68 | 69 | - name: 'autorelease: pending' 70 | color: ededed 71 | description: Release please needs to do its work on this. 72 | - name: 'autorelease: triggered' 73 | color: ededed 74 | description: Release please has triggered a release for this. 75 | - name: 'autorelease: tagged' 76 | color: ededed 77 | description: Release please has completed a release for this. 78 | 79 | - name: 'tests: run' 80 | color: 3DED97 81 | description: Label to trigger Github Action tests. 82 | 83 | - name: 'flakybot: flaky' 84 | color: 86d9d7 85 | description: Tells the Flaky Bot not to close or comment on this issue. 86 | - name: 'flakybot: quiet' 87 | color: 86d9d7 88 | description: Tells the Flaky Bot to comment less. 89 | - name: 'flakybot: issue' 90 | color: a9f9f7 91 | description: An issue filed by the Flaky Bot. Should not be added manually. 92 | 93 | - name: 'k8s' 94 | color: 1D76DB 95 | description: Kubernetes related issue or functionality. 96 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | handleGHRelease: true 16 | packageName: cloud-sql-proxy 17 | releaseType: simple 18 | versionFile: "cmd/version.txt" 19 | branches: 20 | - branch: v1 21 | versionFile: "proxy/util/version.txt" 22 | handleGHRelease: true 23 | packageName: cloud-sql-proxy 24 | releaseType: simple 25 | extraFiles: 26 | - README.md 27 | - cmd/root.go 28 | - docs/cmd/cloud-sql-proxy.md 29 | - examples/k8s-health-check/README.md 30 | - examples/k8s-service/README.md 31 | - examples/k8s-sidecar/README.md 32 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended" 4 | ], 5 | "dependencyDashboardLabels": ["type: process"], 6 | "commitMessagePrefix": "deps: ", 7 | "postUpdateOptions": [ 8 | "gomodTidy" 9 | ], 10 | "prConcurrentLimit": 3, 11 | "packageRules": [ 12 | { 13 | "matchManagers": ["github-actions"], 14 | "groupName": "dependencies for github" 15 | }, 16 | { 17 | "matchManagers": ["dockerfile"], 18 | "groupName": "container images" 19 | }, 20 | { 21 | "groupName": "Non-major dependency updates", 22 | "matchManagers": ["gomod"], 23 | "matchUpdateTypes": ["minor", "patch"], 24 | }, 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.github/trusted-contribution.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Trigger presubmit tests for trusted contributors 16 | # https://github.com/googleapis/repo-automation-bots/tree/main/packages/trusted-contribution 17 | # Install: https://github.com/apps/trusted-contributions-gcf 18 | 19 | trustedContributors: 20 | - "dependabot[bot]" 21 | - "renovate-bot" 22 | - "renovate[bot]" 23 | - "forking-renovate[bot]" 24 | - "release-please[bot]" 25 | annotations: 26 | # Trigger Cloud Build tests 27 | - type: comment 28 | text: "/gcbrun" -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: "CodeQL" 16 | 17 | on: 18 | push: 19 | branches: ["main"] 20 | pull_request: 21 | branches: ["main"] 22 | paths-ignore: 23 | - "**/*.md" 24 | - "**/*.txt" 25 | 26 | # Declare default permissions as read only. 27 | permissions: read-all 28 | 29 | jobs: 30 | analyze: 31 | name: Analyze 32 | runs-on: ubuntu-latest 33 | permissions: 34 | actions: read 35 | contents: read 36 | security-events: write 37 | 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | language: ["go"] 42 | 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 46 | 47 | - name: Setup Go 48 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 49 | with: 50 | go-version: "1.24" 51 | if: ${{ matrix.language == 'go' }} 52 | 53 | # Initializes the CodeQL tools for scanning. 54 | - name: Initialize CodeQL 55 | uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 56 | with: 57 | languages: ${{ matrix.language }} 58 | 59 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 60 | # If this step fails, then you should remove it and run the build manually 61 | - name: Autobuild 62 | uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 63 | 64 | - name: Perform CodeQL Analysis 65 | uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 66 | with: 67 | category: "/language:${{matrix.language}}" 68 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: code coverage 16 | on: [pull_request] 17 | 18 | # Declare default permissions as read only. 19 | permissions: read-all 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Setup Go 26 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 27 | with: 28 | go-version: "1.24" 29 | 30 | - name: Checkout base branch 31 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 32 | with: 33 | ref: ${{ github.base_ref }} 34 | - name: Calculate base code coverage 35 | run: | 36 | go test -short -coverprofile current_cover.out ./... || true 37 | export CUR_COVER=$(go tool cover -func current_cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}') 38 | echo "CUR_COVER=$CUR_COVER" >> $GITHUB_ENV 39 | 40 | - name: Checkout PR branch 41 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 42 | - name: Calculate PR code coverage 43 | run: | 44 | go test -short -coverprofile pr_cover.out ./... || true 45 | export PR_COVER=$(go tool cover -func pr_cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}') 46 | echo "PR_COVER=$PR_COVER" >> $GITHUB_ENV 47 | 48 | - name: Verify code coverage. If your reading this and the step has failed, please add tests to cover your changes. 49 | run: | 50 | go tool cover -func pr_cover.out 51 | if [ "${{ env.PR_COVER }}" -lt "${{ env.CUR_COVER }}" ]; then 52 | exit 1; 53 | fi 54 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: docs 16 | on: 17 | pull_request: 18 | 19 | # Declare default permissions as read only. 20 | permissions: read-all 21 | 22 | jobs: 23 | docs: 24 | name: Check docs are up to date 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Setup Go 28 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 29 | with: 30 | go-version: "1.24" 31 | - name: Checkout code 32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | - name: Generate docs and fail if there are differences 34 | run: | 35 | go install ./cmd/gendocs 36 | gendocs 37 | git diff --exit-code 38 | -------------------------------------------------------------------------------- /.github/workflows/govulncheck.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: govulncheck 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | on: 21 | push: 22 | branches: 23 | - "main" 24 | - "v1" 25 | pull_request: 26 | branches: 27 | - "main" 28 | - "v1" 29 | schedule: 30 | - cron: "0 2 * * *" 31 | 32 | jobs: 33 | govulncheck_job: 34 | runs-on: ubuntu-latest 35 | name: Run govulncheck 36 | steps: 37 | - name: Setup Go 38 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 39 | with: 40 | go-version: "1.24" 41 | check-latest: true 42 | - name: Checkout code 43 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 44 | with: 45 | ref: ${{ github.event.pull_request.head.sha }} 46 | repository: ${{ github.event.pull_request.head.repo.full_name }} 47 | - id: govulncheck 48 | uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1 49 | with: 50 | # Let actions/checkout above check-out the correct SHA 51 | repo-checkout: false 52 | -------------------------------------------------------------------------------- /.github/workflows/labels.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Sync labels 16 | on: 17 | push: 18 | branches: 19 | - main 20 | 21 | # Declare default permissions as read only. 22 | permissions: read-all 23 | 24 | jobs: 25 | build: 26 | runs-on: ubuntu-latest 27 | permissions: 28 | issues: write 29 | pull-requests: write 30 | steps: 31 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 32 | - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # v1.3.0 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: lint 16 | on: 17 | pull_request: 18 | 19 | # Declare default permissions as read only. 20 | permissions: read-all 21 | 22 | jobs: 23 | lint: 24 | name: run lint 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Setup Go 28 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 29 | with: 30 | go-version: "1.24" 31 | - name: Checkout code 32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | - name: > 34 | Verify go mod tidy. If you're reading this and the check has 35 | failed, run `goimports -w . && go mod tidy && golangci-lint run` 36 | run: | 37 | go mod tidy && git diff --exit-code 38 | - name: golangci-lint 39 | uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 40 | with: 41 | version: latest 42 | args: --timeout 3m 43 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: OSSF Scorecard 16 | on: 17 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 18 | branch_protection_rule: 19 | schedule: 20 | # weekly on Sunday 21 | - cron: '0 20 * * 0' 22 | push: 23 | branches: [ "main" ] 24 | 25 | # Declare default permissions as read only. 26 | permissions: read-all 27 | 28 | jobs: 29 | analysis: 30 | name: Scorecard analysis 31 | runs-on: ubuntu-latest 32 | permissions: 33 | # Needed to upload the results to code-scanning dashboard. 34 | security-events: write 35 | 36 | steps: 37 | - name: "Checkout code" 38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 39 | with: 40 | persist-credentials: false 41 | 42 | - name: "Run analysis" 43 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 44 | with: 45 | results_file: results.sarif 46 | results_format: sarif 47 | 48 | - name: Filter SARIF to skip false positives 49 | # filter out DangerousWorkflow alerts as they do not account for safe use of labels to trigger actions 50 | env: 51 | SCORECARD_SKIPPED_RULE_IDS: "DangerousWorkflowID" 52 | run: | 53 | SCORECARD_SKIPPED_RULE_IDS_JSON=$(echo $SCORECARD_SKIPPED_RULE_IDS | jq -cR 'split(",")') 54 | # Trim the SARIF file to remove false positive detections 55 | cat results.sarif | jq '.runs[].results |= map(select(.ruleId as $id | '$SCORECARD_SKIPPED_RULE_IDS_JSON' | all($id != .)))' > resultsFiltered.sarif 56 | 57 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 58 | # format to the repository Actions tab. 59 | - name: "Upload artifact" 60 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 61 | with: 62 | name: SARIF file 63 | path: results.sarif 64 | retention-days: 5 65 | 66 | # Upload the results to GitHub's code scanning dashboard. 67 | - name: "Upload to code-scanning" 68 | uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 69 | with: 70 | sarif_file: resultsFiltered.sarif 71 | -------------------------------------------------------------------------------- /.github/workflows/v1-periodic.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: v1 periodic 16 | on: 17 | schedule: 18 | - cron: "0 2 * * *" 19 | 20 | # Declare default permissions as read only. 21 | permissions: read-all 22 | 23 | jobs: 24 | integration: 25 | name: integration tests 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | os: [macos-latest, windows-latest, ubuntu-latest] 30 | fail-fast: false 31 | permissions: 32 | contents: "read" 33 | id-token: "write" 34 | steps: 35 | - name: Checkout code 36 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 37 | with: 38 | ref: v1 39 | 40 | - name: Setup Go 41 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 42 | with: 43 | go-version: "1.24" 44 | 45 | - id: auth 46 | name: Authenticate to Google Cloud 47 | uses: google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193 # v2.1.10 48 | with: 49 | workload_identity_provider: ${{ secrets.PROVIDER_NAME }} 50 | service_account: ${{ secrets.SERVICE_ACCOUNT }} 51 | access_token_lifetime: 600s 52 | 53 | - id: secrets 54 | name: Get secrets 55 | uses: google-github-actions/get-secretmanager-secrets@a8440875e1c2892062aef9061228d4f1af8f919b # v2.2.3 56 | with: 57 | secrets: |- 58 | MYSQL_CONNECTION_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_CONNECTION_NAME 59 | MYSQL_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_USER 60 | MYSQL_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_PASS 61 | MYSQL_DB:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_DB 62 | POSTGRES_CONNECTION_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_CONNECTION_NAME 63 | POSTGRES_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_USER 64 | POSTGRES_USER_IAM:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_USER_IAM 65 | POSTGRES_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_PASS 66 | POSTGRES_DB:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_DB 67 | SQLSERVER_CONNECTION_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_CONNECTION_NAME 68 | SQLSERVER_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_USER 69 | SQLSERVER_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_PASS 70 | SQLSERVER_DB:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_DB 71 | 72 | - name: Enable fuse config (Linux) 73 | if: runner.os == 'Linux' 74 | run: | 75 | sudo sed -i 's/#user_allow_other/user_allow_other/g' /etc/fuse.conf 76 | 77 | - name: Run tests 78 | env: 79 | GOOGLE_CLOUD_PROJECT: "${{ secrets.GOOGLE_CLOUD_PROJECT }}" 80 | MYSQL_CONNECTION_NAME: "${{ steps.secrets.outputs.MYSQL_CONNECTION_NAME }}" 81 | MYSQL_USER: "${{ steps.secrets.outputs.MYSQL_USER }}" 82 | MYSQL_PASS: "${{ steps.secrets.outputs.MYSQL_PASS }}" 83 | MYSQL_DB: "${{ steps.secrets.outputs.MYSQL_DB }}" 84 | POSTGRES_CONNECTION_NAME: "${{ steps.secrets.outputs.POSTGRES_CONNECTION_NAME }}" 85 | POSTGRES_USER: "${{ steps.secrets.outputs.POSTGRES_USER }}" 86 | POSTGRES_USER_IAM: "${{ steps.secrets.outputs.POSTGRES_USER_IAM }}" 87 | POSTGRES_PASS: "${{ steps.secrets.outputs.POSTGRES_PASS }}" 88 | POSTGRES_DB: "${{ steps.secrets.outputs.POSTGRES_DB }}" 89 | SQLSERVER_CONNECTION_NAME: "${{ steps.secrets.outputs.SQLSERVER_CONNECTION_NAME }}" 90 | SQLSERVER_USER: "${{ steps.secrets.outputs.SQLSERVER_USER }}" 91 | SQLSERVER_PASS: "${{ steps.secrets.outputs.SQLSERVER_PASS }}" 92 | SQLSERVER_DB: "${{ steps.secrets.outputs.SQLSERVER_DB }}" 93 | TMPDIR: "/tmp" 94 | TMP: "${{ runner.temp }}" 95 | # specifying bash shell ensures a failure in a piped process isn't lost by using `set -eo pipefail` 96 | shell: bash 97 | run: | 98 | go test -race -v ./... | tee test_results.txt 99 | 100 | - name: Convert test output to XML 101 | if: ${{ github.event_name == 'schedule' && always() }} 102 | run: | 103 | go install github.com/jstemmer/go-junit-report/v2@latest 104 | go-junit-report -in test_results.txt -set-exit-code -out v1periodic_sponge_log.xml 105 | 106 | - name: FlakyBot (Linux) 107 | # only run flakybot on periodic (schedule) event 108 | if: ${{ github.event_name == 'schedule' && runner.os == 'Linux' && always() }} 109 | run: | 110 | curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot -o flakybot -s -L 111 | chmod +x ./flakybot 112 | ./flakybot --repo ${{github.repository}} --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} 113 | 114 | - name: FlakyBot (Windows) 115 | # only run flakybot on periodic (schedule) event 116 | if: ${{ github.event_name == 'schedule' && runner.os == 'Windows' && always() }} 117 | run: | 118 | curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot.exe -o flakybot.exe -s -L 119 | ./flakybot.exe --repo ${{github.repository}} --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} 120 | 121 | - name: FlakyBot (macOS) 122 | # only run flakybot on periodic (schedule) event 123 | if: ${{ github.event_name == 'schedule' && runner.os == 'macOS' && always() }} 124 | run: | 125 | curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot-darwin-amd64 -o flakybot -s -L 126 | chmod +x ./flakybot 127 | ./flakybot --repo ${{github.repository}} --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} 128 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # direnv 2 | .envrc 3 | 4 | # IDEs 5 | .idea/ 6 | .vscode/ 7 | 8 | /cloud-sql-proxy 9 | /cloud-sql-proxy.exe 10 | 11 | /key.json 12 | /logs/ 13 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # .golangci.yml 15 | linters: 16 | disable-all: true 17 | enable: 18 | # From https://golangci-lint.run/usage/linters/ 19 | # This is a minor deviation from the default linters 20 | - goimports 21 | - gosimple 22 | - govet 23 | - ineffassign 24 | - revive 25 | - staticcheck 26 | - unused 27 | issues: 28 | exclude-use-default: false 29 | linters-settings: 30 | revive: 31 | rules: 32 | # From https://revive.run/docs#recommended-configuration 33 | - name: blank-imports 34 | - name: context-as-argument 35 | - name: context-keys-type 36 | - name: dot-imports 37 | - name: empty-block 38 | - name: errorf 39 | - name: error-naming 40 | - name: error-return 41 | - name: error-strings 42 | - name: exported 43 | - name: if-return 44 | - name: import-shadowing 45 | - name: increment-decrement 46 | - name: indent-error-flow 47 | - name: range 48 | - name: range-val-address 49 | - name: range-val-in-closure 50 | - name: receiver-naming 51 | - name: redefines-builtin-id 52 | - name: superfluous-else 53 | - name: time-naming 54 | - name: unexported-return 55 | - name: unreachable-code 56 | - name: unused-parameter 57 | - name: var-declaration 58 | - name: var-naming 59 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). 11 | You (or your employer) retain the copyright to your contribution; this simply 12 | gives us permission to use and redistribute your contributions as part of the 13 | project. 14 | 15 | If you or your current employer have already signed the Google CLA (even if it 16 | was for a different project), you probably don't need to do it again. 17 | 18 | Visit to see your current agreements or to 19 | sign a new one. 20 | 21 | ### Review our Community Guidelines 22 | 23 | This project follows 24 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 25 | 26 | ## Contribution process 27 | 28 | ### Code Reviews 29 | 30 | All submissions, including submissions by project members, require review. We 31 | use GitHub pull requests for this purpose. Consult 32 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 33 | information on using pull requests. 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Use the latest stable golang 1.x to compile to a binary 16 | FROM --platform=$BUILDPLATFORM golang:1 as build 17 | 18 | WORKDIR /go/src/cloud-sql-proxy 19 | COPY . . 20 | 21 | ARG TARGETOS 22 | ARG TARGETARCH 23 | 24 | RUN go get ./... 25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container" 27 | 28 | # Final Stage 29 | FROM gcr.io/distroless/static:nonroot@sha256:188ddfb9e497f861177352057cb21913d840ecae6c843d39e00d44fa64daa51c 30 | 31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy" 32 | 33 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy 34 | # set the uid as an integer for compatibility with runAsNonRoot in Kubernetes 35 | USER 65532 36 | ENTRYPOINT ["/cloud-sql-proxy"] 37 | -------------------------------------------------------------------------------- /Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Use the latest stable golang 1.x to compile to a binary 16 | FROM --platform=$BUILDPLATFORM golang:1-alpine as build 17 | 18 | WORKDIR /go/src/cloud-sql-proxy 19 | COPY . . 20 | 21 | ARG TARGETOS 22 | ARG TARGETARCH 23 | 24 | RUN go get ./... 25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container.alpine" 27 | 28 | # Final stage 29 | FROM alpine:3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c 30 | 31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy" 32 | 33 | RUN apk add --no-cache \ 34 | ca-certificates \ 35 | libc6-compat 36 | # Install fuse and allow enable non-root users to mount 37 | RUN apk add --no-cache fuse && sed -i 's/^#user_allow_other$/user_allow_other/g' /etc/fuse.conf 38 | # Add a non-root user matching the nonroot user from the main container 39 | RUN addgroup -g 65532 -S nonroot && adduser -u 65532 -S nonroot -G nonroot 40 | # Set the uid as an integer for compatibility with runAsNonRoot in Kubernetes 41 | USER 65532 42 | 43 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy 44 | ENTRYPOINT ["/cloud-sql-proxy"] 45 | -------------------------------------------------------------------------------- /Dockerfile.bookworm: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Use the latest stable golang 1.x to compile to a binary 16 | FROM --platform=$BUILDPLATFORM golang:1 as build 17 | 18 | WORKDIR /go/src/cloud-sql-proxy 19 | COPY . . 20 | 21 | ARG TARGETOS 22 | ARG TARGETARCH 23 | 24 | RUN go get ./... 25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container.bookworm" 27 | 28 | # Final stage 29 | FROM gcr.io/cloud-marketplace-containers/google/debian12@sha256:12e5f5b44ddf1814696b7814c4d3a180a58b34cdcbde554b57150468f3023c61 30 | 31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy" 32 | 33 | RUN apt-get update && apt-get install -y ca-certificates 34 | # Install fuse and allow enable non-root users to mount 35 | RUN apt-get update && apt-get install -y fuse && sed -i 's/^#user_allow_other$/user_allow_other/g' /etc/fuse.conf 36 | # Add a non-root user matching the nonroot user from the main container 37 | RUN groupadd -g 65532 -r nonroot && useradd -u 65532 -g 65532 -r nonroot 38 | # Set the uid as an integer for compatibility with runAsNonRoot in Kubernetes 39 | USER 65532 40 | 41 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy 42 | ENTRYPOINT ["/cloud-sql-proxy"] 43 | -------------------------------------------------------------------------------- /Dockerfile.bullseye: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Use the latest stable golang 1.x to compile to a binary 16 | FROM --platform=$BUILDPLATFORM golang:1 as build 17 | 18 | WORKDIR /go/src/cloud-sql-proxy 19 | COPY . . 20 | 21 | ARG TARGETOS 22 | ARG TARGETARCH 23 | 24 | RUN go get ./... 25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container.bullseye" 27 | 28 | # Final stage 29 | FROM gcr.io/cloud-marketplace-containers/google/debian11@sha256:80b80968e1ad7e0d0769ecd6d14b4fbd009284049b2b7b12f2853758cc2ff337 30 | 31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy" 32 | 33 | RUN apt-get update && apt-get install -y ca-certificates 34 | # Install fuse and allow enable non-root users to mount 35 | RUN apt-get update && apt-get install -y fuse && sed -i 's/^#user_allow_other$/user_allow_other/g' /etc/fuse.conf 36 | # Add a non-root user matching the nonroot user from the main container 37 | RUN groupadd -g 65532 -r nonroot && useradd -u 65532 -g 65532 -r nonroot 38 | # Set the uid as an integer for compatibility with runAsNonRoot in Kubernetes 39 | USER 65532 40 | 41 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy 42 | ENTRYPOINT ["/cloud-sql-proxy"] 43 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /cloudsql/cloudsql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudsql 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "net" 21 | 22 | "cloud.google.com/go/cloudsqlconn" 23 | ) 24 | 25 | // Dialer dials a Cloud SQL instance and returns its database engine version. 26 | type Dialer interface { 27 | // Dial returns a connection to the specified instance. 28 | Dial(ctx context.Context, inst string, opts ...cloudsqlconn.DialOption) (net.Conn, error) 29 | // EngineVersion retrieves the provided instance's database version (e.g., 30 | // POSTGRES_14) 31 | EngineVersion(ctx context.Context, inst string) (string, error) 32 | 33 | io.Closer 34 | } 35 | 36 | // Logger is the interface used throughout the project for logging. 37 | type Logger interface { 38 | // Debugf is for reporting additional information about internal operations. 39 | Debugf(format string, args ...interface{}) 40 | // Infof is for reporting informational messages. 41 | Infof(format string, args ...interface{}) 42 | // Errorf is for reporting errors. 43 | Errorf(format string, args ...interface{}) 44 | } 45 | -------------------------------------------------------------------------------- /cmd/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func assert[T comparable](t *testing.T, want, got T) { 22 | t.Helper() 23 | if got != want { 24 | t.Errorf("got %v, want %v", got, want) 25 | } 26 | } 27 | 28 | func TestNewCommandWithConfigFile(t *testing.T) { 29 | tcs := []struct { 30 | desc string 31 | args []string 32 | setup func() 33 | assert func(t *testing.T, c *Command) 34 | }{ 35 | { 36 | desc: "toml config file", 37 | args: []string{"--config-file", "testdata/config-toml.toml"}, 38 | setup: func() {}, 39 | assert: func(t *testing.T, c *Command) { 40 | assert(t, 1, len(c.conf.Instances)) 41 | assert(t, true, c.conf.Debug) 42 | assert(t, 5555, c.conf.Port) 43 | assert(t, true, c.conf.DebugLogs) 44 | assert(t, true, c.conf.IAMAuthN) 45 | }, 46 | }, 47 | { 48 | desc: "yaml config file", 49 | args: []string{"--config-file", "testdata/config-yaml.yaml"}, 50 | setup: func() {}, 51 | assert: func(t *testing.T, c *Command) { 52 | assert(t, 1, len(c.conf.Instances)) 53 | assert(t, true, c.conf.Debug) 54 | }, 55 | }, 56 | { 57 | desc: "json config file", 58 | args: []string{"--config-file", "testdata/config-json.json"}, 59 | setup: func() {}, 60 | assert: func(t *testing.T, c *Command) { 61 | assert(t, 1, len(c.conf.Instances)) 62 | assert(t, true, c.conf.Debug) 63 | }, 64 | }, 65 | { 66 | desc: "config file with two instances", 67 | args: []string{"--config-file", "testdata/two-instances.toml"}, 68 | setup: func() {}, 69 | assert: func(t *testing.T, c *Command) { 70 | assert(t, 2, len(c.conf.Instances)) 71 | }, 72 | }, 73 | { 74 | desc: "instance argument overrides env config precedence", 75 | args: []string{"proj:region:inst"}, 76 | setup: func() { 77 | t.Setenv("CSQL_PROXY_INSTANCE_CONNECTION_NAME", "p:r:i") 78 | }, 79 | assert: func(t *testing.T, c *Command) { 80 | assert(t, "proj:region:inst", c.conf.Instances[0].Name) 81 | }, 82 | }, 83 | { 84 | desc: "instance env overrides config file precedence", 85 | args: []string{"--config-file", "testdata/config.json"}, 86 | setup: func() { 87 | t.Setenv("CSQL_PROXY_INSTANCE_CONNECTION_NAME", "p:r:i") 88 | }, 89 | assert: func(t *testing.T, c *Command) { 90 | assert(t, "p:r:i", c.conf.Instances[0].Name) 91 | }, 92 | }, 93 | { 94 | desc: "flag overrides env config precedence", 95 | args: []string{"proj:region:inst", "--debug"}, 96 | setup: func() { 97 | t.Setenv("CSQL_PROXY_DEBUG", "false") 98 | }, 99 | assert: func(t *testing.T, c *Command) { 100 | assert(t, true, c.conf.Debug) 101 | }, 102 | }, 103 | { 104 | desc: "flag overrides config file precedence", 105 | args: []string{ 106 | "proj:region:inst", 107 | "--config-file", "testdata/config.toml", 108 | "--debug", 109 | }, 110 | setup: func() {}, 111 | assert: func(t *testing.T, c *Command) { 112 | assert(t, true, c.conf.Debug) 113 | }, 114 | }, 115 | { 116 | desc: "env overrides config file precedence", 117 | args: []string{ 118 | "proj:region:inst", 119 | "--config-file", "testdata/config.toml", 120 | }, 121 | setup: func() { 122 | t.Setenv("CSQL_PROXY_DEBUG", "false") 123 | }, 124 | assert: func(t *testing.T, c *Command) { 125 | assert(t, false, c.conf.Debug) 126 | }, 127 | }, 128 | } 129 | 130 | for _, tc := range tcs { 131 | t.Run(tc.desc, func(t *testing.T) { 132 | tc.setup() 133 | 134 | cmd, err := invokeProxyCommand(tc.args) 135 | if err != nil { 136 | t.Fatalf("want error = nil, got = %v", err) 137 | } 138 | 139 | tc.assert(t, cmd) 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /cmd/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | var ( 22 | errSigInt = &exitError{ 23 | Err: errors.New("SIGINT signal received"), 24 | Code: 130, 25 | } 26 | 27 | errSigTerm = &exitError{ 28 | Err: errors.New("SIGTERM signal received"), 29 | Code: 143, 30 | } 31 | 32 | errSigTermZero = &exitError{ 33 | Err: errors.New("SIGTERM signal received"), 34 | Code: 0, 35 | } 36 | 37 | errQuitQuitQuit = &exitError{ 38 | Err: errors.New("/quitquitquit received request"), 39 | Code: 0, // This error guarantees a clean exit. 40 | } 41 | ) 42 | 43 | func newBadCommandError(msg string) error { 44 | return &exitError{ 45 | Err: errors.New(msg), 46 | Code: 1, 47 | } 48 | } 49 | 50 | // exitError is an error with an exit code, that's returned when the cmd exits. 51 | // When possible, try to match these conventions: https://tldp.org/LDP/abs/html/exitcodes.html 52 | type exitError struct { 53 | Code int 54 | Err error 55 | } 56 | 57 | func (e *exitError) Error() string { 58 | if e.Err == nil { 59 | return "" 60 | } 61 | return e.Err.Error() 62 | } 63 | -------------------------------------------------------------------------------- /cmd/gendocs/gen_cloud-sql-proxy_docs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | 22 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd" 23 | "github.com/spf13/cobra/doc" 24 | ) 25 | 26 | func main() { 27 | if len(os.Args) > 2 { 28 | fmt.Fprintf(os.Stderr, "usage: %s [output directory]\n", os.Args[0]) 29 | os.Exit(1) 30 | } 31 | 32 | path := "docs/cmd" 33 | if len(os.Args) == 2 { 34 | path = os.Args[1] 35 | } 36 | 37 | outDir, err := filepath.Abs(path) 38 | if err != nil { 39 | fmt.Fprintf(os.Stderr, "failed to get output directory: %v\n", err) 40 | os.Exit(1) 41 | } 42 | 43 | // Set environment variables used so the output is consistent, 44 | // regardless of where we run. 45 | os.Setenv("TMPDIR", "/tmp") 46 | 47 | cloudSQLProxy := cmd.NewCommand() 48 | cloudSQLProxy.Execute() 49 | cloudSQLProxy.Command.DisableAutoGenTag = true 50 | doc.GenMarkdownTree(cloudSQLProxy.Command, outDir) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql" 18 | 19 | // Option is a function that configures a Command. 20 | type Option func(*Command) 21 | 22 | // WithLogger overrides the default logger. 23 | func WithLogger(l cloudsql.Logger) Option { 24 | return func(c *Command) { 25 | c.logger = l 26 | } 27 | } 28 | 29 | // WithDialer configures the Command to use the provided dialer to connect to 30 | // Cloud SQL instances. 31 | func WithDialer(d cloudsql.Dialer) Option { 32 | return func(c *Command) { 33 | c.dialer = d 34 | } 35 | } 36 | 37 | // WithFuseDir mounts a directory at the path using FUSE to access Cloud SQL 38 | // instances. 39 | func WithFuseDir(dir string) Option { 40 | return func(c *Command) { 41 | c.conf.FUSEDir = dir 42 | } 43 | } 44 | 45 | // WithFuseTempDir sets the temp directory where Unix sockets are created with 46 | // FUSE 47 | func WithFuseTempDir(dir string) Option { 48 | return func(c *Command) { 49 | c.conf.FUSETempDir = dir 50 | } 51 | } 52 | 53 | // WithMaxConnections sets the maximum allowed number of connections. Default 54 | // is no limit. 55 | func WithMaxConnections(mc uint64) Option { 56 | return func(c *Command) { 57 | c.conf.MaxConnections = mc 58 | } 59 | } 60 | 61 | // WithUserAgent sets additional user agents for Admin API tracking and should 62 | // be a space separated list of additional user agents, e.g. 63 | // cloud-sql-proxy-operator/0.0.1,other-agent/1.0.0 64 | func WithUserAgent(agent string) Option { 65 | return func(c *Command) { 66 | c.conf.OtherUserAgents = agent 67 | } 68 | } 69 | 70 | // WithAutoIP enables legacy behavior of v1 and will try to connect to first IP 71 | // address returned by the SQL Admin API. In most cases, this flag should not 72 | // be used. Prefer default of public IP or use --private-ip instead.` 73 | func WithAutoIP() Option { 74 | return func(c *Command) { 75 | c.conf.AutoIP = true 76 | } 77 | } 78 | 79 | // WithQuietLogging configures the Proxy to log error messages only. 80 | func WithQuietLogging() Option { 81 | return func(c *Command) { 82 | c.conf.Quiet = true 83 | } 84 | } 85 | 86 | // WithDebugLogging configures the Proxy to log debug level messages. 87 | func WithDebugLogging() Option { 88 | return func(c *Command) { 89 | c.conf.DebugLogs = true 90 | } 91 | } 92 | 93 | // WithLazyRefresh configures the Proxy to refresh connection info on an 94 | // as-needed basis when the cached copy has expired. 95 | func WithLazyRefresh() Option { 96 | return func(c *Command) { 97 | c.conf.LazyRefresh = true 98 | } 99 | } 100 | 101 | // WithConnRefuseNotify configures the Proxy to call the provided function when 102 | // a connection is refused. The notification function is run in a goroutine. 103 | func WithConnRefuseNotify(n func()) Option { 104 | return func(c *Command) { 105 | c.connRefuseNotify = n 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /cmd/options_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "runtime" 22 | "testing" 23 | 24 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql" 25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log" 26 | "github.com/spf13/cobra" 27 | ) 28 | 29 | type testDialer struct { 30 | cloudsql.Dialer 31 | } 32 | 33 | func TestCommandOptions(t *testing.T) { 34 | logger := log.NewStdLogger(io.Discard, io.Discard) 35 | dialer := &testDialer{} 36 | tcs := []struct { 37 | desc string 38 | isValid func(*Command) error 39 | option Option 40 | skip bool 41 | }{ 42 | { 43 | desc: "with logger", 44 | isValid: func(c *Command) error { 45 | if c.logger != logger { 46 | return errors.New("loggers do not match") 47 | } 48 | return nil 49 | }, 50 | option: WithLogger(logger), 51 | }, 52 | { 53 | desc: "with dialer", 54 | isValid: func(c *Command) error { 55 | if c.dialer != dialer { 56 | return errors.New("dialers do not match") 57 | } 58 | return nil 59 | }, 60 | option: WithDialer(dialer), 61 | }, 62 | { 63 | desc: "with FUSE dir", 64 | isValid: func(c *Command) error { 65 | if c.conf.FUSEDir != "somedir" { 66 | return fmt.Errorf( 67 | "want = %v, got = %v", "somedir", c.conf.FUSEDir, 68 | ) 69 | } 70 | return nil 71 | }, 72 | option: WithFuseDir("somedir"), 73 | // FUSE isn't available on GitHub macOS runners 74 | // and FUSE isn't supported on Windows, so skip this test. 75 | skip: runtime.GOOS == "darwin" || runtime.GOOS == "windows", 76 | }, 77 | { 78 | desc: "with FUSE temp dir", 79 | isValid: func(c *Command) error { 80 | if c.conf.FUSETempDir != "somedir" { 81 | return fmt.Errorf( 82 | "want = %v, got = %v", "somedir", c.conf.FUSEDir, 83 | ) 84 | } 85 | return nil 86 | }, 87 | option: WithFuseTempDir("somedir"), 88 | // FUSE isn't available on GitHub macOS runners 89 | // and FUSE isn't supported on Windows, so skip this test. 90 | skip: runtime.GOOS == "darwin" || runtime.GOOS == "windows", 91 | }, 92 | { 93 | desc: "with max connections", 94 | isValid: func(c *Command) error { 95 | if c.conf.MaxConnections != 1 { 96 | return fmt.Errorf( 97 | "want = %v, got = %v", 1, c.conf.MaxConnections, 98 | ) 99 | } 100 | return nil 101 | }, 102 | option: WithMaxConnections(1), 103 | }, 104 | { 105 | desc: "with user agent", 106 | isValid: func(c *Command) error { 107 | if c.conf.OtherUserAgents != "agents-go-here" { 108 | return fmt.Errorf( 109 | "want = %v, got = %v", 110 | "agents-go-here", c.conf.OtherUserAgents, 111 | ) 112 | } 113 | return nil 114 | }, 115 | option: WithUserAgent("agents-go-here"), 116 | }, 117 | { 118 | desc: "with auto IP", 119 | isValid: func(c *Command) error { 120 | if !c.conf.AutoIP { 121 | return errors.New("auto IP was false, but should be true") 122 | } 123 | return nil 124 | }, 125 | option: WithAutoIP(), 126 | }, 127 | { 128 | desc: "with quiet logging", 129 | isValid: func(c *Command) error { 130 | if !c.conf.Quiet { 131 | return errors.New("quiet was false, but should be true") 132 | } 133 | return nil 134 | }, 135 | option: WithQuietLogging(), 136 | }, 137 | { 138 | desc: "with lazy refresh", 139 | isValid: func(c *Command) error { 140 | if !c.conf.LazyRefresh { 141 | return errors.New( 142 | "LazyRefresh was false, but should be true", 143 | ) 144 | } 145 | return nil 146 | }, 147 | option: WithLazyRefresh(), 148 | }, 149 | } 150 | 151 | for _, tc := range tcs { 152 | t.Run(tc.desc, func(t *testing.T) { 153 | if tc.skip { 154 | t.Skip("skipping unsupported test case") 155 | } 156 | got, err := invokeProxyWithOption(nil, tc.option) 157 | if err != nil { 158 | t.Fatal(err) 159 | } 160 | if err := tc.isValid(got); err != nil { 161 | t.Errorf("option did not initialize command correctly: %v", err) 162 | } 163 | }) 164 | } 165 | } 166 | 167 | func TestCommandOptionsOverridesCLI(t *testing.T) { 168 | tcs := []struct { 169 | desc string 170 | isValid func(*Command) error 171 | option Option 172 | args []string 173 | }{ 174 | { 175 | desc: "with duplicate max connections", 176 | isValid: func(c *Command) error { 177 | if c.conf.MaxConnections != 10 { 178 | return errors.New("max connections do not match") 179 | } 180 | return nil 181 | }, 182 | option: WithMaxConnections(10), 183 | args: []string{"--max-connections", "20"}, 184 | }, 185 | { 186 | desc: "with quiet logging", 187 | isValid: func(c *Command) error { 188 | if !c.conf.Quiet { 189 | return errors.New("quiet was false, but should be true") 190 | } 191 | return nil 192 | }, 193 | option: WithQuietLogging(), 194 | args: []string{"--quiet", "false"}, 195 | }, 196 | } 197 | for _, tc := range tcs { 198 | t.Run(tc.desc, func(t *testing.T) { 199 | got, err := invokeProxyWithOption(tc.args, tc.option) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | if err := tc.isValid(got); err != nil { 204 | t.Errorf("option did not initialize command correctly: %v", err) 205 | } 206 | }) 207 | } 208 | } 209 | 210 | func invokeProxyWithOption(args []string, o Option) (*Command, error) { 211 | c := NewCommand(o) 212 | // Keep the test output quiet 213 | c.SilenceUsage = true 214 | c.SilenceErrors = true 215 | // Disable execute behavior 216 | c.RunE = func(*cobra.Command, []string) error { 217 | return nil 218 | } 219 | args = append(args, "test-project:us-central1:test-instance") 220 | c.SetArgs(args) 221 | 222 | err := c.Execute() 223 | 224 | return c, err 225 | } 226 | -------------------------------------------------------------------------------- /cmd/root_linux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "os" 21 | "path/filepath" 22 | "testing" 23 | "time" 24 | 25 | "github.com/coreos/go-systemd/v22/daemon" 26 | "github.com/spf13/cobra" 27 | ) 28 | 29 | func TestNewCommandArgumentsOnLinux(t *testing.T) { 30 | defaultTmp := filepath.Join(os.TempDir(), "csql-tmp") 31 | tcs := []struct { 32 | desc string 33 | args []string 34 | wantDir string 35 | wantTempDir string 36 | }{ 37 | { 38 | desc: "using the fuse flag", 39 | args: []string{"--fuse", "/cloudsql"}, 40 | wantDir: "/cloudsql", 41 | wantTempDir: defaultTmp, 42 | }, 43 | { 44 | desc: "using the fuse temporary directory flag", 45 | args: []string{"--fuse", "/cloudsql", "--fuse-tmp-dir", "/mycooldir"}, 46 | wantDir: "/cloudsql", 47 | wantTempDir: "/mycooldir", 48 | }, 49 | } 50 | 51 | for _, tc := range tcs { 52 | t.Run(tc.desc, func(t *testing.T) { 53 | c := NewCommand() 54 | // Keep the test output quiet 55 | c.SilenceUsage = true 56 | c.SilenceErrors = true 57 | // Disable execute behavior 58 | c.RunE = func(*cobra.Command, []string) error { 59 | return nil 60 | } 61 | c.SetArgs(tc.args) 62 | 63 | err := c.Execute() 64 | if err != nil { 65 | t.Fatalf("want error = nil, got = %v", err) 66 | } 67 | 68 | if got, want := c.conf.FUSEDir, tc.wantDir; got != want { 69 | t.Fatalf("FUSEDir: want = %v, got = %v", want, got) 70 | } 71 | 72 | if got, want := c.conf.FUSETempDir, tc.wantTempDir; got != want { 73 | t.Fatalf("FUSEDir: want = %v, got = %v", want, got) 74 | } 75 | }) 76 | } 77 | } 78 | 79 | func TestSdNotifyOnLinux(t *testing.T) { 80 | tcs := []struct { 81 | desc string 82 | proxyMustFail bool 83 | notifyState string 84 | }{ 85 | { 86 | desc: "System with systemd Type=notify and proxy started successfully", 87 | proxyMustFail: false, 88 | notifyState: daemon.SdNotifyReady, 89 | }, 90 | { 91 | desc: "System with systemd Type=notify and proxy failed to start", 92 | proxyMustFail: true, 93 | notifyState: daemon.SdNotifyStopping, 94 | }, 95 | } 96 | 97 | // Create a temp dir for the socket file. 98 | testDir, err := os.MkdirTemp("/tmp/", "test-") 99 | if err != nil { 100 | t.Fatalf("Fail to create the temp dir: %v", err) 101 | } 102 | defer os.RemoveAll(testDir) 103 | 104 | //Set up the socket stream to listen for notifications. 105 | socketAddr := filepath.Join(testDir, "notify-socket.sock") 106 | conn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: socketAddr, Net: "unixgram"}) 107 | if err != nil { 108 | t.Fatalf("net.ListenUnixgram error: %v", err) 109 | } 110 | 111 | // To simulate systemd behavior with Type=notify, set NOTIFY_SOCKET 112 | // to the name of the socket that listens for notifications. 113 | os.Setenv("NOTIFY_SOCKET", socketAddr) 114 | defer os.Unsetenv("NOTIFY_SOCKET") 115 | 116 | s := &spyDialer{} 117 | c := NewCommand(WithDialer(s)) 118 | // Keep the test output quiet 119 | c.SilenceUsage = false 120 | c.SilenceErrors = false 121 | c.SetArgs([]string{"my-project:my-region:my-instance"}) 122 | 123 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 124 | defer cancel() 125 | 126 | for _, tc := range tcs { 127 | t.Run(tc.desc, func(t *testing.T) { 128 | 129 | if tc.proxyMustFail { 130 | c.conf.FUSEDir = "invalid" 131 | } 132 | 133 | go c.ExecuteContext(ctx) 134 | 135 | stateReceived := make([]byte, 4096) 136 | length, _, err := conn.ReadFromUnix(stateReceived) 137 | if err != nil { 138 | t.Fatalf("conn.ReadFromUnix error: %s\n", err) 139 | } 140 | if string(stateReceived[0:length]) != tc.notifyState { 141 | t.Fatalf("Expected Notify State %v, got %v", tc.notifyState, string(stateReceived)) 142 | } 143 | 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /cmd/root_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func TestWindowsDoesNotSupportFUSE(t *testing.T) { 24 | c := NewCommand() 25 | // Keep the test output quiet 26 | c.SilenceUsage = true 27 | c.SilenceErrors = true 28 | // Disable execute behavior 29 | c.RunE = func(*cobra.Command, []string) error { return nil } 30 | c.SetArgs([]string{"--fuse"}) 31 | 32 | err := c.Execute() 33 | if err == nil { 34 | t.Fatal("want error != nil, got = nil") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cmd/testdata/config-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "instance-connection-name": "proj:region:inst", 3 | "debug": true 4 | } 5 | -------------------------------------------------------------------------------- /cmd/testdata/config-toml.toml: -------------------------------------------------------------------------------- 1 | instance-connection-name = "proj:region:inst" 2 | debug = true 3 | port = "5555" 4 | debug-logs = true 5 | auto-iam-authn = true 6 | -------------------------------------------------------------------------------- /cmd/testdata/config-yaml.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | instance-connection-name: "proj:region:inst" 16 | debug: true 17 | -------------------------------------------------------------------------------- /cmd/testdata/two-instances.toml: -------------------------------------------------------------------------------- 1 | instance-connection-name-0 = "x:y:z" 2 | instance-connection-name-1 = "a:b:c" 3 | -------------------------------------------------------------------------------- /cmd/version.txt: -------------------------------------------------------------------------------- 1 | 2.16.0 2 | -------------------------------------------------------------------------------- /cmd/wait_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "io" 19 | "net" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestWaitCommandFlags(t *testing.T) { 25 | ln, err := net.Listen("tcp", "localhost:0") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | defer ln.Close() 30 | host, port, err := net.SplitHostPort(ln.Addr().String()) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | go func() { 35 | conn, err := ln.Accept() 36 | if err != nil { 37 | return 38 | } 39 | defer conn.Close() 40 | // Use a read deadline to produce read error 41 | conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) 42 | // Read client request first. 43 | io.ReadAll(conn) 44 | // Write a generic 200 response back. 45 | conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) 46 | }() 47 | 48 | _, err = invokeProxyCommand([]string{ 49 | "wait", 50 | "--http-address", host, 51 | "--http-port", port, 52 | "--max=1s", 53 | }) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | } 58 | 59 | func TestWaitCommandFails(t *testing.T) { 60 | _, err := invokeProxyCommand([]string{ 61 | "wait", 62 | // assuming default host and port 63 | "--max=100ms", 64 | }) 65 | if err == nil { 66 | t.Fatal("wait should fail when endpoint does not respond") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/cmd/cloud-sql-proxy_completion.md: -------------------------------------------------------------------------------- 1 | ## cloud-sql-proxy completion 2 | 3 | Generate the autocompletion script for the specified shell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for cloud-sql-proxy for the specified shell. 8 | See each sub-command's help for details on how to use the generated script. 9 | 10 | 11 | ### Options 12 | 13 | ``` 14 | -h, --help help for completion 15 | ``` 16 | 17 | ### Options inherited from parent commands 18 | 19 | ``` 20 | --http-address string Address for Prometheus and health check server (default "localhost") 21 | --http-port string Port for Prometheus and health check server (default "9090") 22 | ``` 23 | 24 | ### SEE ALSO 25 | 26 | * [cloud-sql-proxy](cloud-sql-proxy.md) - cloud-sql-proxy authorizes and encrypts connections to Cloud SQL. 27 | * [cloud-sql-proxy completion bash](cloud-sql-proxy_completion_bash.md) - Generate the autocompletion script for bash 28 | * [cloud-sql-proxy completion fish](cloud-sql-proxy_completion_fish.md) - Generate the autocompletion script for fish 29 | * [cloud-sql-proxy completion powershell](cloud-sql-proxy_completion_powershell.md) - Generate the autocompletion script for powershell 30 | * [cloud-sql-proxy completion zsh](cloud-sql-proxy_completion_zsh.md) - Generate the autocompletion script for zsh 31 | 32 | -------------------------------------------------------------------------------- /docs/cmd/cloud-sql-proxy_completion_bash.md: -------------------------------------------------------------------------------- 1 | ## cloud-sql-proxy completion bash 2 | 3 | Generate the autocompletion script for bash 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the bash shell. 8 | 9 | This script depends on the 'bash-completion' package. 10 | If it is not installed already, you can install it via your OS's package manager. 11 | 12 | To load completions in your current shell session: 13 | 14 | source <(cloud-sql-proxy completion bash) 15 | 16 | To load completions for every new session, execute once: 17 | 18 | #### Linux: 19 | 20 | cloud-sql-proxy completion bash > /etc/bash_completion.d/cloud-sql-proxy 21 | 22 | #### macOS: 23 | 24 | cloud-sql-proxy completion bash > $(brew --prefix)/etc/bash_completion.d/cloud-sql-proxy 25 | 26 | You will need to start a new shell for this setup to take effect. 27 | 28 | 29 | ``` 30 | cloud-sql-proxy completion bash 31 | ``` 32 | 33 | ### Options 34 | 35 | ``` 36 | -h, --help help for bash 37 | --no-descriptions disable completion descriptions 38 | ``` 39 | 40 | ### Options inherited from parent commands 41 | 42 | ``` 43 | --http-address string Address for Prometheus and health check server (default "localhost") 44 | --http-port string Port for Prometheus and health check server (default "9090") 45 | ``` 46 | 47 | ### SEE ALSO 48 | 49 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell 50 | 51 | -------------------------------------------------------------------------------- /docs/cmd/cloud-sql-proxy_completion_fish.md: -------------------------------------------------------------------------------- 1 | ## cloud-sql-proxy completion fish 2 | 3 | Generate the autocompletion script for fish 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the fish shell. 8 | 9 | To load completions in your current shell session: 10 | 11 | cloud-sql-proxy completion fish | source 12 | 13 | To load completions for every new session, execute once: 14 | 15 | cloud-sql-proxy completion fish > ~/.config/fish/completions/cloud-sql-proxy.fish 16 | 17 | You will need to start a new shell for this setup to take effect. 18 | 19 | 20 | ``` 21 | cloud-sql-proxy completion fish [flags] 22 | ``` 23 | 24 | ### Options 25 | 26 | ``` 27 | -h, --help help for fish 28 | --no-descriptions disable completion descriptions 29 | ``` 30 | 31 | ### Options inherited from parent commands 32 | 33 | ``` 34 | --http-address string Address for Prometheus and health check server (default "localhost") 35 | --http-port string Port for Prometheus and health check server (default "9090") 36 | ``` 37 | 38 | ### SEE ALSO 39 | 40 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell 41 | 42 | -------------------------------------------------------------------------------- /docs/cmd/cloud-sql-proxy_completion_powershell.md: -------------------------------------------------------------------------------- 1 | ## cloud-sql-proxy completion powershell 2 | 3 | Generate the autocompletion script for powershell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for powershell. 8 | 9 | To load completions in your current shell session: 10 | 11 | cloud-sql-proxy completion powershell | Out-String | Invoke-Expression 12 | 13 | To load completions for every new session, add the output of the above command 14 | to your powershell profile. 15 | 16 | 17 | ``` 18 | cloud-sql-proxy completion powershell [flags] 19 | ``` 20 | 21 | ### Options 22 | 23 | ``` 24 | -h, --help help for powershell 25 | --no-descriptions disable completion descriptions 26 | ``` 27 | 28 | ### Options inherited from parent commands 29 | 30 | ``` 31 | --http-address string Address for Prometheus and health check server (default "localhost") 32 | --http-port string Port for Prometheus and health check server (default "9090") 33 | ``` 34 | 35 | ### SEE ALSO 36 | 37 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell 38 | 39 | -------------------------------------------------------------------------------- /docs/cmd/cloud-sql-proxy_completion_zsh.md: -------------------------------------------------------------------------------- 1 | ## cloud-sql-proxy completion zsh 2 | 3 | Generate the autocompletion script for zsh 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the zsh shell. 8 | 9 | If shell completion is not already enabled in your environment you will need 10 | to enable it. You can execute the following once: 11 | 12 | echo "autoload -U compinit; compinit" >> ~/.zshrc 13 | 14 | To load completions in your current shell session: 15 | 16 | source <(cloud-sql-proxy completion zsh) 17 | 18 | To load completions for every new session, execute once: 19 | 20 | #### Linux: 21 | 22 | cloud-sql-proxy completion zsh > "${fpath[1]}/_cloud-sql-proxy" 23 | 24 | #### macOS: 25 | 26 | cloud-sql-proxy completion zsh > $(brew --prefix)/share/zsh/site-functions/_cloud-sql-proxy 27 | 28 | You will need to start a new shell for this setup to take effect. 29 | 30 | 31 | ``` 32 | cloud-sql-proxy completion zsh [flags] 33 | ``` 34 | 35 | ### Options 36 | 37 | ``` 38 | -h, --help help for zsh 39 | --no-descriptions disable completion descriptions 40 | ``` 41 | 42 | ### Options inherited from parent commands 43 | 44 | ``` 45 | --http-address string Address for Prometheus and health check server (default "localhost") 46 | --http-port string Port for Prometheus and health check server (default "9090") 47 | ``` 48 | 49 | ### SEE ALSO 50 | 51 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell 52 | 53 | -------------------------------------------------------------------------------- /docs/cmd/cloud-sql-proxy_wait.md: -------------------------------------------------------------------------------- 1 | ## cloud-sql-proxy wait 2 | 3 | Wait for another Proxy process to start 4 | 5 | ### Synopsis 6 | 7 | 8 | Waiting for Proxy Startup 9 | 10 | Sometimes it is necessary to wait for the Proxy to start. 11 | 12 | To help ensure the Proxy is up and ready, the Proxy includes a wait 13 | subcommand with an optional --max flag to set the maximum time to wait. 14 | The wait command uses a separate Proxy's startup endpoint to determine 15 | if the other Proxy process is ready. 16 | 17 | Invoke the wait command, like this: 18 | 19 | # waits for another Proxy process' startup endpoint to respond 20 | ./cloud-sql-proxy wait 21 | 22 | Configuration 23 | 24 | By default, the Proxy will wait up to the maximum time for the startup 25 | endpoint to respond. The wait command requires that the Proxy be started in 26 | another process with the HTTP health check or Prometheus enabled. If an 27 | alternate health check port or address is used, as in: 28 | 29 | ./cloud-sql-proxy \ 30 | --http-address 0.0.0.0 \ 31 | --http-port 9191 \ 32 | --health-check 33 | 34 | Then the wait command must also be told to use the same custom values: 35 | 36 | ./cloud-sql-proxy wait \ 37 | --http-address 0.0.0.0 \ 38 | --http-port 9191 39 | 40 | By default the wait command will wait 30 seconds. To alter this value, 41 | use: 42 | 43 | ./cloud-sql-proxy wait --max 10s 44 | 45 | 46 | ``` 47 | cloud-sql-proxy wait [flags] 48 | ``` 49 | 50 | ### Options 51 | 52 | ``` 53 | -h, --help help for wait 54 | -m, --max duration maximum amount of time to wait for startup (default 30s) 55 | ``` 56 | 57 | ### Options inherited from parent commands 58 | 59 | ``` 60 | --http-address string Address for Prometheus and health check server (default "localhost") 61 | --http-port string Port for Prometheus and health check server (default "9090") 62 | ``` 63 | 64 | ### SEE ALSO 65 | 66 | * [cloud-sql-proxy](cloud-sql-proxy.md) - cloud-sql-proxy authorizes and encrypts connections to Cloud SQL. 67 | 68 | -------------------------------------------------------------------------------- /examples/disaster-recovery/README.md: -------------------------------------------------------------------------------- 1 | # Coordinate disaster recovery with Secret Manager 2 | 3 | ## Background 4 | 5 | This document assumes you are already using the following strategy for 6 | detecting and triggering failovers: 7 | 1. Using an independent service to detect when the primary is down 8 | 2. Trigger a promotion of an existing read replica to become a primary 9 | 3. Update a Secret Manager secret with the name of the current primary 10 | 11 | ## Restart Auth Proxy when secret changes 12 | 13 | This option uses a wrapper script around the Cloud SQL Auth Proxy to detect 14 | when the secret has changed, and restart the Proxy with the new value. This 15 | could be done in many languages, but here’s an example using bash: 16 | 17 | > [failover.sh](examples/disaster-recovery/failover.sh) 18 | ```sh 19 | #! /bin/bash 20 | 21 | SECRET_ID="my-secret-id" # TODO(developer): replace this value 22 | REFRESH_INTERVAL=5 23 | PORT=5432 24 | 25 | # Get the latest version of the secret and start the Proxy 26 | INSTANCE=$(gcloud secrets versions access "latest" --secret="$SECRET_ID") 27 | cloud-sql-proxy --port "$PORT" "$INSTANCE" & 28 | PID=$! 29 | 30 | # Every 5s, get the latest version of the secret. If it's changed, restart the 31 | # Proxy with the new value. 32 | while true; do 33 | sleep $REFRESH_INTERVAL 34 | NEW=$(gcloud secrets versions access "latest" --secret="$SECRET_ID") 35 | if [ "$INSTANCE" != "$NEW" ]; then 36 | INSTANCE=$NEW 37 | kill $PID 38 | wait $PID 39 | cloud-sql-proxy --port "$PORT" "$INSTANCE" & 40 | PID=$! 41 | fi 42 | done 43 | ``` 44 | 45 | ## Benefits of this approach 46 | 47 | Using this approach will help assist with failovers without needing to 48 | reconfigure your application. Instead, by changing the Proxy the application 49 | will always connect to 127.0.0.1 and won’t need to restart to apply 50 | configuration changes. Additionally, it will prevent split brain syndrome by 51 | ensuring that your application can only connect to the current “primary”. 52 | -------------------------------------------------------------------------------- /examples/disaster-recovery/failover.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # [START cloud_sql_proxy_secret_manager_failover] 16 | #! /bin/bash 17 | 18 | SECRET_ID="my-secret-id" # TODO(developer): replace this value 19 | PORT=5432 20 | 21 | # Get the latest version of the secret and start the proxy 22 | INSTANCE=$(gcloud secrets versions access "latest" --secret="$SECRET_ID") 23 | cloud-sql-proxy --port "$PORT" "$INSTANCE" & 24 | PID=$! 25 | 26 | # Every 5s, get the latest version of the secret. If it's changed, restart the 27 | # proxy with the new value. 28 | while true; do 29 | sleep 5 30 | NEW=$(gcloud secrets versions access "latest" --secret="$SECRET_ID") 31 | if [ "$INSTANCE" != "$NEW" ]; then 32 | INSTANCE=$NEW 33 | kill $PID 34 | wait $PID 35 | cloud-sql-proxy --port "$PORT" "$INSTANCE" & 36 | PID=$! 37 | fi 38 | done 39 | # [END cloud_sql_proxy_secret_manager_failover] 40 | 41 | -------------------------------------------------------------------------------- /examples/k8s-service/ca_csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": [], 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "US", 10 | "L": "Boulder", 11 | "O": "My Cool Self-Signing Certificate Authority", 12 | "OU": "WWW", 13 | "ST": "Colorado" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/k8s-service/deployment.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: 19 | spec: 20 | replicas: 5 21 | selector: 22 | matchLabels: 23 | app: 24 | template: 25 | metadata: 26 | labels: 27 | app: 28 | spec: 29 | serviceAccountName: 30 | volumes: 31 | - name: cacert 32 | secret: 33 | secretName: 34 | items: 35 | - key: tls.crt 36 | path: cert.pem 37 | containers: 38 | - name: 39 | image: 40 | ports: 41 | - containerPort: 8080 42 | volumeMounts: 43 | - name: cacert 44 | mountPath: "/etc/ca" 45 | readOnly: true 46 | env: 47 | - name: DB_HOST 48 | value: ".default.svc.cluster.local" # using the "default" namespace 49 | - name: DB_USER 50 | valueFrom: 51 | secretKeyRef: 52 | name: 53 | key: username 54 | - name: DB_PASS 55 | valueFrom: 56 | secretKeyRef: 57 | name: 58 | key: password 59 | - name: DB_NAME 60 | valueFrom: 61 | secretKeyRef: 62 | name: 63 | key: database 64 | - name: DB_PORT 65 | value: "5432" 66 | - name: CA_CERT 67 | value: "/etc/ca/cert.pem" 68 | -------------------------------------------------------------------------------- /examples/k8s-service/pgbouncer_deployment.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: 23 | template: 24 | metadata: 25 | labels: 26 | app: 27 | spec: 28 | serviceAccountName: 29 | volumes: 30 | - name: cacert 31 | secret: 32 | secretName: 33 | items: 34 | - key: tls.crt 35 | path: cert.pem 36 | - name: servercert 37 | secret: 38 | secretName: 39 | items: 40 | - key: tls.crt 41 | path: cert.pem 42 | - key: tls.key 43 | path: key.pem 44 | containers: 45 | - name: pgbouncer 46 | image: 47 | ports: 48 | - containerPort: 5432 49 | volumeMounts: 50 | - name: cacert 51 | mountPath: "/etc/ca" 52 | readOnly: true 53 | - name: servercert 54 | mountPath: "/etc/server" 55 | readOnly: true 56 | env: 57 | - name: DB_HOST 58 | value: "127.0.0.1" 59 | - name: DB_USER 60 | valueFrom: 61 | secretKeyRef: 62 | name: 63 | key: username 64 | - name: DB_PASSWORD 65 | valueFrom: 66 | secretKeyRef: 67 | name: 68 | key: password 69 | - name: DB_NAME 70 | valueFrom: 71 | secretKeyRef: 72 | name: 73 | key: database 74 | - name: DB_PORT 75 | value: "5431" 76 | - name: CLIENT_TLS_SSLMODE 77 | value: "require" 78 | - name: CLIENT_TLS_CA_FILE 79 | value: "/etc/ca/cert.pem" 80 | - name: CLIENT_TLS_KEY_FILE 81 | value: "/etc/server/key.pem" 82 | - name: CLIENT_TLS_CERT_FILE 83 | value: "/etc/server/cert.pem" 84 | initContainers: 85 | - name: cloud-sql-proxy 86 | restartPolicy: Always 87 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy 88 | # Make sure to update on a regular schedule! 89 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.11.4 90 | args: 91 | - "--port=5431" 92 | - "" 93 | securityContext: 94 | runAsNonRoot: true 95 | -------------------------------------------------------------------------------- /examples/k8s-service/pgbouncer_service.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: 19 | spec: 20 | selector: 21 | app: 22 | ports: 23 | - protocol: TCP 24 | port: 5432 25 | targetPort: 5432 26 | -------------------------------------------------------------------------------- /examples/k8s-service/server_csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": [ 3 | "pgbouncersvc.default.svc.cluster.local", 4 | "localhost" 5 | ], 6 | "key": { 7 | "algo": "rsa", 8 | "size": 2048 9 | }, 10 | "names": [ 11 | { 12 | "C": "US", 13 | "L": "Boulder", 14 | "O": "My Cool Kubernetes Cluster", 15 | "OU": "WWW", 16 | "ST": "Colorado" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/k8s-sidecar/job_with_shutdown_hook.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ### 16 | # This demonstrates how to configure a batch job so that it shuts down 17 | # the proxy containers when it has finished processing. 18 | # 19 | # The main job container should send a POST request to the proxy's /quitquitquit 20 | # api when the job process finishes. This will cause the proxy side-car 21 | # container to shut down. 22 | # 23 | # In Kubernetes 1.28, side-car containers will be properly supported, and this 24 | # extra step will become unnecessary. 25 | # 26 | # See https://github.com/kubernetes/enhancements/issues/753 27 | # and https://github.com/GoogleCloudPlatform/cloud-sql-proxy-operator/issues/381 28 | 29 | apiVersion: batch/v1 30 | kind: Job 31 | metadata: 32 | name: job 33 | labels: 34 | app: busybox 35 | spec: 36 | template: 37 | metadata: 38 | creationTimestamp: null 39 | labels: 40 | app: busybox 41 | spec: 42 | containers: 43 | - name: my-application 44 | # Note: This demonstrates a way to run the proxy in an older 45 | # kubernetes cluster that does not support native sidecar containers. 46 | # It is better to run the job as a native sidecar container. 47 | # 48 | # See the Kubernetes documentation: 49 | # https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/ 50 | # 51 | # Run your batch job command. 52 | # Then, send a HTTTP POST request to the proxy sidecar container's 53 | # /quitquitquit api. This will cause the proxy process to exit. 54 | command: 55 | - sh 56 | - -c 57 | - > 58 | my_batch_job --host=127.0.0.1 --port= --username= --dbname= 59 | curl http://localhost:9091/quitquitquit 60 | image: busybox 61 | imagePullPolicy: IfNotPresent 62 | resources: {} 63 | terminationMessagePath: /dev/termination-log 64 | terminationMessagePolicy: File 65 | - name: cloud-sql-proxy 66 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy 67 | # Make sure to update on a regular schedule! 68 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1 69 | args: 70 | # Enable the admin api server on port 9091 71 | - "--admin-port=9091" 72 | # Enable the /quitquitquit admin api endpoint 73 | - "--quitquitquit" 74 | 75 | # Tell the proxy to exit gracefully if it receives a SIGTERM 76 | - "--exit-zero-on-sigterm" 77 | 78 | # Replace DB_PORT with the port the proxy should listen on 79 | - "--port=" 80 | - "" 81 | 82 | securityContext: 83 | runAsNonRoot: true 84 | resources: 85 | requests: 86 | memory: "2Gi" 87 | cpu: "1" 88 | restartPolicy: Never 89 | terminationGracePeriodSeconds: 30 90 | -------------------------------------------------------------------------------- /examples/k8s-sidecar/job_with_sidecar.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ### 16 | # This demonstrates how to configure a batch job so that it shuts down 17 | # the proxy containers when it has finished processing. 18 | # 19 | # This works in Kubernetes 1.29 and higher, demonstrating how to run the proxy 20 | # using a native side-car container. 21 | # 22 | # See https://github.com/kubernetes/enhancements/issues/753 23 | # and https://github.com/GoogleCloudPlatform/cloud-sql-proxy-operator/issues/381 24 | 25 | apiVersion: batch/v1 26 | kind: Job 27 | metadata: 28 | name: job 29 | labels: 30 | app: busybox 31 | spec: 32 | template: 33 | metadata: 34 | creationTimestamp: null 35 | labels: 36 | app: busybox 37 | spec: 38 | containers: 39 | - name: my-application 40 | # Run your batch job command. 41 | # Then, send a HTTTP POST request to the proxy sidecar container's 42 | # /quitquitquit api. This will cause the proxy process to exit. 43 | command: 44 | - my_batch_job 45 | - --host=127.0.0.1 46 | - --port= 47 | - --username= 48 | - --dbname= 49 | image: my-application-image 50 | imagePullPolicy: IfNotPresent 51 | resources: {} 52 | terminationMessagePath: /dev/termination-log 53 | terminationMessagePolicy: File 54 | restartPolicy: Never 55 | terminationGracePeriodSeconds: 30 56 | initContainers: 57 | - name: cloud-sql-proxy 58 | restartPolicy: Always 59 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy 60 | # Make sure to update on a regular schedule! 61 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.3 62 | args: 63 | # Enable the admin api server on port 9091 64 | - "--admin-port=9091" 65 | 66 | # Tell the proxy to exit gracefully if it receives a SIGTERM 67 | - "--exit-zero-on-sigterm" 68 | 69 | # Replace DB_PORT with the port the proxy should listen on 70 | - "--port=" 71 | - "" 72 | securityContext: 73 | runAsNonRoot: true 74 | resources: 75 | requests: 76 | memory: "2Gi" 77 | cpu: "1" 78 | -------------------------------------------------------------------------------- /examples/k8s-sidecar/no_proxy_private_ip.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: 23 | template: 24 | metadata: 25 | labels: 26 | app: 27 | spec: 28 | containers: 29 | - name: 30 | # ... other container configuration 31 | env: 32 | - name: DB_USER 33 | valueFrom: 34 | secretKeyRef: 35 | name: 36 | key: username 37 | - name: DB_PASS 38 | valueFrom: 39 | secretKeyRef: 40 | name: 41 | key: password 42 | - name: DB_NAME 43 | valueFrom: 44 | secretKeyRef: 45 | name: 46 | key: database 47 | # [START cloud_sql_proxy_secret_host] 48 | - name: DB_HOST 49 | valueFrom: 50 | secretKeyRef: 51 | name: 52 | key: db_host 53 | # [END cloud_sql_proxy_secret_host] 54 | -------------------------------------------------------------------------------- /examples/k8s-sidecar/proxy_with_sa_key.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: 23 | template: 24 | metadata: 25 | labels: 26 | app: 27 | spec: 28 | containers: 29 | - name: 30 | # ... other container configuration 31 | env: 32 | - name: DB_USER 33 | valueFrom: 34 | secretKeyRef: 35 | name: 36 | key: username 37 | - name: DB_PASS 38 | valueFrom: 39 | secretKeyRef: 40 | name: 41 | key: password 42 | - name: DB_NAME 43 | valueFrom: 44 | secretKeyRef: 45 | name: 46 | key: database 47 | initContainers: 48 | - name: cloud-sql-proxy 49 | restartPolicy: Always 50 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy 51 | # Make sure to update on a regular schedule! 52 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1 53 | args: 54 | # If connecting from a VPC-native GKE cluster, you can use the 55 | # following flag to have the proxy connect over private IP 56 | # - "--private-ip" 57 | 58 | # If you are not connecting with Automatic IAM AuthN, you can delete 59 | # the following flag. 60 | - "--auto-iam-authn" 61 | 62 | # Enable structured logging with LogEntry format: 63 | - "--structured-logs" 64 | 65 | # Replace DB_PORT with the port the proxy should listen on 66 | - "--port=" 67 | - "" 68 | 69 | # [START cloud_sql_proxy_k8s_volume_mount] 70 | # This flag specifies where the service account key can be found 71 | - "--credentials-file=/secrets/service_account.json" 72 | securityContext: 73 | # The default Cloud SQL Auth Proxy image runs as the 74 | # "nonroot" user and group (uid: 65532) by default. 75 | runAsNonRoot: true 76 | volumeMounts: 77 | - name: 78 | mountPath: /secrets/ 79 | readOnly: true 80 | # [END cloud_sql_proxy_k8s_volume_mount] 81 | # Resource configuration depends on an application's requirements. You 82 | # should adjust the following values based on what your application 83 | # needs. For details, see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 84 | resources: 85 | requests: 86 | # The proxy's memory use scales linearly with the number of active 87 | # connections. Fewer open connections will use less memory. Adjust 88 | # this value based on your application's requirements. 89 | memory: "2Gi" 90 | # The proxy's CPU use scales linearly with the amount of IO between 91 | # the database and the application. Adjust this value based on your 92 | # application's requirements. 93 | cpu: "1" 94 | # [START cloud_sql_proxy_k8s_volume_secret] 95 | volumes: 96 | - name: 97 | secret: 98 | secretName: 99 | # [END cloud_sql_proxy_k8s_volume_secret] 100 | -------------------------------------------------------------------------------- /examples/k8s-sidecar/proxy_with_workload_identity.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # [START cloud_sql_proxy_k8s_sa] 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: 20 | spec: 21 | selector: 22 | matchLabels: 23 | app: 24 | template: 25 | metadata: 26 | labels: 27 | app: 28 | spec: 29 | serviceAccountName: 30 | # [END cloud_sql_proxy_k8s_sa] 31 | # [START cloud_sql_proxy_k8s_secrets] 32 | containers: 33 | - name: 34 | # ... other container configuration 35 | env: 36 | - name: DB_USER 37 | valueFrom: 38 | secretKeyRef: 39 | name: 40 | key: username 41 | - name: DB_PASS 42 | valueFrom: 43 | secretKeyRef: 44 | name: 45 | key: password 46 | - name: DB_NAME 47 | valueFrom: 48 | secretKeyRef: 49 | name: 50 | key: database 51 | # [END cloud_sql_proxy_k8s_secrets] 52 | # [START cloud_sql_proxy_k8s_container] 53 | initContainers: 54 | - name: cloud-sql-proxy 55 | restartPolicy: Always 56 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy 57 | # Make sure to update on a regular schedule! 58 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1 59 | args: 60 | # If connecting from a VPC-native GKE cluster, you can use the 61 | # following flag to have the proxy connect over private IP 62 | # - "--private-ip" 63 | 64 | # If you are not connecting with Automatic IAM, you can delete 65 | # the following flag. 66 | - "--auto-iam-authn" 67 | 68 | # Enable structured logging with LogEntry format: 69 | - "--structured-logs" 70 | 71 | # Replace DB_PORT with the port the proxy should listen on 72 | - "--port=" 73 | - "" 74 | 75 | securityContext: 76 | # The default Cloud SQL Auth Proxy image runs as the 77 | # "nonroot" user and group (uid: 65532) by default. 78 | runAsNonRoot: true 79 | # You should use resource requests/limits as a best practice to prevent 80 | # pods from consuming too many resources and affecting the execution of 81 | # other pods. You should adjust the following values based on what your 82 | # application needs. For details, see 83 | # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 84 | resources: 85 | requests: 86 | # The proxy's memory use scales linearly with the number of active 87 | # connections. Fewer open connections will use less memory. Adjust 88 | # this value based on your application's requirements. 89 | memory: "2Gi" 90 | # The proxy's CPU use scales linearly with the amount of IO between 91 | # the database and the application. Adjust this value based on your 92 | # application's requirements. 93 | cpu: "1" 94 | # [END cloud_sql_proxy_k8s_container] 95 | -------------------------------------------------------------------------------- /examples/k8s-sidecar/service_account.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # [START cloud_sql_proxy_k8s_sa_yml] 16 | apiVersion: v1 17 | kind: ServiceAccount 18 | metadata: 19 | name: # TODO(developer): replace these values 20 | # [END cloud_sql_proxy_k8s_sa_yml] -------------------------------------------------------------------------------- /examples/multi-container/ruby/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | FROM ruby:3.2.2 17 | 18 | # Copy local code to the container image. 19 | ENV APP_HOME /app 20 | WORKDIR $APP_HOME 21 | COPY . ./ 22 | 23 | # Install production dependencies. 24 | RUN bundle install 25 | 26 | EXPOSE 8080 27 | 28 | CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "8080"] 29 | -------------------------------------------------------------------------------- /examples/multi-container/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | source 'https://rubygems.org' 16 | 17 | gem 'json' 18 | gem 'pg' 19 | gem 'sequel' 20 | gem 'sinatra' 21 | gem 'thin' 22 | -------------------------------------------------------------------------------- /examples/multi-container/ruby/README.md: -------------------------------------------------------------------------------- 1 | # Cloud SQL Auth Proxy Sidecar 2 | 3 | In the following example, we will deploy the Cloud SQL Proxy as a sidecar to an 4 | existing application which connects to a Cloud SQL instance. 5 | 6 | ## Before you begin 7 | 8 | 1. If you haven't already, [create a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project). 9 | 10 | 1. [Enable the APIs](https://console.cloud.google.com/flows/enableapi?apiid=run.googleapis.com,sqladmin.googleapis.com,run.googleapis.com) that will be used during this tutorial: 11 | 12 | * Cloud SQL Admin 13 | * Cloud Build 14 | * Cloud Run 15 | 16 | 1. Create a Cloud SQL Postgres Instance by following these 17 | [instructions](https://cloud.google.com/sql/docs/postgres/create-instance). 18 | Note the connection string and default password that you create. 19 | 20 | 1. Create a database for your application by following these 21 | [instructions](https://cloud.google.com/sql/docs/postgres/create-manage-databases). 22 | Note the database name. 23 | 24 | 1. Optionally, create a database user for your instance following these 25 | [instructions](https://cloud.google.com/sql/docs/postgres/create-manage-users). 26 | Otherwise, use the username "postgres". 27 | 28 | ## Deploying the Application 29 | 30 | The application you will be deploying should connect to the Cloud SQL Proxy using 31 | TCP mode (for example, using the address "127.0.0.1:5432"). Follow the examples 32 | on the [Connect Auth Proxy documentation](https://cloud.google.com/sql/docs/postgres/connect-auth-proxy#expandable-1) 33 | page to correctly configure your application. 34 | 35 | The connection pool is configured in the following sample: 36 | 37 | ```ruby 38 | require 'sinatra' 39 | require 'sequel' 40 | 41 | set :bind, '0.0.0.0' 42 | set :port, 8080 43 | 44 | # Configure a connection pool that connects to the proxy via TCP 45 | def connect_tcp 46 | Sequel.connect( 47 | adapter: 'postgres', 48 | host: ENV["INSTANCE_HOST"], 49 | port: ENV["DB_PORT"], 50 | database: ENV["DB_NAME"], 51 | user: ENV["DB_USER"], 52 | password: ENV["DB_PASS"], 53 | pool_timeout: 5, 54 | max_connections: 5, 55 | ) 56 | end 57 | 58 | DB = connect_tcp() 59 | ``` 60 | 61 | Next, build the container image for the main application and deploy it: 62 | 63 | ```bash 64 | gcloud builds submit --tag gcr.io//run-cloudsql 65 | ``` 66 | 67 | Finally, update the `multicontainer.yaml` file with the correct values for your 68 | deployment for `YOUR_PROJECT_ID`, `DB_USER`, `DB_PASS`, `DB_NAME`, and `INSTANCE_CONNECTION_NAME` 69 | listing the Cloud SQL container image as a sidecar: 70 | 71 | ```yaml 72 | apiVersion: serving.knative.dev/v1 73 | kind: Service 74 | metadata: 75 | annotations: 76 | run.googleapis.com/launch-stage: ALPHA 77 | name: multicontainer-service 78 | spec: 79 | template: 80 | metadata: 81 | annotations: 82 | run.googleapis.com/execution-environment: gen1 #or gen2 83 | 84 | spec: 85 | containers: 86 | - name: my-app 87 | image: gcr.io//run-cloudsql 88 | ports: 89 | - containerPort: 8080 90 | env: 91 | - name: DB_USER 92 | value: 93 | - name: DB_PASS 94 | value: 95 | - name: DB_NAME 96 | value: 97 | - name: INSTANCE_HOST 98 | value: "127.0.0.1" 99 | - name: DB_PORT 100 | value: "5432" 101 | initContainers: 102 | - name: cloud-sql-proxy 103 | restartPolicy: Always 104 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest 105 | args: 106 | # Ensure the port number on the --port argument matches the value of 107 | # the DB_PORT env var on the my-app container. 108 | - "--port=5432" 109 | - "" 110 | 111 | ``` 112 | 113 | You can optionally use Secret Manager to store the database password. See 114 | [this documentation](https://cloud.google.com/run/docs/deploying#yaml) for more details. 115 | 116 | Before deploying, you will need to make sure that the service account associated 117 | with the Cloud Run deployment has the Cloud SQL Client role. 118 | See [this documentation](https://cloud.google.com/sql/docs/postgres/roles-and-permissions) 119 | for more details. The default service account will already have these permissions. 120 | 121 | Finally, you can deploy the service using: 122 | 123 | ```bash 124 | gcloud run services replace multicontainer.yaml 125 | ``` 126 | 127 | Once the service is deployed, the console should print out a URL. You can test 128 | the service by sending a curl request with your gcloud identity token in the headers: 129 | 130 | ```bash 131 | curl -H \ 132 | "Authorization: Bearer $(gcloud auth print-identity-token)" \ 133 | 134 | ``` 135 | -------------------------------------------------------------------------------- /examples/multi-container/ruby/app.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'json' 16 | require 'sequel' 17 | require 'sinatra' 18 | 19 | 20 | set :bind, '0.0.0.0' 21 | set :port, 8080 22 | 23 | # Configure a connection pool that connects to the proxy via TCP 24 | def connect_tcp 25 | Sequel.connect( 26 | adapter: :postgres, 27 | host: ENV.fetch("INSTANCE_HOST") { "127.0.0.1" }, 28 | port: ENV.fetch("DB_PORT") { 5432 }, 29 | database: ENV["DB_NAME"], 30 | user: ENV["DB_USER"], 31 | password: ENV["DB_PASS"], 32 | pool_timeout: 5, 33 | max_connections: 5, 34 | ) 35 | end 36 | 37 | DB = connect_tcp() 38 | 39 | 40 | get '/' do 41 | content_type :json 42 | # Connect to the database and get the current time 43 | return DB["SELECT NOW()"].all.to_json 44 | end 45 | -------------------------------------------------------------------------------- /examples/multi-container/ruby/config.ru: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require './app' 16 | 17 | run Sinatra::Application 18 | -------------------------------------------------------------------------------- /examples/multi-container/ruby/multicontainer.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: serving.knative.dev/v1 16 | kind: Service 17 | metadata: 18 | annotations: 19 | run.googleapis.com/launch-stage: ALPHA 20 | name: multicontainer-service 21 | spec: 22 | template: 23 | metadata: 24 | annotations: 25 | run.googleapis.com/execution-environment: gen1 #or gen2 26 | # Uncomment the following line if connecting to Cloud SQL using Private IP 27 | # via a VPC access connector 28 | # run.googleapis.com/vpc-access-connector: 29 | spec: 30 | containers: 31 | - name: my-app 32 | image: gcr.io//run-cloudsql:latest 33 | ports: 34 | - containerPort: 8080 35 | env: 36 | - name: DB_USER 37 | value: 38 | - name: DB_PASS 39 | value: 40 | - name: DB_NAME 41 | value: 42 | - name: INSTANCE_HOST 43 | value: "127.0.0.1" 44 | - name: DB_PORT 45 | value: "5432" 46 | initContainers: 47 | - name: cloud-sql-proxy 48 | restartPolicy: Always 49 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest 50 | args: 51 | # If connecting to a Cloud SQL instance within a VPC network, you can use the 52 | # following flag to have the proxy connect over private IP 53 | # - "--private-ip" 54 | 55 | # Ensure the port number on the --port argument matches the value of the DB_PORT env var on the my-app container. 56 | - "--port=5432" 57 | # instance connection name takes format "PROJECT:REGION:INSTANCE_NAME" 58 | - "" 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/GoogleCloudPlatform/cloud-sql-proxy/v2 2 | 3 | go 1.24 4 | 5 | require ( 6 | cloud.google.com/go/cloudsqlconn v1.17.1 7 | contrib.go.opencensus.io/exporter/prometheus v0.4.2 8 | contrib.go.opencensus.io/exporter/stackdriver v0.13.14 9 | github.com/coreos/go-systemd/v22 v22.5.0 10 | github.com/go-sql-driver/mysql v1.9.2 11 | github.com/google/go-cmp v0.7.0 12 | github.com/hanwen/go-fuse/v2 v2.7.2 13 | github.com/jackc/pgx/v5 v5.7.5 14 | github.com/microsoft/go-mssqldb v1.8.1 15 | github.com/spf13/cobra v1.9.1 16 | github.com/spf13/pflag v1.0.6 17 | github.com/spf13/viper v1.20.1 18 | go.opencensus.io v0.24.0 19 | go.uber.org/zap v1.27.0 20 | golang.org/x/oauth2 v0.30.0 21 | golang.org/x/sys v0.33.0 22 | google.golang.org/api v0.233.0 23 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 24 | ) 25 | 26 | require ( 27 | cloud.google.com/go/auth v0.16.1 // indirect 28 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect 29 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 30 | cloud.google.com/go/monitoring v1.21.2 // indirect 31 | cloud.google.com/go/trace v1.11.2 // indirect 32 | filippo.io/edwards25519 v1.1.0 // indirect 33 | github.com/aws/aws-sdk-go v1.43.31 // indirect 34 | github.com/beorn7/perks v1.0.1 // indirect 35 | github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect 36 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 37 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect 38 | github.com/felixge/httpsnoop v1.0.4 // indirect 39 | github.com/fsnotify/fsnotify v1.8.0 // indirect 40 | github.com/go-kit/log v0.2.1 // indirect 41 | github.com/go-logfmt/logfmt v0.5.1 // indirect 42 | github.com/go-logr/logr v1.4.2 // indirect 43 | github.com/go-logr/stdr v1.2.2 // indirect 44 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 45 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect 46 | github.com/golang-sql/sqlexp v0.1.0 // indirect 47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 48 | github.com/golang/protobuf v1.5.4 // indirect 49 | github.com/google/s2a-go v0.1.9 // indirect 50 | github.com/google/uuid v1.6.0 // indirect 51 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 52 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect 53 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 54 | github.com/jackc/pgpassfile v1.0.0 // indirect 55 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 56 | github.com/jackc/puddle/v2 v2.2.2 // indirect 57 | github.com/jmespath/go-jmespath v0.4.0 // indirect 58 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 59 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 60 | github.com/prometheus/client_golang v1.13.0 // indirect 61 | github.com/prometheus/client_model v0.2.0 // indirect 62 | github.com/prometheus/common v0.37.0 // indirect 63 | github.com/prometheus/procfs v0.8.0 // indirect 64 | github.com/prometheus/prometheus v0.35.0 // indirect 65 | github.com/prometheus/statsd_exporter v0.22.7 // indirect 66 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 67 | github.com/sagikazarmark/locafero v0.7.0 // indirect 68 | github.com/sourcegraph/conc v0.3.0 // indirect 69 | github.com/spf13/afero v1.12.0 // indirect 70 | github.com/spf13/cast v1.7.1 // indirect 71 | github.com/subosito/gotenv v1.6.0 // indirect 72 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 73 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect 74 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 75 | go.opentelemetry.io/otel v1.35.0 // indirect 76 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 77 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 78 | go.uber.org/multierr v1.10.0 // indirect 79 | golang.org/x/crypto v0.38.0 // indirect 80 | golang.org/x/net v0.40.0 // indirect 81 | golang.org/x/sync v0.14.0 // indirect 82 | golang.org/x/text v0.25.0 // indirect 83 | golang.org/x/time v0.11.0 // indirect 84 | google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect 85 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect 86 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 87 | google.golang.org/grpc v1.72.1 // indirect 88 | google.golang.org/protobuf v1.36.6 // indirect 89 | gopkg.in/yaml.v2 v2.4.0 // indirect 90 | gopkg.in/yaml.v3 v3.0.1 // indirect 91 | ) 92 | -------------------------------------------------------------------------------- /internal/gcloud/gcloud.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gcloud 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "fmt" 21 | "runtime" 22 | "time" 23 | 24 | "golang.org/x/oauth2" 25 | exec "golang.org/x/sys/execabs" 26 | ) 27 | 28 | // config represents the credentials returned by `gcloud config config-helper`. 29 | type config struct { 30 | Credential struct { 31 | AccessToken string `json:"access_token"` 32 | TokenExpiry time.Time `json:"token_expiry"` 33 | } 34 | } 35 | 36 | func (c *config) Token() *oauth2.Token { 37 | return &oauth2.Token{ 38 | AccessToken: c.Credential.AccessToken, 39 | Expiry: c.Credential.TokenExpiry, 40 | } 41 | } 42 | 43 | // Path returns the absolute path to the gcloud command. If the command is not 44 | // found it returns an error. 45 | func Path() (string, error) { 46 | g := "gcloud" 47 | if runtime.GOOS == "windows" { 48 | g = g + ".cmd" 49 | } 50 | return exec.LookPath(g) 51 | } 52 | 53 | // configHelper implements oauth2.TokenSource via the `gcloud config config-helper` command. 54 | type configHelper struct{} 55 | 56 | // Token helps gcloudTokenSource implement oauth2.TokenSource. 57 | func (configHelper) Token() (*oauth2.Token, error) { 58 | gcloudCmd, err := Path() 59 | if err != nil { 60 | return nil, err 61 | } 62 | buf, errbuf := new(bytes.Buffer), new(bytes.Buffer) 63 | cmd := exec.Command(gcloudCmd, "--format", "json", "config", "config-helper", "--min-expiry", "1h") 64 | cmd.Stdout = buf 65 | cmd.Stderr = errbuf 66 | 67 | if err := cmd.Run(); err != nil { 68 | err = fmt.Errorf("error reading config: %v; stderr was:\n%v", err, errbuf) 69 | return nil, err 70 | } 71 | 72 | c := &config{} 73 | if err := json.Unmarshal(buf.Bytes(), c); err != nil { 74 | return nil, err 75 | } 76 | return c.Token(), nil 77 | } 78 | 79 | // TokenSource returns an oauth2.TokenSource backed by the gcloud CLI. 80 | func TokenSource() (oauth2.TokenSource, error) { 81 | h := configHelper{} 82 | tok, err := h.Token() 83 | if err != nil { 84 | return nil, err 85 | } 86 | return oauth2.ReuseTokenSource(tok, h), nil 87 | } 88 | -------------------------------------------------------------------------------- /internal/gcloud/gcloud_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gcloud_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/gcloud" 21 | ) 22 | 23 | func TestGcloud(t *testing.T) { 24 | if testing.Short() { 25 | t.Skip("skipping gcloud integration tests") 26 | } 27 | 28 | // gcloud is configured. Try to obtain a token from gcloud config 29 | // helper. 30 | ts, err := gcloud.TokenSource() 31 | if err != nil { 32 | t.Fatalf("failed to get token source: %v", err) 33 | } 34 | 35 | _, err = ts.Token() 36 | if err != nil { 37 | t.Fatalf("failed to get token: %v", err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/healthcheck/healthcheck.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package healthcheck tests and communicates the health of the Cloud SQL Auth Proxy. 16 | package healthcheck 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "net/http" 22 | "sync" 23 | 24 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql" 25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy" 26 | ) 27 | 28 | // Check provides HTTP handlers for use as healthchecks typically in a 29 | // Kubernetes context. 30 | type Check struct { 31 | startedOnce *sync.Once 32 | started chan struct{} 33 | stoppedOnce *sync.Once 34 | stopped chan struct{} 35 | proxy *proxy.Client 36 | logger cloudsql.Logger 37 | } 38 | 39 | // NewCheck is the initializer for Check. 40 | func NewCheck(p *proxy.Client, l cloudsql.Logger) *Check { 41 | return &Check{ 42 | startedOnce: &sync.Once{}, 43 | started: make(chan struct{}), 44 | stoppedOnce: &sync.Once{}, 45 | stopped: make(chan struct{}), 46 | proxy: p, 47 | logger: l, 48 | } 49 | } 50 | 51 | // NotifyStarted notifies the check that the proxy has started up successfully. 52 | func (c *Check) NotifyStarted() { 53 | c.startedOnce.Do(func() { close(c.started) }) 54 | } 55 | 56 | // NotifyStopped notifies the check that the proxy has started up successfully. 57 | func (c *Check) NotifyStopped() { 58 | c.stoppedOnce.Do(func() { close(c.stopped) }) 59 | } 60 | 61 | // HandleStartup reports whether the Check has been notified of startup. 62 | func (c *Check) HandleStartup(w http.ResponseWriter, _ *http.Request) { 63 | select { 64 | case <-c.started: 65 | w.WriteHeader(http.StatusOK) 66 | w.Write([]byte("ok")) 67 | default: 68 | w.WriteHeader(http.StatusServiceUnavailable) 69 | w.Write([]byte("error")) 70 | } 71 | } 72 | 73 | var ( 74 | errNotStarted = errors.New("proxy is not started") 75 | errStopped = errors.New("proxy has stopped") 76 | ) 77 | 78 | // HandleReadiness ensures the Check has been notified of successful startup, 79 | // that the proxy has not reached maximum connections, and that the Proxy has 80 | // not started shutting down. 81 | func (c *Check) HandleReadiness(w http.ResponseWriter, _ *http.Request) { 82 | select { 83 | case <-c.started: 84 | default: 85 | c.logger.Errorf("[Health Check] Readiness failed: %v", errNotStarted) 86 | w.WriteHeader(http.StatusServiceUnavailable) 87 | w.Write([]byte(errNotStarted.Error())) 88 | return 89 | } 90 | 91 | select { 92 | case <-c.stopped: 93 | c.logger.Errorf("[Health Check] Readiness failed: %v", errStopped) 94 | w.WriteHeader(http.StatusServiceUnavailable) 95 | w.Write([]byte(errStopped.Error())) 96 | return 97 | default: 98 | } 99 | 100 | if open, maxCount := c.proxy.ConnCount(); maxCount > 0 && open == maxCount { 101 | err := fmt.Errorf("max connections reached (open = %v, max = %v)", open, maxCount) 102 | c.logger.Errorf("[Health Check] Readiness failed: %v", err) 103 | w.WriteHeader(http.StatusServiceUnavailable) 104 | w.Write([]byte(err.Error())) 105 | return 106 | } 107 | 108 | // No error cases apply, 200 status. 109 | w.WriteHeader(http.StatusOK) 110 | w.Write([]byte("ok")) 111 | } 112 | 113 | // HandleLiveness indicates the process is up and responding to HTTP requests. 114 | // If this check fails (because it's not reachable), the process is in a bad 115 | // state and should be restarted. 116 | func (c *Check) HandleLiveness(w http.ResponseWriter, _ *http.Request) { 117 | w.WriteHeader(http.StatusOK) 118 | w.Write([]byte("ok")) 119 | } 120 | -------------------------------------------------------------------------------- /internal/healthcheck/healthcheck_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package healthcheck_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "io" 21 | "net" 22 | "net/http" 23 | "net/http/httptest" 24 | "net/url" 25 | "os" 26 | "strings" 27 | "testing" 28 | "time" 29 | 30 | "cloud.google.com/go/cloudsqlconn" 31 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql" 32 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/healthcheck" 33 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log" 34 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy" 35 | ) 36 | 37 | var ( 38 | logger = log.NewStdLogger(os.Stdout, os.Stdout) 39 | proxyHost = "127.0.0.1" 40 | proxyPort = 9000 41 | ) 42 | 43 | func proxyAddr() string { 44 | return fmt.Sprintf("%s:%d", proxyHost, proxyPort) 45 | } 46 | 47 | func dialTCP(t *testing.T, addr string) net.Conn { 48 | for i := 0; i < 10; i++ { 49 | conn, err := net.Dial("tcp", addr) 50 | if err == nil { 51 | return conn 52 | } 53 | time.Sleep(100 * time.Millisecond) 54 | } 55 | t.Fatalf("failed to dial %v", addr) 56 | return nil 57 | } 58 | 59 | type fakeDialer struct{} 60 | 61 | func (*fakeDialer) Dial(_ context.Context, _ string, _ ...cloudsqlconn.DialOption) (net.Conn, error) { 62 | conn, _ := net.Pipe() 63 | return conn, nil 64 | } 65 | 66 | func (*fakeDialer) EngineVersion(_ context.Context, _ string) (string, error) { 67 | return "POSTGRES_14", nil 68 | } 69 | 70 | func (*fakeDialer) Close() error { 71 | return nil 72 | } 73 | 74 | func newProxyWithParams(t *testing.T, maxConns uint64, dialer cloudsql.Dialer, instances []proxy.InstanceConnConfig) *proxy.Client { 75 | c := &proxy.Config{ 76 | Addr: proxyHost, 77 | Port: proxyPort, 78 | Instances: instances, 79 | MaxConnections: maxConns, 80 | } 81 | p, err := proxy.NewClient(context.Background(), dialer, logger, c, nil) 82 | if err != nil { 83 | t.Fatalf("proxy.NewClient: %v", err) 84 | } 85 | return p 86 | } 87 | 88 | func newTestProxyWithMaxConns(t *testing.T, maxConns uint64) *proxy.Client { 89 | return newProxyWithParams(t, maxConns, &fakeDialer{}, []proxy.InstanceConnConfig{ 90 | {Name: "proj:region:pg"}, 91 | }) 92 | } 93 | 94 | func newTestProxy(t *testing.T) *proxy.Client { 95 | return newProxyWithParams(t, 0, &fakeDialer{}, []proxy.InstanceConnConfig{{Name: "proj:region:pg"}}) 96 | } 97 | 98 | func TestHandleStartupWhenNotNotified(t *testing.T) { 99 | p := newTestProxy(t) 100 | defer func() { 101 | if err := p.Close(); err != nil { 102 | t.Logf("failed to close proxy client: %v", err) 103 | } 104 | }() 105 | check := healthcheck.NewCheck(p, logger) 106 | 107 | rec := httptest.NewRecorder() 108 | check.HandleStartup(rec, &http.Request{URL: &url.URL{}}) 109 | 110 | // Startup is not complete because the Check has not been notified of the 111 | // proxy's startup. 112 | resp := rec.Result() 113 | if got, want := resp.StatusCode, http.StatusServiceUnavailable; got != want { 114 | t.Fatalf("want = %v, got = %v", want, got) 115 | } 116 | } 117 | 118 | func TestHandleStartupWhenNotified(t *testing.T) { 119 | p := newTestProxy(t) 120 | defer func() { 121 | if err := p.Close(); err != nil { 122 | t.Logf("failed to close proxy client: %v", err) 123 | } 124 | }() 125 | check := healthcheck.NewCheck(p, logger) 126 | 127 | check.NotifyStarted() 128 | 129 | rec := httptest.NewRecorder() 130 | check.HandleStartup(rec, &http.Request{URL: &url.URL{}}) 131 | 132 | resp := rec.Result() 133 | if got, want := resp.StatusCode, http.StatusOK; got != want { 134 | t.Fatalf("want = %v, got = %v", want, got) 135 | } 136 | } 137 | 138 | func TestHandleReadinessWhenNotNotified(t *testing.T) { 139 | p := newTestProxy(t) 140 | defer func() { 141 | if err := p.Close(); err != nil { 142 | t.Logf("failed to close proxy client: %v", err) 143 | } 144 | }() 145 | check := healthcheck.NewCheck(p, logger) 146 | 147 | rec := httptest.NewRecorder() 148 | check.HandleReadiness(rec, &http.Request{URL: &url.URL{}}) 149 | 150 | resp := rec.Result() 151 | if got, want := resp.StatusCode, http.StatusServiceUnavailable; got != want { 152 | t.Fatalf("want = %v, got = %v", want, got) 153 | } 154 | } 155 | 156 | func TestHandleReadinessWhenStopped(t *testing.T) { 157 | p := newTestProxy(t) 158 | defer func() { 159 | if err := p.Close(); err != nil { 160 | t.Logf("failed to close proxy client: %v", err) 161 | } 162 | }() 163 | check := healthcheck.NewCheck(p, logger) 164 | 165 | check.NotifyStarted() // The Proxy has started. 166 | check.NotifyStopped() // And now the Proxy is shutting down. 167 | 168 | rec := httptest.NewRecorder() 169 | check.HandleReadiness(rec, &http.Request{URL: &url.URL{}}) 170 | 171 | resp := rec.Result() 172 | if got, want := resp.StatusCode, http.StatusServiceUnavailable; got != want { 173 | t.Fatalf("want = %v, got = %v", want, got) 174 | } 175 | } 176 | 177 | func TestHandleReadinessForMaxConns(t *testing.T) { 178 | p := newTestProxyWithMaxConns(t, 1) 179 | defer func() { 180 | if err := p.Close(); err != nil { 181 | t.Logf("failed to close proxy client: %v", err) 182 | } 183 | }() 184 | started := make(chan struct{}) 185 | check := healthcheck.NewCheck(p, logger) 186 | go p.Serve(context.Background(), func() { 187 | check.NotifyStarted() 188 | close(started) 189 | }) 190 | select { 191 | case <-started: 192 | // proxy has started 193 | case <-time.After(10 * time.Second): 194 | t.Fatal("proxy has not started after 10 seconds") 195 | } 196 | 197 | conn := dialTCP(t, proxyAddr()) 198 | defer conn.Close() 199 | 200 | // The proxy calls the dialer in a separate goroutine. So wait for that 201 | // goroutine to run before asserting on the readiness response. 202 | waitForConnect := func(t *testing.T, wantCode int) *http.Response { 203 | for i := 0; i < 10; i++ { 204 | rec := httptest.NewRecorder() 205 | check.HandleReadiness(rec, &http.Request{URL: &url.URL{}}) 206 | resp := rec.Result() 207 | if resp.StatusCode == wantCode { 208 | return resp 209 | } 210 | time.Sleep(time.Second) 211 | } 212 | t.Fatalf("failed to receive status code = %v", wantCode) 213 | return nil 214 | } 215 | resp := waitForConnect(t, http.StatusServiceUnavailable) 216 | 217 | body, err := io.ReadAll(resp.Body) 218 | if err != nil { 219 | t.Fatalf("failed to read response body: %v", err) 220 | } 221 | if !strings.Contains(string(body), "max connections") { 222 | t.Fatalf("want max connections error, got = %v", string(body)) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /internal/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "io" 19 | llog "log" 20 | "os" 21 | 22 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql" 23 | "go.uber.org/zap" 24 | "go.uber.org/zap/zapcore" 25 | ) 26 | 27 | // StdLogger is the standard logger that distinguishes between info and error 28 | // logs. 29 | type StdLogger struct { 30 | infoLog *llog.Logger 31 | debugLog *llog.Logger 32 | errLog *llog.Logger 33 | } 34 | 35 | // NewStdLogger create a Logger that uses out and err for informational and 36 | // error messages. 37 | func NewStdLogger(out, err io.Writer) cloudsql.Logger { 38 | return &StdLogger{ 39 | infoLog: llog.New(out, "", llog.LstdFlags), 40 | debugLog: llog.New(out, "", llog.LstdFlags), 41 | errLog: llog.New(err, "", llog.LstdFlags), 42 | } 43 | } 44 | 45 | // Infof logs informational messages 46 | func (l *StdLogger) Infof(format string, v ...interface{}) { 47 | l.infoLog.Printf(format, v...) 48 | } 49 | 50 | // Errorf logs error messages 51 | func (l *StdLogger) Errorf(format string, v ...interface{}) { 52 | l.errLog.Printf(format, v...) 53 | } 54 | 55 | // Debugf logs debug messages 56 | func (l *StdLogger) Debugf(format string, v ...interface{}) { 57 | l.debugLog.Printf(format, v...) 58 | } 59 | 60 | // StructuredLogger writes log messages in JSON. 61 | type StructuredLogger struct { 62 | logger *zap.SugaredLogger 63 | } 64 | 65 | // Infof logs informational messages 66 | func (l *StructuredLogger) Infof(format string, v ...interface{}) { 67 | l.logger.Infof(format, v...) 68 | } 69 | 70 | // Errorf logs error messages 71 | func (l *StructuredLogger) Errorf(format string, v ...interface{}) { 72 | l.logger.Errorf(format, v...) 73 | } 74 | 75 | // Debugf logs debug messages 76 | func (l *StructuredLogger) Debugf(format string, v ...interface{}) { 77 | l.logger.Debugf(format, v...) 78 | } 79 | 80 | // NewStructuredLogger creates a Logger that logs messages using JSON. 81 | func NewStructuredLogger(quiet bool) (cloudsql.Logger, func() error) { 82 | // Configure structured logs to adhere to LogEntry format 83 | // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry 84 | c := zap.NewProductionEncoderConfig() 85 | c.LevelKey = "severity" 86 | c.MessageKey = "message" 87 | c.TimeKey = "timestamp" 88 | c.EncodeLevel = zapcore.CapitalLevelEncoder 89 | c.EncodeTime = zapcore.ISO8601TimeEncoder 90 | 91 | enc := zapcore.NewJSONEncoder(c) 92 | 93 | var syncer zapcore.WriteSyncer 94 | // quiet disables writing to the info log 95 | if quiet { 96 | syncer = zapcore.AddSync(io.Discard) 97 | } else { 98 | syncer = zapcore.Lock(os.Stdout) 99 | } 100 | core := zapcore.NewTee( 101 | zapcore.NewCore(enc, syncer, zap.LevelEnablerFunc(func(l zapcore.Level) bool { 102 | // Anything below error, goes to the info log. 103 | return l < zapcore.ErrorLevel 104 | })), 105 | zapcore.NewCore(enc, zapcore.Lock(os.Stderr), zap.LevelEnablerFunc(func(l zapcore.Level) bool { 106 | // Anything at error or higher goes to the error log. 107 | return l >= zapcore.ErrorLevel 108 | })), 109 | ) 110 | l := &StructuredLogger{ 111 | logger: zap.New(core).Sugar(), 112 | } 113 | return l, l.logger.Sync 114 | } 115 | -------------------------------------------------------------------------------- /internal/proxy/fuse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows && !openbsd && !freebsd 16 | // +build !windows,!openbsd,!freebsd 17 | 18 | package proxy 19 | 20 | import ( 21 | "context" 22 | "syscall" 23 | 24 | "github.com/hanwen/go-fuse/v2/fs" 25 | "github.com/hanwen/go-fuse/v2/fuse" 26 | "github.com/hanwen/go-fuse/v2/fuse/nodefs" 27 | ) 28 | 29 | // symlink implements a symbolic link, returning the underlying path when 30 | // Readlink is called. 31 | type symlink struct { 32 | fs.Inode 33 | path string 34 | } 35 | 36 | // Readlink implements fs.NodeReadlinker and returns the symlink's path. 37 | func (s *symlink) Readlink(_ context.Context) ([]byte, syscall.Errno) { 38 | return []byte(s.path), fs.OK 39 | } 40 | 41 | // readme represents a static read-only text file. 42 | type readme struct { 43 | fs.Inode 44 | } 45 | 46 | const readmeText = ` 47 | When applications attempt to open files in this directory, a remote connection 48 | to the Cloud SQL instance of the same name will be established. 49 | 50 | For example, when you run one of the following commands, the proxy will initiate 51 | a connection to the corresponding Cloud SQL instance, given you have the correct 52 | IAM permissions. 53 | 54 | mysql -u root -S "/somedir/project:region:instance" 55 | 56 | # or 57 | 58 | psql "host=/somedir/project:region:instance dbname=mydb user=myuser" 59 | 60 | For MySQL, the proxy will create a socket with the instance connection name 61 | (e.g., project:region:instance) in this directory. For Postgres, the proxy will 62 | create a directory with the instance connection name, and create a socket inside 63 | that directory with the special Postgres name: .s.PGSQL.5432. 64 | 65 | Listing the contents of this directory will show all instances with active 66 | connections. 67 | ` 68 | 69 | // Getattr implements fs.NodeGetattrer and indicates that this file is a regular 70 | // file. 71 | func (*readme) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno { 72 | *out = fuse.AttrOut{Attr: fuse.Attr{ 73 | Mode: 0444 | syscall.S_IFREG, 74 | Size: uint64(len(readmeText)), 75 | }} 76 | return fs.OK 77 | } 78 | 79 | // Read implements fs.NodeReader and supports incremental reads. 80 | func (*readme) Read(_ context.Context, _ fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { 81 | end := int(off) + len(dest) 82 | if end > len(readmeText) { 83 | end = len(readmeText) 84 | } 85 | return fuse.ReadResultData([]byte(readmeText[off:end])), fs.OK 86 | } 87 | 88 | // Open implements fs.NodeOpener and supports opening the README as a read-only 89 | // file. 90 | func (*readme) Open(_ context.Context, _ uint32) (fs.FileHandle, uint32, syscall.Errno) { 91 | df := nodefs.NewDataFile([]byte(readmeText)) 92 | rf := nodefs.NewReadOnlyFile(df) 93 | return rf, 0, fs.OK 94 | } 95 | -------------------------------------------------------------------------------- /internal/proxy/fuse_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | ) 21 | 22 | const ( 23 | macfusePath = "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse" 24 | osxfusePath = "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse" 25 | ) 26 | 27 | // SupportsFUSE checks if macfuse or osxfuse are installed on the host by 28 | // looking for both in their known installation location. 29 | func SupportsFUSE() error { 30 | // This code follows the same strategy as hanwen/go-fuse. 31 | // See https://github.com/hanwen/go-fuse/blob/0f728ba15b38579efefc3dc47821882ca18ffea7/fuse/mount_darwin.go#L121-L124. 32 | 33 | // check for macfuse first (newer version of osxfuse) 34 | if _, err := os.Stat(macfusePath); err != nil { 35 | // if that fails, check for osxfuse next 36 | if _, err := os.Stat(osxfusePath); err != nil { 37 | return errors.New("failed to find osxfuse or macfuse: verify FUSE installation and try again (see https://osxfuse.github.io).") 38 | } 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/proxy/fuse_freebsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | ) 21 | 22 | var errFUSENotSupported = errors.New("FUSE is not supported on FreeBSD") 23 | 24 | // SupportsFUSE is false on FreeBSD. 25 | func SupportsFUSE() error { 26 | return errFUSENotSupported 27 | } 28 | 29 | type fuseMount struct { 30 | // fuseDir is always an empty string on FreeBSD. 31 | fuseDir string 32 | } 33 | 34 | func configureFUSE(c *Client, conf *Config) (*Client, error) { return nil, errFUSENotSupported } 35 | func (c *Client) fuseMounts() []*socketMount { return nil } 36 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { return errFUSENotSupported } 37 | func (c *Client) unmountFUSE() error { return nil } 38 | func (c *Client) waitForFUSEMounts() {} 39 | -------------------------------------------------------------------------------- /internal/proxy/fuse_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "errors" 19 | "os/exec" 20 | ) 21 | 22 | // SupportsFUSE checks if the fusermount binary is present in the PATH or a well 23 | // known location. 24 | func SupportsFUSE() error { 25 | // This code follows the same strategy found in hanwen/go-fuse. 26 | // See https://github.com/hanwen/go-fuse/blob/0f728ba15b38579efefc3dc47821882ca18ffea7/fuse/mount_linux.go#L184-L198. 27 | if _, err := exec.LookPath("fusermount"); err != nil { 28 | if _, err := exec.LookPath("/bin/fusermount"); err != nil { 29 | return errors.New("fusermount binary not found in PATH or /bin") 30 | } 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/proxy/fuse_linux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy_test 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy" 22 | ) 23 | 24 | func TestFUSESupport(t *testing.T) { 25 | if testing.Short() { 26 | t.Skip("skipping fuse tests in short mode.") 27 | } 28 | 29 | removePath := func() func() { 30 | original := os.Getenv("PATH") 31 | os.Unsetenv("PATH") 32 | return func() { os.Setenv("PATH", original) } 33 | } 34 | if err := proxy.SupportsFUSE(); err != nil { 35 | t.Fatalf("expected FUSE to be support (PATH set): %v", err) 36 | } 37 | cleanup := removePath() 38 | defer cleanup() 39 | 40 | if err := proxy.SupportsFUSE(); err != nil { 41 | t.Fatalf("expected FUSE to be supported (PATH unset): %v", err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/proxy/fuse_openbsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | ) 21 | 22 | var errFUSENotSupported = errors.New("FUSE is not supported on OpenBSD") 23 | 24 | // SupportsFUSE is false on OpenBSD. 25 | func SupportsFUSE() error { 26 | return errFUSENotSupported 27 | } 28 | 29 | type fuseMount struct { 30 | // fuseDir is always an empty string on OpenBSD. 31 | fuseDir string 32 | } 33 | 34 | func configureFUSE(c *Client, conf *Config) (*Client, error) { return nil, errFUSENotSupported } 35 | func (c *Client) fuseMounts() []*socketMount { return nil } 36 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { return errFUSENotSupported } 37 | func (c *Client) unmountFUSE() error { return nil } 38 | func (c *Client) waitForFUSEMounts() {} 39 | -------------------------------------------------------------------------------- /internal/proxy/fuse_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "path/filepath" 21 | "strings" 22 | ) 23 | 24 | var errFUSENotSupported = errors.New("FUSE is not supported on Windows") 25 | 26 | // SupportsFUSE is false on Windows. 27 | func SupportsFUSE() error { 28 | return errFUSENotSupported 29 | } 30 | 31 | // UnixAddress returns the Unix socket for a given instance in the provided 32 | // directory, by replacing all colons in the instance's name with periods. 33 | func UnixAddress(dir, inst string) string { 34 | inst2 := strings.ReplaceAll(inst, ":", ".") 35 | return filepath.Join(dir, inst2) 36 | } 37 | 38 | type fuseMount struct { 39 | // fuseDir is always an empty string on Windows. 40 | fuseDir string 41 | } 42 | 43 | func configureFUSE(c *Client, conf *Config) (*Client, error) { return nil, errFUSENotSupported } 44 | func (c *Client) fuseMounts() []*socketMount { return nil } 45 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { return errFUSENotSupported } 46 | func (c *Client) unmountFUSE() error { return nil } 47 | func (c *Client) waitForFUSEMounts() {} 48 | -------------------------------------------------------------------------------- /internal/proxy/internal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "testing" 19 | "unsafe" 20 | 21 | "github.com/google/go-cmp/cmp" 22 | ) 23 | 24 | func TestClientUsesSyncAtomicAlignment(t *testing.T) { 25 | // The sync/atomic pkg has a bug that requires the developer to guarantee 26 | // 64-bit alignment when using 64-bit functions on 32-bit systems. 27 | c := &Client{} //nolint:staticcheck 28 | 29 | if a := unsafe.Offsetof(c.connCount); a%64 != 0 { 30 | t.Errorf("Client.connCount is not 64-bit aligned: want 0, got %v", a) 31 | } 32 | } 33 | 34 | func TestParseImpersonationChain(t *testing.T) { 35 | tcs := []struct { 36 | desc string 37 | in string 38 | wantTarget string 39 | wantChain []string 40 | }{ 41 | { 42 | desc: "when there is only a target", 43 | in: "sv1@developer.gserviceaccount.com", 44 | wantTarget: "sv1@developer.gserviceaccount.com", 45 | }, 46 | { 47 | desc: "when there are delegates", 48 | in: "sv1@developer.gserviceaccount.com,sv2@developer.gserviceaccount.com,sv3@developer.gserviceaccount.com", 49 | wantTarget: "sv1@developer.gserviceaccount.com", 50 | wantChain: []string{ 51 | "sv3@developer.gserviceaccount.com", 52 | "sv2@developer.gserviceaccount.com", 53 | }, 54 | }, 55 | } 56 | for _, tc := range tcs { 57 | t.Run(tc.desc, func(t *testing.T) { 58 | gotTarget, gotChain := parseImpersonationChain(tc.in) 59 | if gotTarget != tc.wantTarget { 60 | t.Fatalf("target: want = %v, got = %v", tc.wantTarget, gotTarget) 61 | } 62 | if !equalSlice(tc.wantChain, gotChain) { 63 | t.Fatalf("want chain != got chain: %v", cmp.Diff(tc.wantChain, gotChain)) 64 | } 65 | }) 66 | } 67 | } 68 | 69 | func equalSlice[T comparable](x, y []T) bool { 70 | if len(x) != len(y) { 71 | return false 72 | } 73 | for i := 0; i < len(x); i++ { 74 | if x[i] != y[i] { 75 | return false 76 | } 77 | } 78 | return true 79 | } 80 | -------------------------------------------------------------------------------- /internal/proxy/proxy_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows && !openbsd && !freebsd 16 | // +build !windows,!openbsd,!freebsd 17 | 18 | package proxy 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "os" 24 | "path/filepath" 25 | "sync" 26 | "syscall" 27 | 28 | "github.com/hanwen/go-fuse/v2/fs" 29 | "github.com/hanwen/go-fuse/v2/fuse" 30 | ) 31 | 32 | type socketSymlink struct { 33 | socket *socketMount 34 | symlink *symlink 35 | } 36 | 37 | func configureFUSE(c *Client, conf *Config) (*Client, error) { 38 | if _, err := os.Stat(conf.FUSEDir); err != nil { 39 | return nil, err 40 | } 41 | if err := os.MkdirAll(conf.FUSETempDir, 0777); err != nil { 42 | return nil, err 43 | } 44 | c.fuseMount = fuseMount{ 45 | fuseDir: conf.FUSEDir, 46 | fuseTempDir: conf.FUSETempDir, 47 | fuseSockets: map[string]socketSymlink{}, 48 | // Use pointers for the following mutexes so fuseMount may be embedded 49 | // as a value and support zero value lookups on fuseDir. 50 | fuseMu: &sync.Mutex{}, 51 | fuseServerMu: &sync.Mutex{}, 52 | fuseWg: &sync.WaitGroup{}, 53 | } 54 | return c, nil 55 | } 56 | 57 | type fuseMount struct { 58 | // fuseDir specifies the directory where a FUSE server is mounted. The value 59 | // is empty if FUSE is not enabled. The directory holds symlinks to Unix 60 | // domain sockets in the fuseTmpDir. 61 | fuseDir string 62 | fuseTempDir string 63 | // fuseMu protects access to fuseSockets. 64 | fuseMu *sync.Mutex 65 | // fuseSockets is a map of instance connection name to socketMount and 66 | // symlink. 67 | fuseSockets map[string]socketSymlink 68 | fuseServerMu *sync.Mutex 69 | fuseServer *fuse.Server 70 | fuseWg *sync.WaitGroup 71 | fuseExitCh chan error 72 | 73 | // Inode adds support for FUSE operations. 74 | fs.Inode 75 | } 76 | 77 | // Readdir returns a list of all active Unix sockets in addition to the README. 78 | func (c *Client) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) { 79 | entries := []fuse.DirEntry{ 80 | {Name: "README", Mode: 0555 | fuse.S_IFREG}, 81 | } 82 | var active []string 83 | c.fuseMu.Lock() 84 | for k := range c.fuseSockets { 85 | active = append(active, k) 86 | } 87 | c.fuseMu.Unlock() 88 | 89 | for _, a := range active { 90 | entries = append(entries, fuse.DirEntry{ 91 | Name: a, 92 | Mode: 0777 | syscall.S_IFSOCK, 93 | }) 94 | } 95 | return fs.NewListDirStream(entries), fs.OK 96 | } 97 | 98 | // Lookup implements the fs.NodeLookuper interface and returns an index node 99 | // (inode) for a symlink that points to a Unix domain socket. The Unix domain 100 | // socket is connected to the requested Cloud SQL instance. Lookup returns a 101 | // symlink (instead of the socket itself) so that multiple callers all use the 102 | // same Unix socket. 103 | func (c *Client) Lookup(_ context.Context, instance string, _ *fuse.EntryOut) (*fs.Inode, syscall.Errno) { 104 | ctx := context.Background() 105 | if instance == "README" { 106 | return c.NewInode(ctx, &readme{}, fs.StableAttr{}), fs.OK 107 | } 108 | 109 | if _, err := parseConnName(instance); err != nil { 110 | c.logger.Debugf("could not parse instance connection name for %q: %v", instance, err) 111 | return nil, syscall.ENOENT 112 | } 113 | 114 | c.fuseMu.Lock() 115 | defer c.fuseMu.Unlock() 116 | if l, ok := c.fuseSockets[instance]; ok { 117 | c.logger.Debugf("found existing socket for instance %q", instance) 118 | return l.symlink.EmbeddedInode(), fs.OK 119 | } 120 | 121 | c.logger.Debugf("creating new socket for instance %q", instance) 122 | s, err := c.newSocketMount( 123 | ctx, withUnixSocket(*c.conf, c.fuseTempDir), 124 | nil, InstanceConnConfig{Name: instance}, 125 | ) 126 | if err != nil { 127 | c.logger.Errorf("could not create socket for %q: %v", instance, err) 128 | return nil, syscall.ENOENT 129 | } 130 | 131 | c.fuseWg.Add(1) 132 | go func() { 133 | defer c.fuseWg.Done() 134 | sErr := c.serveSocketMount(ctx, s) 135 | if sErr != nil { 136 | c.logger.Debugf("could not serve socket for instance %q: %v", instance, sErr) 137 | c.fuseMu.Lock() 138 | defer c.fuseMu.Unlock() 139 | delete(c.fuseSockets, instance) 140 | select { 141 | // Best effort attempt to send error. 142 | // If this send fails, it means the reading goroutine has 143 | // already pulled a value out of the channel and is no longer 144 | // reading any more values. In other words, we report only the 145 | // first error. 146 | case c.fuseExitCh <- sErr: 147 | default: 148 | return 149 | } 150 | } 151 | }() 152 | 153 | // Return a symlink that points to the actual Unix socket within the 154 | // temporary directory. For Postgres, return a symlink that points to the 155 | // directory which holds the ".s.PGSQL.5432" Unix socket. 156 | sl := &symlink{path: filepath.Join(c.fuseTempDir, instance)} 157 | c.fuseSockets[instance] = socketSymlink{ 158 | socket: s, 159 | symlink: sl, 160 | } 161 | return c.NewInode(ctx, sl, fs.StableAttr{ 162 | Mode: 0777 | fuse.S_IFLNK}, 163 | ), fs.OK 164 | } 165 | 166 | func withUnixSocket(c Config, tmpDir string) *Config { 167 | c.UnixSocket = tmpDir 168 | return &c 169 | } 170 | 171 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { 172 | srv, err := fs.Mount(c.fuseDir, c, &fs.Options{ 173 | MountOptions: fuse.MountOptions{AllowOther: true}, 174 | }) 175 | if err != nil { 176 | return fmt.Errorf("FUSE mount failed: %q: %v", c.fuseDir, err) 177 | } 178 | c.fuseServerMu.Lock() 179 | c.fuseServer = srv 180 | c.fuseExitCh = make(chan error) 181 | 182 | c.fuseServerMu.Unlock() 183 | notify() 184 | select { 185 | case err = <-c.fuseExitCh: 186 | return err 187 | case <-ctx.Done(): 188 | return ctx.Err() 189 | } 190 | } 191 | 192 | func (c *Client) fuseMounts() []*socketMount { 193 | var mnts []*socketMount 194 | c.fuseMu.Lock() 195 | for _, m := range c.fuseSockets { 196 | mnts = append(mnts, m.socket) 197 | } 198 | c.fuseMu.Unlock() 199 | return mnts 200 | } 201 | 202 | func (c *Client) unmountFUSE() error { 203 | c.fuseServerMu.Lock() 204 | defer c.fuseServerMu.Unlock() 205 | if c.fuseServer == nil { 206 | return nil 207 | } 208 | return c.fuseServer.Unmount() 209 | } 210 | 211 | func (c *Client) waitForFUSEMounts() { c.fuseWg.Wait() } 212 | -------------------------------------------------------------------------------- /internal/proxy/proxy_other_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package proxy_test 19 | 20 | import ( 21 | "context" 22 | "os" 23 | "testing" 24 | 25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy" 26 | ) 27 | 28 | var ( 29 | pg = "proj:region:pg" 30 | pg2 = "proj:region:pg2" 31 | mysql = "proj:region:mysql" 32 | mysql2 = "proj:region:mysql2" 33 | sqlserver = "proj:region:sqlserver" 34 | sqlserver2 = "proj:region:sqlserver2" 35 | ) 36 | 37 | func verifySocketPermissions(t *testing.T, addr string) { 38 | fi, err := os.Stat(addr) 39 | if err != nil { 40 | t.Fatalf("os.Stat(%v): %v", addr, err) 41 | } 42 | if fm := fi.Mode(); fm != 0777|os.ModeSocket { 43 | t.Fatalf("file mode: want = %v, got = %v", 0777|os.ModeSocket, fm) 44 | } 45 | } 46 | 47 | func TestFuseClosesGracefully(t *testing.T) { 48 | c, err := proxy.NewClient( 49 | context.Background(), nil, testLogger, 50 | &proxy.Config{ 51 | FUSEDir: t.TempDir(), 52 | FUSETempDir: t.TempDir(), 53 | Token: "mytoken", 54 | }, 55 | nil) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | if err := c.Close(); err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /internal/proxy/proxy_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | var ( 23 | pg = strings.ReplaceAll("proj:region:pg", ":", ".") 24 | pg2 = strings.ReplaceAll("proj:region:pg2", ":", ".") 25 | mysql = strings.ReplaceAll("proj:region:mysql", ":", ".") 26 | mysql2 = strings.ReplaceAll("proj:region:mysql2", ":", ".") 27 | sqlserver = strings.ReplaceAll("proj:region:sqlserver", ":", ".") 28 | sqlserver2 = strings.ReplaceAll("proj:region:sqlserver2", ":", ".") 29 | ) 30 | 31 | func verifySocketPermissions(t *testing.T, addr string) { 32 | // On Linux and Darwin, we check that the socket named by addr exists with 33 | // os.Stat. That operation is not supported on Windows. 34 | // See https://github.com/microsoft/Windows-Containers/issues/97#issuecomment-887713195 35 | } 36 | -------------------------------------------------------------------------------- /internal/proxy/unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package proxy 19 | 20 | import "path/filepath" 21 | 22 | // UnixAddress is defined as a function to distinguish between Unix-based 23 | // implementations where the dir and inst are simply joined, and Windows-based 24 | // implementations where the inst must be further altered. 25 | func UnixAddress(dir, inst string) string { 26 | return filepath.Join(dir, inst) 27 | } 28 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package main 19 | 20 | import ( 21 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd" 22 | ) 23 | 24 | func main() { 25 | cmd.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /main_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "os" 21 | "path/filepath" 22 | "time" 23 | 24 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd" 25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log" 26 | "golang.org/x/sys/windows/svc" 27 | "gopkg.in/natefinch/lumberjack.v2" 28 | ) 29 | 30 | type windowsService struct{} 31 | 32 | func (m *windowsService) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { 33 | // start the service 34 | changes <- svc.Status{State: svc.StartPending} 35 | 36 | // set up the log file 37 | exePath, err := os.Executable() 38 | if err != nil { 39 | changes <- svc.Status{State: svc.StopPending} 40 | return true, 101 // service specific exit code=101 41 | } 42 | 43 | logFolder := filepath.Join(filepath.Dir(exePath), "logs") 44 | os.Mkdir(logFolder, 0644) // ignore all errors 45 | 46 | logFile := &lumberjack.Logger{ 47 | Filename: filepath.Join(logFolder, "cloud-sql-proxy.log"), 48 | MaxSize: 50, // megabytes 49 | MaxBackups: 10, 50 | MaxAge: 30, //days 51 | } 52 | 53 | logger := log.NewStdLogger(logFile, logFile) 54 | logger.Infof("Starting...") 55 | 56 | // start the main command 57 | ctx, cancel := context.WithCancel(context.Background()) 58 | defer cancel() 59 | 60 | app := cmd.NewCommand(cmd.WithLogger(logger)) 61 | 62 | cmdErrCh := make(chan error, 1) 63 | go func() { 64 | cmdErrCh <- app.ExecuteContext(ctx) 65 | }() 66 | 67 | // now running 68 | changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} 69 | 70 | var cmdErr error 71 | 72 | loop: 73 | for { 74 | select { 75 | case err := <-cmdErrCh: 76 | cmdErr = err 77 | break loop 78 | 79 | case c := <-r: 80 | switch c.Cmd { 81 | case svc.Interrogate: 82 | changes <- c.CurrentStatus 83 | // testing deadlock from https://code.google.com/archive/p/winsvc/issues/4 84 | time.Sleep(100 * time.Millisecond) 85 | changes <- c.CurrentStatus 86 | 87 | case svc.Stop, svc.Shutdown: 88 | cancel() 89 | 90 | default: 91 | logger.Errorf("unexpected control request #%d", c) 92 | } 93 | } 94 | } 95 | 96 | // start shutting down 97 | logger.Infof("Stopping...") 98 | 99 | changes <- svc.Status{State: svc.StopPending} 100 | 101 | if cmdErr != nil && errors.Is(cmdErr, context.Canceled) { 102 | logger.Errorf("Unexpected error: %v", cmdErr) 103 | return true, 2 104 | } 105 | 106 | return false, 0 107 | } 108 | 109 | func main() { 110 | // determine if running as a windows service 111 | inService, err := svc.IsWindowsService() 112 | if err != nil { 113 | os.Exit(99) // failed to determine service status 114 | } 115 | 116 | // running as service? 117 | if inService { 118 | err := svc.Run("cloud-sql-proxy", &windowsService{}) 119 | if err != nil { 120 | os.Exit(100) // failed to execute service 121 | } 122 | return 123 | } 124 | 125 | // run as commandline 126 | cmd.Execute() 127 | } 128 | -------------------------------------------------------------------------------- /migration-guide.md: -------------------------------------------------------------------------------- 1 | # Migrating from v1 to v2 2 | 3 | The Cloud SQL Auth Proxy v2 CLI interface maintains a close match to the v1 4 | interface. Migrating to v2 will require minimal changes. Below are a number 5 | of examples of v1 vs v2 invocations covering the most common uses. See 6 | [Flag Changes](#flag-changes) for details. 7 | 8 | All the examples below use `` as a placeholder for 9 | your instance connection name, e.g., `my-cool-project:us-central1:my-db`. 10 | 11 | ## Container Image Name Change 12 | 13 | As part of releasing a v2, we have updated the image name to be more descriptive. 14 | Compare: 15 | 16 | ``` 17 | # v1 18 | gcr.io/cloudsql-docker/gce-proxy 19 | ``` 20 | 21 | vs 22 | 23 | ``` 24 | # v2 25 | gcr.io/cloud-sql-connectors/cloud-sql-proxy 26 | ``` 27 | 28 | To update to the v2 container, make sure to update the image name. 29 | 30 | ## Behavior Differences 31 | 32 | In v1, when a client connected, the Proxy would first try to use a public IP 33 | and then attempt to use a private IP. In v2, the Proxy now defaults to public 34 | IP without trying private IP. If you want to use private IP, you must pass 35 | either the `--private-ip` flag or the query parameter. See the README for details. 36 | 37 | In some cases, the v1 behavior may be preferable. Use the `--auto-ip` flag to 38 | mimic v1 behavior. We generally recommend using deterministic IP address selection, 39 | but recognize in some legacy environments `--auto-ip` may be necessary. 40 | 41 | ## Executable Name Change 42 | 43 | Note that the name of the executable has changed, using hyphens rather than underscores: 44 | 45 | ```shell 46 | # v1 47 | ./cloud_sql_proxy 48 | ``` 49 | 50 | vs 51 | 52 | ```shell 53 | # v2 54 | ./cloud-sql-proxy 55 | ``` 56 | 57 | ## Sample Invocations 58 | 59 | ### Listen on TCP socket 60 | 61 | ```shell 62 | # v1 63 | ./cloud_sql_proxy -instances==tcp:5432 64 | 65 | # v2 66 | # Using automatic database port selection (MySQL 3306, Postgres 5432, SQL Server 1433) 67 | ./cloud-sql-proxy 68 | ``` 69 | 70 | ### Listen on Unix Socket 71 | 72 | ```shell 73 | # v1 74 | ./cloud_sql_proxy -dir /cloudsql -instances= 75 | 76 | # v2 77 | ./cloud-sql-proxy --unix-socket /cloudsql 78 | ``` 79 | 80 | ### Listen on multiple TCP sockets with incrementing ports 81 | 82 | ```shell 83 | # v1 84 | ./cloud_sql_proxy -instances==tcp:5000,=tcp:5001 85 | 86 | # v2 87 | # starts listener on port 5000, increments for additional listeners 88 | ./cloud-sql-proxy --port 5000 89 | ``` 90 | 91 | ### Listen on multiple TCP sockets with non-sequential ports 92 | 93 | ```shell 94 | # v1 95 | ./cloud_sql_proxy -instances==tcp:6000,=tcp:7000 96 | 97 | # v2 98 | ./cloud-sql-proxy '?port=6000' '?port=7000' 99 | ``` 100 | 101 | ### Listen on all interfaces 102 | 103 | ```shell 104 | # v1 105 | ./cloud_sql_proxy -instances==tcp:0.0.0.0:6000 106 | 107 | # v2 108 | ./cloud-sql-proxy --address 0.0.0.0 --port 6000 109 | ``` 110 | 111 | ## Environment variable changes 112 | 113 | In v1 it was possible to do this: 114 | 115 | ``` shell 116 | export INSTANCES="=tcp:3306,=tcp:5432" 117 | 118 | ./cloud_sql_proxy 119 | ``` 120 | 121 | In v2, we've significantly expanded the support for environment variables. 122 | All flags can be set with an environment variable including instance connection names. 123 | 124 | For example, in v2 this is possible: 125 | 126 | ``` shell 127 | export CSQL_PROXY_INSTANCE_CONNECTION_NAME_0="?port=3306" 128 | export CSQL_PROXY_INSTANCE_CONNECTION_NAME_1="?port=5432" 129 | 130 | export CSQL_PROXY_AUTO_IAM_AUTHN=true 131 | 132 | ./cloud-sql-proxy 133 | ``` 134 | 135 | See the [help message][] for more details. 136 | 137 | [help message]: https://github.com/GoogleCloudPlatform/cloud-sql-proxy/blob/10bec27e4d44c14fe9e68f25fef6c373324e8bab/cmd/root.go#L240-L264 138 | 139 | ## Flag Changes 140 | 141 | The following table lists in alphabetical order v1 flags and their v2 version. 142 | 143 | - 🗓️: Planned 144 | - ❌: Not supported in V2 145 | - 🤔: Unplanned, but has open feature request 146 | 147 | | v1 | v2 | Notes | 148 | | --------------------------- | --------------------- | ------------------------------------------------------------------------------------ | 149 | | check_region | ❌ | | 150 | | credential_file | credentials-file | | 151 | | dir | unix-socket | | 152 | | enable_iam_login | auto-iam-authn | | 153 | | fd_rlimit | 🤔 | [Feature Request](https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/1258) | 154 | | fuse | fuse | | 155 | | fuse_tmp | fuse-temp-dir | | 156 | | health_check_port | http-port | Use --http-address=0.0.0.0 when using a health check in Kubernetes | 157 | | host | sqladmin-api-endpoint | | 158 | | instances_metadata | 🤔 | [Feature Request](https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/1259) | 159 | | ip_address_types | private-ip | Defaults to public. To connect to a private IP, you must add the --private-ip flag | 160 | | log_debug_stdout | ❌ | v2 logs to stdout, errors to stderr by default | 161 | | max_connections | max-connections | | 162 | | projects | ❌ | v2 prefers explicit connection configuration to avoid user error | 163 | | quiet | quiet | quiet disables all logging except errors | 164 | | quota_project | quota-project | | 165 | | refresh_config_throttle | ❌ | | 166 | | skip_failed_instance_config | ❌ | This flag was only necessary with Unix sockets. Use TCP sockets to avoid failed startup. | 167 | | structured_logs | structured-logs | | 168 | | term_timeout | max-sigterm-delay | | 169 | | token | token | | 170 | | use_http_health_check | health-check | | 171 | | verbose | ❌ | | 172 | | version | version | | 173 | -------------------------------------------------------------------------------- /tests/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package tests contains end to end tests meant to verify the Cloud SQL Auth 16 | // proxy works as expected when executed as a binary. 17 | // 18 | // Required flags: 19 | // 20 | // -mysql_conn_name, -db_user, -db_pass 21 | package tests 22 | 23 | import ( 24 | "bufio" 25 | "context" 26 | "errors" 27 | "flag" 28 | "fmt" 29 | "io" 30 | "os" 31 | "strings" 32 | 33 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd" 34 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log" 35 | ) 36 | 37 | var ( 38 | impersonatedUser = flag.String( 39 | "impersonated_user", 40 | os.Getenv("IMPERSONATED_USER"), 41 | "Name of the service account that supports impersonation (impersonator must have roles/iam.serviceAccountTokenCreator)", 42 | ) 43 | ) 44 | 45 | // ProxyExec represents an execution of the Cloud SQL Auth Proxy. 46 | type ProxyExec struct { 47 | Out io.ReadCloser 48 | 49 | cmd *cmd.Command 50 | cancel context.CancelFunc 51 | closers []io.Closer 52 | done chan bool // closed once the cmd is completed 53 | err error 54 | } 55 | 56 | // StartProxy returns a proxyExec representing a running instance of the proxy. 57 | func StartProxy(ctx context.Context, args ...string) (*ProxyExec, error) { 58 | ctx, cancel := context.WithCancel(ctx) 59 | // Open a pipe for tracking the output from the cmd 60 | pr, pw, err := os.Pipe() 61 | if err != nil { 62 | cancel() 63 | return nil, fmt.Errorf("unable to open stdout pipe: %w", err) 64 | } 65 | 66 | c := cmd.NewCommand(cmd.WithLogger(log.NewStdLogger(pw, pw))) 67 | c.SetArgs(args) 68 | c.SetOut(pw) 69 | c.SetErr(pw) 70 | 71 | p := &ProxyExec{ 72 | Out: pr, 73 | cmd: c, 74 | cancel: cancel, 75 | closers: []io.Closer{pr, pw}, 76 | done: make(chan bool), 77 | } 78 | // Start the command in the background 79 | go func() { 80 | defer close(p.done) 81 | defer cancel() 82 | p.err = c.ExecuteContext(ctx) 83 | }() 84 | return p, nil 85 | } 86 | 87 | // Stop sends the TERM signal to the proxy and returns. 88 | func (p *ProxyExec) Stop() { 89 | p.cancel() 90 | } 91 | 92 | // Waits until the execution is completed and returns any error. 93 | func (p *ProxyExec) Wait(ctx context.Context) error { 94 | select { 95 | case <-ctx.Done(): 96 | return ctx.Err() 97 | case <-p.done: 98 | return p.err 99 | } 100 | } 101 | 102 | // Done returns true if the proxy has exited. 103 | func (p *ProxyExec) Done() bool { 104 | select { 105 | case <-p.done: 106 | return true 107 | default: 108 | } 109 | return false 110 | } 111 | 112 | // Close releases any resources associated with the instance. 113 | func (p *ProxyExec) Close() { 114 | p.cancel() 115 | for _, c := range p.closers { 116 | c.Close() 117 | } 118 | } 119 | 120 | // WaitForServe waits until the proxy ready to serve traffic by waiting for a 121 | // known log message (i.e. "ready for new connections"). Returns any output 122 | // from the proxy while starting or any errors experienced before the proxy was 123 | // ready to server. 124 | func (p *ProxyExec) WaitForServe(ctx context.Context) (string, error) { 125 | in := bufio.NewReader(p.Out) 126 | for { 127 | select { 128 | case <-ctx.Done(): 129 | // dump all output and return it as an error 130 | all, err := io.ReadAll(in) 131 | if err != nil { 132 | return "", err 133 | } 134 | return "", errors.New(string(all)) 135 | default: 136 | } 137 | s, err := in.ReadString('\n') 138 | if err != nil { 139 | return "", err 140 | } 141 | if strings.Contains(s, "Error") || strings.Contains(s, "error") { 142 | return "", errors.New(s) 143 | } 144 | if strings.Contains(s, "ready for new connections") { 145 | return s, nil 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tests/connection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tests 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "net/http" 21 | "net/http/httputil" 22 | "os" 23 | "testing" 24 | "time" 25 | 26 | "golang.org/x/oauth2" 27 | "golang.org/x/oauth2/google" 28 | ) 29 | 30 | const connTestTimeout = time.Minute 31 | 32 | // removeAuthEnvVar retrieves an OAuth2 token and a path to a service account key 33 | // and then unsets GOOGLE_APPLICATION_CREDENTIALS. It returns a cleanup function 34 | // that restores the original setup. 35 | func removeAuthEnvVar(t *testing.T) (*oauth2.Token, string, func()) { 36 | ts, err := google.DefaultTokenSource(context.Background(), 37 | "https://www.googleapis.com/auth/cloud-platform", 38 | ) 39 | if err != nil { 40 | t.Errorf("failed to resolve token source: %v", err) 41 | } 42 | tok, err := ts.Token() 43 | if err != nil { 44 | t.Errorf("failed to get token: %v", err) 45 | } 46 | if *ipType != "public" { 47 | return tok, "", func() {} 48 | } 49 | path, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS") 50 | if !ok { 51 | t.Fatalf("GOOGLE_APPLICATION_CREDENTIALS was not set in the environment") 52 | } 53 | if err := os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS"); err != nil { 54 | t.Fatalf("failed to unset GOOGLE_APPLICATION_CREDENTIALS") 55 | } 56 | return tok, path, func() { 57 | os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", path) 58 | } 59 | } 60 | 61 | func keyfile(t *testing.T) string { 62 | path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") 63 | if path == "" { 64 | t.Fatal("GOOGLE_APPLICATION_CREDENTIALS not set") 65 | } 66 | creds, err := os.ReadFile(path) 67 | if err != nil { 68 | t.Fatalf("io.ReadAll(): %v", err) 69 | } 70 | return string(creds) 71 | } 72 | func proxyConnTestWithReady(t *testing.T, args []string, driver, dsn string, ready func() error) { 73 | ctx, cancel := context.WithTimeout(context.Background(), connTestTimeout) 74 | defer cancel() 75 | // Start the proxy 76 | p, err := StartProxy(ctx, args...) 77 | if err != nil { 78 | t.Fatalf("unable to start proxy: %v", err) 79 | } 80 | defer p.Close() 81 | output, err := p.WaitForServe(ctx) 82 | if err != nil { 83 | t.Fatalf("unable to verify proxy was serving: %s \n %s", err, output) 84 | } 85 | if err := ready(); err != nil { 86 | t.Fatalf("proxy was not ready: %v", err) 87 | } 88 | 89 | // Connect to the instance 90 | db, err := sql.Open(driver, dsn) 91 | if err != nil { 92 | t.Fatalf("unable to connect to db: %s", err) 93 | } 94 | defer db.Close() 95 | _, err = db.Exec("SELECT 1;") 96 | if err != nil { 97 | t.Fatalf("unable to exec on db: %s", err) 98 | } 99 | } 100 | 101 | // proxyConnTest is a test helper to verify the proxy works with a basic connectivity test. 102 | func proxyConnTest(t *testing.T, args []string, driver, dsn string) { 103 | proxyConnTestWithReady(t, args, driver, dsn, func() error { return nil }) 104 | } 105 | 106 | // testHealthCheck verifies that when a proxy client serves the given instance, 107 | // the readiness endpoint serves http.StatusOK. 108 | func testHealthCheck(t *testing.T, connName string) { 109 | t.Helper() 110 | ctx, cancel := context.WithTimeout(context.Background(), connTestTimeout) 111 | defer cancel() 112 | 113 | args := []string{connName, "--health-check"} 114 | // Start the proxy. 115 | p, err := StartProxy(ctx, args...) 116 | if err != nil { 117 | t.Fatalf("unable to start proxy: %v", err) 118 | } 119 | defer p.Close() 120 | _, err = p.WaitForServe(ctx) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | 125 | var ( 126 | gErr error 127 | resp *http.Response 128 | ) 129 | for i := 0; i < 10; i++ { 130 | resp, gErr = http.Get("http://localhost:9090/readiness") 131 | if gErr != nil { 132 | time.Sleep(100 * time.Millisecond) 133 | continue 134 | } 135 | if resp.StatusCode != http.StatusOK { 136 | time.Sleep(100 * time.Millisecond) 137 | continue 138 | } 139 | return // The response is OK, the test passes. 140 | } 141 | if gErr != nil { 142 | t.Fatalf("HTTP GET failed: %v", gErr) 143 | } 144 | respBody, dErr := httputil.DumpResponse(resp, true) 145 | if dErr != nil { 146 | t.Fatalf("failed to dump HTTP response: %v", dErr) 147 | } 148 | t.Fatalf("HTTP GET failed: response =\n%v", string(respBody)) 149 | } 150 | -------------------------------------------------------------------------------- /tests/fuse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows && !darwin 16 | 17 | package tests 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "testing" 23 | "time" 24 | 25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy" 26 | ) 27 | 28 | func TestPostgresFUSEConnect(t *testing.T) { 29 | if v := os.Getenv("IP_TYPE"); v == "private" || v == "psc" { 30 | t.Skipf("skipping test because IP_TYPE is set to %v", v) 31 | } 32 | if testing.Short() { 33 | t.Skip("skipping Postgres integration tests") 34 | } 35 | tmpDir, cleanup := createTempDir(t) 36 | defer cleanup() 37 | 38 | host := proxy.UnixAddress(tmpDir, *postgresConnName) 39 | dsn := fmt.Sprintf( 40 | "host=%s user=%s password=%s database=%s sslmode=disable", 41 | host, *postgresUser, *postgresPass, *postgresDB, 42 | ) 43 | testFUSE(t, tmpDir, host, dsn) 44 | } 45 | 46 | func testFUSE(t *testing.T, tmpDir, host string, dsn string) { 47 | tmpDir2, cleanup2 := createTempDir(t) 48 | defer cleanup2() 49 | 50 | waitForFUSE := func() error { 51 | var err error 52 | for i := 0; i < 10; i++ { 53 | _, err = os.Stat(host) 54 | if err == nil { 55 | return nil 56 | } 57 | time.Sleep(500 * time.Millisecond) 58 | } 59 | return fmt.Errorf("failed to find FUSE mounted Unix socket: %v", err) 60 | } 61 | 62 | tcs := []struct { 63 | desc string 64 | dbUser string 65 | args []string 66 | }{ 67 | { 68 | desc: "using default fuse", 69 | args: []string{fmt.Sprintf("--fuse=%s", tmpDir), fmt.Sprintf("--fuse-tmp-dir=%s", tmpDir2)}, 70 | }, 71 | { 72 | desc: "using fuse with auto-iam-authn", 73 | args: []string{fmt.Sprintf("--fuse=%s", tmpDir), "--auto-iam-authn"}, 74 | }, 75 | } 76 | 77 | for _, tc := range tcs { 78 | t.Run(tc.desc, func(t *testing.T) { 79 | proxyConnTestWithReady(t, tc.args, "pgx", dsn, waitForFUSE) 80 | // given the kernel some time to unmount the fuse 81 | time.Sleep(100 * time.Millisecond) 82 | }) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /tests/other_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // other_test runs various tests that are database agnostic. 16 | package tests 17 | 18 | import ( 19 | "bufio" 20 | "context" 21 | "os" 22 | "strings" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | func TestVersion(t *testing.T) { 28 | ctx := context.Background() 29 | 30 | data, err := os.ReadFile("../cmd/version.txt") 31 | if err != nil { 32 | t.Fatalf("failed to read version.txt: %v", err) 33 | } 34 | want := strings.TrimSpace(string(data)) 35 | 36 | // Start the proxy 37 | p, err := StartProxy(ctx, "--version") 38 | if err != nil { 39 | t.Fatalf("proxy start failed: %v", err) 40 | } 41 | defer p.Close() 42 | 43 | // Assume the proxy should be able to print "version" relatively quickly 44 | ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) 45 | defer cancel() 46 | err = p.Wait(ctx) 47 | if err != nil { 48 | t.Fatalf("proxy exited unexpectedly: %v", err) 49 | } 50 | output, err := bufio.NewReader(p.Out).ReadString('\n') 51 | if err != nil { 52 | t.Fatalf("failed to read output from proxy: %v", err) 53 | } 54 | if !strings.Contains(output, want) { 55 | t.Errorf("proxy did not return correct version: want %q, got %q", want, output) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/sqlserver_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // sqlserver_test runs various tests against a SqlServer flavored Cloud SQL instance. 16 | package tests 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | "os" 22 | "testing" 23 | 24 | _ "github.com/microsoft/go-mssqldb" 25 | ) 26 | 27 | var ( 28 | sqlserverConnName = flag.String("sqlserver_conn_name", os.Getenv("SQLSERVER_CONNECTION_NAME"), "Cloud SQL SqlServer instance connection name, in the form of 'project:region:instance'.") 29 | sqlserverUser = flag.String("sqlserver_user", os.Getenv("SQLSERVER_USER"), "Name of database user.") 30 | sqlserverPass = flag.String("sqlserver_pass", os.Getenv("SQLSERVER_PASS"), "Password for the database user; be careful when entering a password on the command line (it may go into your terminal's history).") 31 | sqlserverDB = flag.String("sqlserver_db", os.Getenv("SQLSERVER_DB"), "Name of the database to connect to.") 32 | ) 33 | 34 | func requireSQLServerVars(t *testing.T) { 35 | switch "" { 36 | case *sqlserverConnName: 37 | t.Fatal("'sqlserver_conn_name' not set") 38 | case *sqlserverUser: 39 | t.Fatal("'sqlserver_user' not set") 40 | case *sqlserverPass: 41 | t.Fatal("'sqlserver_pass' not set") 42 | case *sqlserverDB: 43 | t.Fatal("'sqlserver_db' not set") 44 | } 45 | } 46 | 47 | func sqlserverDSN() string { 48 | return fmt.Sprintf("sqlserver://%s:%s@127.0.0.1?database=%s", 49 | *sqlserverUser, *sqlserverPass, *sqlserverDB) 50 | } 51 | 52 | func TestSQLServerTCP(t *testing.T) { 53 | if testing.Short() { 54 | t.Skip("skipping SQL Server integration tests") 55 | } 56 | requireSQLServerVars(t) 57 | // Prepare the initial arguments 58 | args := []string{*sqlserverConnName} 59 | // Add the IP type flag using the helper 60 | args = AddIPTypeFlag(args) 61 | // Run the test 62 | proxyConnTest(t, args, "sqlserver", sqlserverDSN()) 63 | } 64 | 65 | func TestSQLServerImpersonation(t *testing.T) { 66 | if testing.Short() { 67 | t.Skip("skipping SQL Server integration tests") 68 | } 69 | requireSQLServerVars(t) 70 | // Prepare the initial arguments 71 | args := []string{ 72 | "--impersonate-service-account", *impersonatedUser, 73 | *sqlserverConnName, 74 | } 75 | // Add the IP type flag using the helper 76 | args = AddIPTypeFlag(args) 77 | // Run the test 78 | proxyConnTest(t, args, "sqlserver", sqlserverDSN()) 79 | } 80 | 81 | func TestSQLServerAuthentication(t *testing.T) { 82 | if testing.Short() { 83 | t.Skip("skipping SQL Server integration tests") 84 | } 85 | requireSQLServerVars(t) 86 | 87 | var creds string 88 | if *ipType == "public" { 89 | creds = keyfile(t) 90 | } 91 | tok, path, cleanup := removeAuthEnvVar(t) 92 | defer cleanup() 93 | 94 | tcs := []struct { 95 | desc string 96 | args []string 97 | }{ 98 | { 99 | desc: "with token", 100 | args: []string{"--token", tok.AccessToken, *sqlserverConnName}, 101 | }, 102 | { 103 | desc: "with token and impersonation", 104 | args: []string{ 105 | "--token", tok.AccessToken, 106 | "--impersonate-service-account", *impersonatedUser, 107 | *sqlserverConnName}, 108 | }, 109 | } 110 | if *ipType == "public" { 111 | additionaTcs := []struct { 112 | desc string 113 | args []string 114 | }{ 115 | { 116 | desc: "with credentials file", 117 | args: []string{"--credentials-file", path, *sqlserverConnName}, 118 | }, 119 | { 120 | desc: "with credentials file and impersonation", 121 | args: []string{ 122 | "--credentials-file", path, 123 | "--impersonate-service-account", *impersonatedUser, 124 | *sqlserverConnName, 125 | }, 126 | }, 127 | { 128 | desc: "with credentials JSON", 129 | args: []string{"--json-credentials", string(creds), *sqlserverConnName}, 130 | }, 131 | { 132 | desc: "with credentials JSON and impersonation", 133 | args: []string{ 134 | "--json-credentials", string(creds), 135 | "--impersonate-service-account", *impersonatedUser, 136 | *sqlserverConnName, 137 | }, 138 | }, 139 | } 140 | tcs = append(tcs, additionaTcs...) 141 | } 142 | for _, tc := range tcs { 143 | t.Run(tc.desc, func(t *testing.T) { 144 | proxyConnTest(t, AddIPTypeFlag(tc.args), "sqlserver", sqlserverDSN()) 145 | }) 146 | } 147 | } 148 | 149 | func TestSQLServerGcloudAuth(t *testing.T) { 150 | if testing.Short() { 151 | t.Skip("skipping SQL Server integration tests") 152 | } 153 | if v := os.Getenv("IP_TYPE"); v == "private" || v == "psc" { 154 | t.Skipf("skipping test because IP_TYPE is set to %v", v) 155 | } 156 | requireSQLServerVars(t) 157 | 158 | tcs := []struct { 159 | desc string 160 | args []string 161 | }{ 162 | { 163 | desc: "gcloud user authentication", 164 | args: []string{"--gcloud-auth", *sqlserverConnName}, 165 | }, 166 | { 167 | desc: "gcloud user authentication with impersonation", 168 | args: []string{ 169 | "--gcloud-auth", 170 | "--impersonate-service-account", *impersonatedUser, 171 | *sqlserverConnName}, 172 | }, 173 | } 174 | for _, tc := range tcs { 175 | t.Run(tc.desc, func(t *testing.T) { 176 | proxyConnTest(t, AddIPTypeFlag(tc.args), "sqlserver", sqlserverDSN()) 177 | }) 178 | } 179 | } 180 | 181 | func TestSQLServerHealthCheck(t *testing.T) { 182 | if testing.Short() { 183 | t.Skip("skipping SQL Server integration tests") 184 | } 185 | testHealthCheck(t, *sqlserverConnName) 186 | } 187 | -------------------------------------------------------------------------------- /windows-service-guide.md: -------------------------------------------------------------------------------- 1 | # Cloud SQL Auth Proxy Windows Service Guide 2 | 3 | This document covers running the *Cloud SQL Auth Proxy* as service 4 | on the Windows operating system. 5 | 6 | It was originally built and tested using Go 1.20.2 on Windows Server 2019. 7 | 8 | ## Install the Windows Service 9 | 10 | Prerequisites: A built binary for Windows of the Cloud SQL Auth Proxy is required. Either build it from source or [download a release](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/releases) of a Windows pre-built version, e.g. `cloud-sql-proxy.x64.exe`. 11 | 12 | First, install the binary by: 13 | 14 | 1. Create a new empty folder, e.g. `C:\Program Files\cloud-sql-proxy` 15 | 2. Copy the binary and helper batch files 16 | 3. Modify the batch files as needed: 17 | - `SERVICE` is the Windows internal service name (as shown in the Task Manager) 18 | - `DISPLAYNAME` is the service name (as shown in the Windows Administration Console (MMC)) 19 | - `CREDENTIALSFILE` is the *full* path to the credentials file, where `%~dp0` points to the full path of the script file folder. 20 | - `CONNECTIONNAME` is the Google SQL connection name in the format of `project-id:region:db-instance` 21 | - Please note that the `--credentials-file \"%CREDENTIALSFILE%\"` argument is optional and is not needed if the local machine runs within the Google Cloud Compute Engine and "defaults" to the VM instance service account. 22 | 4. Grant *read & execute* access to the `Network Service` user 23 | 5. Create a `logs` sub-folder, e.g. `C:\Program Files\cloud-sql-proxy\logs` 24 | 6. Grant *modify* access to the `Network Service` user 25 | 7. Run the `windows_install_service.bat` batch file within an *elevated* command line prompt (read: *Run as Administrator*). 26 | 27 | After that, perform the setup: 28 | 29 | 1. Copy the JSON credentials file, if required 30 | 2. Modify the `windows_install_service.bat` file to your needs 31 | 3. Run the `windows_install_service.bat` file from the commandline 32 | 33 | Please see the FAQ below for common error messages. 34 | 35 | ## Uninstall the Windows Service 36 | 37 | To uninstall the Windows Service, perform the following steps: 38 | 39 | 1. Modify the `windows_remove_service.bat` file to your needs 40 | 2. Run the `windows_remove_service.bat` file from the commandline 41 | 42 | ## FAQ 43 | 44 | ### Error Message: *Access is denied* 45 | 46 | The error message `Access is denied.` (or `System error 5 has occurred.`) occurs when 47 | trying to start the installed service but the service account does not have access 48 | to the service's file directory. 49 | 50 | Usually this is the *Network Service* built-in user. 51 | 52 | Please note that write access is also required for creating and managing the log files, e.g.: 53 | 54 | - `cloud-sql-proxy.log` 55 | - `cloud-sql-proxy-2016-11-04T18-30-00.000.log` 56 | 57 | ### Error Message: *The specified service has been marked for deletion.* 58 | 59 | The error message `The specified service has been marked for deletion.` occurs when 60 | reinstalling the service and the previous deletion request could not be completed 61 | (e.g. because the service was still running or opened in the service manager). 62 | 63 | In this case, the local machine needs to be restarted. 64 | 65 | ### Why not running as the *System* user? 66 | 67 | Since the Cloud Proxy does not require and file system access, besides the log files, 68 | extensive operating system access is not required. 69 | 70 | The *Network Service* accounts allow binding ports while not granting 71 | access to file system resources. 72 | 73 | ### Why not using *Automatic (Delayed Start)* startup type? 74 | 75 | The service is installed in the *Automatic* startup type, by default. 76 | 77 | The alternative *Automatic (Delayed Start)* startup type was introduced 78 | by Microsoft for services that are not required for operating system operations 79 | like Windows Update and similar services. 80 | 81 | However, if the primary purpose of the local machine is to provide services 82 | which require access to the cloud database, then the start of the service 83 | should not be delayed. 84 | 85 | Delayed services might be started even minutes after operating system startup. 86 | -------------------------------------------------------------------------------- /windows_install_service.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | set SERVICE=cloud-sql-proxy 6 | set DISPLAYNAME=Google Cloud SQL Auth Proxy 7 | set CREDENTIALSFILE=%~dp0key.json 8 | set CONNECTIONNAME=project-id:region:db-instance 9 | 10 | sc.exe create "%SERVICE%" binPath= "\"%~dp0cloud-sql-proxy.exe\" --credentials-file \"%CREDENTIALSFILE%\" %CONNECTIONNAME%" obj= "NT AUTHORITY\Network Service" start= auto displayName= "%DISPLAYNAME%" 11 | sc.exe failure "%SERVICE%" reset= 0 actions= restart/0/restart/0/restart/0 12 | net start "%SERVICE%" 13 | 14 | endlocal 15 | -------------------------------------------------------------------------------- /windows_remove_service.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | set SERVICE=cloud-sql-proxy 6 | 7 | net stop "%SERVICE%" 8 | sc.exe delete "%SERVICE%" 9 | 10 | endlocal 11 | --------------------------------------------------------------------------------