pax_global_header00006660000000000000000000000064136501035020014505gustar00rootroot0000000000000052 comment=fb1939ab92846761595833361c6b0b0ecd543861 cloudsql-proxy-1.17.0/000077500000000000000000000000001365010350200146005ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.github/000077500000000000000000000000001365010350200161405ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001365010350200203235ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000024411365010350200227340ustar00rootroot00000000000000--- name: Bug Report about: Report defective or unintentional behavior you've experienced. title: "Brief summary of what bug or error was observed" labels: 'type: bug' --- ## Bug Description Please enter a detailed description of the bug, and any information about what behavior you noticed and how it differs from what you expected. ## Example code (or command) ``` // example ``` ## Stacktrace ``` Any relevant stacktrace here. Be sure to filter sensitive information. ``` ## How to reproduce 1. ? 2. ? ## Environment 1. OS type and version: 2. Cloud SQL Proxy version (`./cloud_sql_proxy -version`): cloudsql-proxy-1.17.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000006021365010350200223110ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Cloud SQL Issue tracker url: https://issuetracker.google.com/savedsearches/559773 about: Please use the Cloud SQL Issue tracker for problems with Cloud SQL itself. - name: StackOverflow url: https://stackoverflow.com/questions/tagged/google-cloud-sql about: Please use the `google-cloud-sql` tag for questions on StackOverflow.cloudsql-proxy-1.17.0/.github/ISSUE_TEMPLATE/documentation-issue.md000066400000000000000000000022731365010350200246500ustar00rootroot00000000000000--- name: Documentation Issue about: Report wrong or missing information with the documentation in the repo. title: "Brief summary of what is missing or incorrect" labels: 'type: docs' --- ## Description Provide a short description of what is missing or incorrect, as well as a link to the specific location of the information. ## Solution What would you prefer the documentation say? Why would this information be more accurate or helpful? ## Additional Context Please reference any other relevant issues, PRs, descriptions, or screenshots here. cloudsql-proxy-1.17.0/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000022751365010350200237740ustar00rootroot00000000000000--- name: Feature Request about: Suggest an idea for new or improved behavior. title: "Brief summary of the proposed feature" labels: 'type: feature request' --- ## Feature Description A clear and concise description of what feature you would like to see, and why it would be useful to have added. ## Alternatives Considered Are there any workaround or third party tools to replicate this behavior? Why would adding this feature be preferred over them? ## Additional Context Please reference any other issues, PRs, descriptions, or screenshots here. cloudsql-proxy-1.17.0/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000020321365010350200225110ustar00rootroot00000000000000--- name: Question about: Questions on how something works or the best way to do something. title: "Breif summary of your question" labels: 'type: question' --- ## Question What's your question? Please provide as much relevant information as possible to reduce turnaround time. ## Additional Context Please reference any other relevant issues, PRs, descriptions, or screenshots here. cloudsql-proxy-1.17.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000007741365010350200217510ustar00rootroot00000000000000## Change Description Please provide a detailed description on what changes your PR will have. ## Checklist - [ ] Make sure to open an issue as a [bug/issue](https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea. - [ ] Ensure the tests and linter pass - [ ] Appropriate documentation is updated (if necessary) ## Relevant issues: - Fixes #cloudsql-proxy-1.17.0/.github/blunderbuss.yml000066400000000000000000000002221365010350200212070ustar00rootroot00000000000000assign_issues: - dmahugh # - jsimonweb # - shubha-rajan # - kurtisvg assign_prs: # - dmahugh # - jsimonweb # - shubha-rajan - kurtisvg cloudsql-proxy-1.17.0/.gitignore000066400000000000000000000001761365010350200165740ustar00rootroot00000000000000 # Jetbrains IDE .idea/ # Compiled binary cmd/cloud_sql_proxy/cloud_sql_proxy # Compiled during tests tests/cloud_sql_proxy cloudsql-proxy-1.17.0/.kokoro/000077500000000000000000000000001365010350200161625ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.kokoro/go113/000077500000000000000000000000001365010350200170145ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.kokoro/go113/common.cfg000066400000000000000000000022011365010350200207600ustar00rootroot00000000000000# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto # Get secrets for tests. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/cloud-sql/proxy" # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. build_file: "cloud-sql-proxy/.kokoro/trampoline.sh" env_vars: { key: "TRAMPOLINE_IMAGE" value: "golang:1.13" } # Tell the trampoline which tests to run. env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/cloud-sql-proxy/.kokoro/tests/run_gofmt.sh" }cloudsql-proxy-1.17.0/.kokoro/go113/continuous.cfg000066400000000000000000000011651365010350200217060ustar00rootroot00000000000000# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto cloudsql-proxy-1.17.0/.kokoro/go113/periodic.cfg000066400000000000000000000011651365010350200212760ustar00rootroot00000000000000# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto cloudsql-proxy-1.17.0/.kokoro/go113/presubmit.cfg000066400000000000000000000011651365010350200215120ustar00rootroot00000000000000# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto cloudsql-proxy-1.17.0/.kokoro/lint/000077500000000000000000000000001365010350200171305ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.kokoro/lint/common.cfg000066400000000000000000000020341365010350200211000ustar00rootroot00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. build_file: "cloud-sql-proxy/.kokoro/trampoline.sh" env_vars: { key: "TRAMPOLINE_IMAGE" value: "golang:1.13" } # Tell the trampoline which tests to run. env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/cloud-sql-proxy/.kokoro/tests/run_gofmt.sh" }cloudsql-proxy-1.17.0/.kokoro/lint/continuous.cfg000066400000000000000000000011651365010350200220220ustar00rootroot00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto cloudsql-proxy-1.17.0/.kokoro/lint/periodic.cfg000066400000000000000000000011651365010350200214120ustar00rootroot00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto cloudsql-proxy-1.17.0/.kokoro/lint/presubmit.cfg000066400000000000000000000011651365010350200216260ustar00rootroot00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Format: //devtools/kokoro/config/proto/build.proto cloudsql-proxy-1.17.0/.kokoro/tests/000077500000000000000000000000001365010350200173245ustar00rootroot00000000000000cloudsql-proxy-1.17.0/.kokoro/tests/run_gofmt.sh000077500000000000000000000021551365010350200216660ustar00rootroot00000000000000#!/bin/bash # Copyright 2020 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDIcd TIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # `-e` enables the script to automatically fail when a command fails set -e # Move into project directory cd github/cloud-sql-proxy # Download and verify dependencies are valid echo -e "******************** Verifing dependencies... ********************\n" go get -t -v ./... echo -e "******************** Dependencies verified. ********************\n" # Verify echo -e "******************** Running gofmt... ********************\n" diff -u <(echo -n) <(gofmt -d .) echo -e "******************** Gofmt complete. ******************** \n" cloudsql-proxy-1.17.0/.kokoro/tests/run_tests.sh000066400000000000000000000022031365010350200217030ustar00rootroot00000000000000#!/bin/bash # Copyright 2020 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDIcd TIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # `-e` enables the script to automatically fail when a command fails set -e # Download and verify dependencies are valid echo -e "******************** Verifing dependencies... ********************\n" go get -t -v ./... echo -e "******************** Dependencies verified. ********************\n" # Load in secrets if [ -n "$KOKORO_GFILE_DIR" ]; then source "${KOKORO_GFILE_DIR}/TEST_SECRETS.sh" fi echo -e "******************** Running tests... ********************\n" go test -v ./... echo -e "******************** Tests complete. ********************\n"cloudsql-proxy-1.17.0/.kokoro/trampoline.sh000066400000000000000000000011731365010350200206720ustar00rootroot00000000000000#!/bin/bash # Copyright 2019 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py"cloudsql-proxy-1.17.0/CONTRIBUTING.md000066400000000000000000000134661365010350200170430ustar00rootroot00000000000000# Contributing 1. **Please sign one of the contributor license agreements below!** 1. Fork the repo, develop and test your code changes, add docs. 1. Make sure that your commit messages clearly describe the changes. 1. Send a pull request. ## Table of contents * [Opening an issue](#opening-an-issue) * [Contributor License Agreements](#contributor-license-agreements) * [Contributor Code of Conduct](#contributor-code-of-conduct) ## Opening an issue If you find a bug in the proxy code or an inaccuracy in the documentation, please open an issue. GitHub provides a guide, [Mastering Issues](https://guides.github.com/features/issues/), that is useful if you are unfamiliar with the process. Here are the specific steps for opening an issue: 1. Go to the project issues page on GitHub. 1. Click the green `New Issue` button located in the upper right corner. 1. In the title field, write a single phrase that identifies your issue. 1. In the main editor, describe your issue. 1. Click the submit button. Thank you. We will do our best to triage your issue within one business day, and attempt to categorize your issues with an estimate of the priority and issue type. We will try to respond with regular updates based on its priority: * **Critical** respond and update daily, resolve with a week * **High** respond and update weekly, resolve within six weeks * **Medium** respond and update every three months, best effort resolution * **Low** respond and update every six months, best effort resolution The priority we assign will be roughly a function of the number of users we expect to be impacted, as well as its severity. As a rule of thumb:
Severity Number of users
Handful Some Most All
Easy, obvious workaround Low Low Medium High
Non-obvious workaround available Low Medium High Critical
Functionality blocked High High Critical Critical
## Contributor License Agreements Open-source software licensing is a wonderful arrangement that benefits everyone, but in an imperfect world, we all need to exercise some legal prudence. In order to protect you, Google, and most of all, everyone who comes to depend on these libraries, we require that all contributors sign our short and human-readable Contributor License Agreement (CLA). We don't want to open the door to patent trolls, predatory lawyers, or anyone else who isn't on board with creating value and making the world a better place. We hope you will agree that the CLA offers very important protection and is easy to understand. Take a moment to read it carefully, and if you agree with what you read, please sign it now. If you believe you've already signed the appropriate CLA already for this or any other Google open-source project, you shouldn't have to do so again. You can review your signed CLAs at [cla.developers.google.com/clas](https://cla.developers.google.com/clas). First, check that you are signed in to a [Google Account](https://accounts.google.com) that matches your [local Git email address](https://help.github.com/articles/setting-your-email-in-git/). Then choose one of the following: * If you are **an individual writing original source code** and **you own the intellectual property**, sign the [Individual CLA](https://developers.google.com/open-source/cla/individual). * If you work for **a company that wants to allow you to contribute**, sign the [Corporate CLA](https://developers.google.com/open-source/cla/corporate). You (and your authorized signer, if corporate) can sign the CLA electronically. After that, we'll be able to accept your contributions. ## Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) cloudsql-proxy-1.17.0/CONTRIBUTORS000066400000000000000000000027041365010350200164630ustar00rootroot00000000000000# This is the official list of people who can contribute # (and typically have contributed) code to the repository. # The AUTHORS file lists the copyright holders; this file # lists people. For example, Google employees are listed here # but not in AUTHORS, because Google holds the copyright. # # The submission process automatically checks to make sure # that people submitting code are listed in this file (by email address). # # Names should be added to this file only after verifying that # the individual or the individual's organization has agreed to # the appropriate Contributor License Agreement, found here: # # https://cla.developers.google.com/about/google-individual # https://cla.developers.google.com/about/google-corporate # # The CLA can be filled out on the web: # # https://cla.developers.google.com/ # # When adding J Random Contributor's name to this file, # either J's name or J's organization's name should be # added to the AUTHORS file, depending on whether the # individual or corporate CLA was used. # Names should be added to this file like so: # Name # # An entry with two email addresses specifies that the # first address should be used in the submit logs and # that the second address should be recognized as the # same person when interacting with Rietveld. # Please keep the list sorted. Mykola Smith Frank van Rest Kevin Malachowski cloudsql-proxy-1.17.0/Dockerfile000066400000000000000000000017451365010350200166010ustar00rootroot00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Use the latest stable golang 1.x to compile to a binary FROM golang:1 as build ARG VERSION="1.17" WORKDIR /go/src/cloudsql-proxy COPY . . RUN go get ./... RUN go build -ldflags "-X 'main.versionString=$VERSION'" -o cloud_sql_proxy ./cmd/cloud_sql_proxy # Final Stage FROM gcr.io/distroless/base-debian10:nonroot COPY --from=build --chown=nonroot /go/src/cloudsql-proxy/cloud_sql_proxy /cloud_sql_proxy USER nonroot cloudsql-proxy-1.17.0/Kubernetes.md000066400000000000000000000113201365010350200172260ustar00rootroot00000000000000Cloud SQL Proxy in a Kubernetes cluster ======================================= The goal of this guide is to help you set-up and use Google Cloud SQL in a Kubernetes cluster (GKE or not), through the Cloud SQL Proxy. To make this as easy as possible, we will use the prepared docker image so we can minimize the number of steps. No compilation needed! Pre-requisites: --------------- In order to set-up the Cloud SQL you will need, - One or more Google Cloud SQL Databases. Refer to [the documentation](https://cloud.google.com/sql/docs/) to create them. - We will assume the name of the database instances are as follow: `project:database1`, `project:database2`, etc. - You need a service-account token with "Project Editor" privilegies, and we will assume the file is in `$HOME/credentials.json`. Refer to [the documentation](https://cloud.google.com/docs/authentication#developer_workflow) to get the json credential file. - Your `$HOME/.kube/config` points to your cluster and the namespace you want to use. Overview -------- The recommended way to use the Cloud SQL Proxy in a Kubernetes cluster is to use a TCP connection, as this allows the pod to be located on any node. We will use [Kubernetes DNS service](http://kubernetes.io/docs/admin/dns/) to connect to the proxy seamlessly. Setting-up the credentials -------------------------- We need to create a secret to store the credentials that the Cloud Proxy needs to connect to the project database instances: ``` kubectl create secret generic service-account-token --from-file=credentials.json=$HOME/credentials.json ``` Creating the Cloud SQL Proxy deployment --------------------------------------- We need to create a deployment that will keep the Cloud SQL Proxy container image alive. Here is an example deployment file, `sqlproxy-deployment.yaml`: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: name: cloudsqlproxy spec: replicas: 1 template: metadata: labels: app: cloudsqlproxy spec: containers: # Make sure to specify image tag in production # Check out the newest version in release page # https://github.com/GoogleCloudPlatform/cloudsql-proxy/releases - image: b.gcr.io/cloudsql-docker/gce-proxy:latest # 'Always' if imageTag is 'latest', else set to 'IfNotPresent' imagePullPolicy: Always name: cloudsqlproxy command: - /cloud_sql_proxy - -dir=/cloudsql - -instances=project:database1=tcp:0.0.0.0:3306,project:database2=tcp:0.0.0.0:3307 - -credential_file=/credentials/credentials.json # set term_timeout if require graceful handling of shutdown # NOTE: proxy will stop accepting new connections; only wait on existing connections - term_timeout=10s lifecycle: preStop: exec: # (optional) add a preStop hook so that termination is delayed # this is required if your server still require new connections (e.g., connection pools) command: ['sleep', '10'] ports: - name: port-database1 containerPort: 3306 - name: port-database2 containerPort: 3307 volumeMounts: - mountPath: /cloudsql name: cloudsql - mountPath: /credentials name: service-account-token volumes: - name: cloudsql emptyDir: - name: service-account-token secret: secretName: service-account-token ``` And then, create the deployment: ``` kubectl apply -f sqlproxy-deployment.yaml ``` This deployment will create pods that listen for connections on port `3306` for `project:database1`, and `3307` for `project:database2`. You can also change the number of replicas to increase availability. Services to find the proxy -------------------------- We can create services to find the pods. We have decided to use one service per database to be able to select the database by name rather than by port. Create the services configuration, `sqlproxy-services.yaml`: ``` apiVersion: v1 kind: Service metadata: name: sqlproxy-service-database1 spec: ports: - port: 3306 targetPort: port-database1 selector: app: cloudsqlproxy --- apiVersion: v1 kind: Service metadata: name: sqlproxy-service-database2 spec: ports: - port: 3306 targetPort: port-database2 selector: app: cloudsqlproxy ``` This will create two different services, `sqlproxy-service-database1` and `sqlproxy-service-database2`. Apply the configuration to create them: ``` kubectl apply -f sqlproxy-services.yaml ``` You can now connect using the same port `3306` to each database: ``` mysql --host=sqlproxy-service-database1 --port=3306 ... mysql --host=sqlproxy-service-database2 --port=3306 ... ``` cloudsql-proxy-1.17.0/LICENSE000066400000000000000000000261361365010350200156150ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cloudsql-proxy-1.17.0/README.md000066400000000000000000000151441365010350200160640ustar00rootroot00000000000000 ## Cloud SQL Proxy The Cloud SQL Proxy allows a user with the appropriate permissions to connect to a Second Generation Cloud SQL database without having to deal with IP whitelisting or SSL certificates manually. It works by opening unix/tcp sockets on the local machine and proxying connections to the associated Cloud SQL instances when the sockets are used. To build from source, ensure you have [go installed](https://golang.org/doc/install) and have set [GOPATH](https://github.com/golang/go/wiki/GOPATH). Then, simply do a go get: go get github.com/GoogleCloudPlatform/cloudsql-proxy/cmd/cloud_sql_proxy The cloud_sql_proxy will be placed in $GOPATH/bin after go get completes. cloud_sql_proxy takes a few arguments to configure what instances to connect to and connection behavior: * `-fuse`: requires access to `/dev/fuse` as well as the `fusermount` binary. An optional `-fuse_tmp` flag can specify where to place temporary files. The directory indicated by `-dir` is mounted. * `-instances="project1:region:instance1,project3:region:instance1"`: A comma-separated list of instances to open inside `-dir`. Also supports exposing a tcp port and renaming the default Unix Domain Sockets; see examples below. Same list can be provided via INSTANCES environment variable, in case when both are provided - proxy will use command line flag. * `-instances_metadata=metadata_key`: Usable on [GCE](https://cloud.google.com/compute/docs/quickstart) only. The given [GCE metadata](https://cloud.google.com/compute/docs/metadata) key will be polled for a list of instances to open in `-dir`. The metadata key is relative from `computeMetadata/v1/`. The format for the value is the same as the 'instances' flag. A hanging-poll strategy is used, meaning that changes to the metadata value will be reflected in the `-dir` even while the proxy is running. When an instance is removed from the list the corresponding socket will be removed from `-dir` as well (unless it was also specified in `-instances`), but any existing connections to this instance will NOT be terminated. * `-ip_address_types=PUBLIC,PRIVATE`: A comma-delimited list of preferred IP types for connecting to an instance. For example, setting this to PRIVATE will force the proxy to connect to instances using an instance's associated private IP. Defaults to `PUBLIC,PRIVATE` * `-term_timeout=30s`: How long to wait for connections to close before shutting down the proxy. Defaults to 0. * `-skip_failed_instance_config`: Setting this flag will allow you to prevent the proxy from terminating when some instance configurations could not be parsed and/or are unavailable. Note: `-instances` and `-instances_metadata` may be used at the same time but are not compatible with the `-fuse` flag. cloud_sql_proxy authentication can be configured in a few different ways. Those listed higher on the list will override options lower on the list: 1. `credential_file` flag 2. `token` flag 3. Service account key at path stored in `GOOGLE_APPLICATION_CREDENTIALS` 4. gcloud _user_ credentials (set from `gcloud auth login`) 5. Default Application Credentials via goauth: 1. `GOOGLE_APPLICATION_CREDENTIALS` (again) 2. gcloud _application default_ credentials (set from ` gcloud auth application-default login`) 3. appengine.AccessToken (for App Engine Go < =1.9) 4. GCE/GAE metadata credentials When the proxy authenticates under the default service account of the Compute Engine VM it is running on the VM must have at least the sqlservice.admin API scope ("https://www.googleapis.com/auth/sqlservice.admin") and the associated project must have the SQL Admin API enabled. The default service account must also have at least WRITER/EDITOR priviledges to any projects of target SQL instances. Specifying the `-credential_file` flag allows use of the proxy outside of Google's cloud. Simply [create a new service account](https://cloud.google.com/sql/docs/mysql/sql-proxy#create-service-account), download the associated JSON file, and set `-credential_file` to the path of the JSON file. You can also set the GOOGLE_APPLICATION_CREDENTIALS environment variable instead of passing this flag. ## Example invocations: ./cloud_sql_proxy -dir=/cloudsql -instances=my-project:us-central1:sql-inst & mysql -u root -S /cloudsql/my-project:us-central1:sql-inst # To retrieve instances from a custom metadata value (only when running on GCE) ./cloud_sql_proxy -dir=/cloudsql -instances_metadata instance/attributes/ & mysql -u root -S /cloudsql/my-project:us-central1:sql-inst # For -fuse you do not need to specify instance names ahead of time: ./cloud_sql_proxy -dir=/cloudsql -fuse & mysql -u root -S /cloudsql/my-project:us-central1:sql-inst # For programs which do not support using Unix Domain Sockets, specify tcp: ./cloud_sql_proxy -dir=/cloudsql -instances=my-project:us-central1:sql-inst=tcp:3306 & mysql -u root -h 127.0.0.1 # For programs which require a certain Unix Domain Socket name: ./cloud_sql_proxy -dir=/cloudsql -instances=my-project:us-central1:sql-inst=unix:custom_socket_name & mysql -u root -S /cloudsql/custom_socket_name # For programs which require a the Unix Domain Socket at a specific location, set an absolute path (overrides -dir): ./cloud_sql_proxy -dir=/cloudsql -instances=my-project:us-central1:sql-inst=unix:/my/custom/sql-socket & mysql -u root -S /my/custom/sql-socket ## Container Images For convenience, we currently host containerized versions of the proxy in the following GCR repos: * `gcr.io/cloudsql-docker/gce-proxy` * `us.gcr.io/cloudsql-docker/gce-proxy` * `eu.gcr.io/cloudsql-docker/gce-proxy` * `asia.gcr.io/cloudsql-docker/gce-proxy` Images are tagged to the version of the proxy they contain. It's strongly suggested to use the latest version of the proxy, and to update the version often. ## To use from Kubernetes: ### Deploying Cloud SQL Proxy as a sidecar container Follow this [page](https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/tree/master/cloudsql). See also [Connecting from Google Kubernetes Engine](https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine). ## Third Party __WARNING__: _These distributions are not officially supported by Google._ ### Installing via Homebrew You can find a formula for with Homebrew [here](https://github.com/tclass/homebrew-cloud_sql_proxy). ### K8s Cluster Service using Helm Follow these [instructions](https://github.com/kubernetes/charts/tree/master/stable/gcloud-sqlproxy). This chart creates a Deployment and a Service, but we recommend deploying the proxy as a sidecar container in your pods. cloudsql-proxy-1.17.0/cmd/000077500000000000000000000000001365010350200153435ustar00rootroot00000000000000cloudsql-proxy-1.17.0/cmd/cloud_sql_proxy/000077500000000000000000000000001365010350200205715ustar00rootroot00000000000000cloudsql-proxy-1.17.0/cmd/cloud_sql_proxy/build.sh000077500000000000000000000063531365010350200222360ustar00rootroot00000000000000#!/bin/bash # # build.sh wraps `go build` to make compiling the Cloud SQL Proxy for # distribution more streamlined. When doing normal development on the proxy, # `go build .` (or even `go run .`) is sufficient for iterating on the code. # This script simply allows a convenient way to cross compile and build a docker # container. # # With no arguments, this script will build a binary marked with "development", # otherwise the binary version will be annotated with the string provided. # # # Build a binary labeled with 'development' # ./build.sh # # # Build a binary labeled with 'beta' # ./build.sh beta # # Specifying 'release' as the first argument to this script will cross compile # for all supported operating systems and architectures. This requires a version # identifier to be supplied as the second argument: # # # Build a binary for each of the supported platforms labeled with '0.001' # ./build.sh release 0.001 # # Specifying docker as the first argument to this script will build a # container, tagging it with the identifier in the second argument. # # # Build a docker container named 'cloud-sql-proxy:my-tag' # ./build docker my-tag files=$(git status -s) if [[ $? != 0 ]]; then echo >&2 "Error running git status" exit 2 fi # build builds a new binary. Expected variables: # VERSION: string to print out when --version is passed to the final binary # OS: operation system to target (windows, darwin, linux, etc) # ARCH: architecture to target (amd64, 386, etc) # OUT: location to place binary build() { echo " Compile -> $OUT" CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCH go build \ -ldflags "-X 'main.versionString=$VERSION'" -a -installsuffix cgo -o $OUT \ github.com/GoogleCloudPlatform/cloudsql-proxy/cmd/cloud_sql_proxy } # git_version echos out version information related to the git repo and date. git_version() { edits="" if [[ "$files" != "" ]]; then edits=" (local edits)" fi echo "sha $(git rev-parse HEAD)$edits built $(date)" } set -e case $1 in "release") if [[ "$files" != "" ]]; then echo >&2 "Can't build a release version with local edits; files:" echo >&2 "$files" exit 1 fi if [[ "$2" == "" ]]; then echo >&2 "Must provide a version number to use as the second parameter:" echo >&2 " $0 release my-version-string" exit 1 fi VERSION="version $2; $(git_version)" echo "Cross-compiling $VERSION..." for OS in windows darwin linux; do for ARCH in amd64 386; do OUT="cloud_sql_proxy.$OS.$ARCH" build done done ;; "docker") if [[ "$2" == "" ]]; then echo >&2 "Must provide a version number to use as the second parameter" echo >&2 " $0 docker my-version-string" exit 1 fi VERSION="version $2; $(git_version)" OS="linux" ARCH="amd64" OUT=cloud_sql_proxy.docker echo "Compiling $VERSION for docker..." build cat >Dockerfile <`") } return []string{cfg.Configuration.Properties.Core.Project}, nil } // Main executes the main function of the proxy, allowing it to be called from tests. // // Setting timeout to a value greater than 0 causes the process to panic after // that amount of time. This is to sidestep an issue where sending a Signal to // the process (via the SSH library) doesn't seem to have an effect, and // closing the SSH session causes the process to get leaked. This timeout will // at least cause the proxy to exit eventually. func Main(timeout time.Duration) { if timeout > 0 { go func() { time.Sleep(timeout) panic("timeout exceeded") }() } main() } func main() { flag.Parse() if *version { fmt.Println("Cloud SQL Proxy:", versionString) return } if *logDebugStdout { logging.LogDebugToStdout() } if !*verbose { logging.LogVerboseToNowhere() } if *quiet { log.Println("Cloud SQL Proxy logging has been disabled by the -quiet flag. All messages (including errors) will be suppressed.") log.SetFlags(0) log.SetOutput(ioutil.Discard) } // Split the input ipAddressTypes to the slice of string ipAddrTypeOptsInput := strings.Split(*ipAddressTypes, ",") if *fdRlimit != 0 { if err := limits.SetupFDLimits(*fdRlimit); err != nil { logging.Infof("failed to setup file descriptor limits: %v", err) } } if *host != "" && !strings.HasSuffix(*host, "/") { logging.Errorf("Flag host should always end with /") flag.PrintDefaults() return } // TODO: needs a better place for consolidation // if instances is blank and env var INSTANCES is supplied use it if envInstances := os.Getenv("INSTANCES"); *instances == "" && envInstances != "" { *instances = envInstances } instList := stringList(*instances) projList := stringList(*projects) // TODO: it'd be really great to consolidate flag verification in one place. if len(instList) == 0 && *instanceSrc == "" && len(projList) == 0 && !*useFuse { var err error projList, err = gcloudProject() if err == nil { logging.Infof("Using gcloud's active project: %v", projList) } else if gErr, ok := err.(*util.GcloudError); ok && gErr.Status == util.GcloudNotFound { log.Fatalf("gcloud is not in the path and -instances and -projects are empty") } else { log.Fatalf("unable to retrieve the active gcloud project and -instances and -projects are empty: %v", err) } } onGCE := metadata.OnGCE() if err := checkFlags(onGCE); err != nil { log.Fatal(err) } ctx := context.Background() client, err := authenticatedClient(ctx) if err != nil { log.Fatal(err) } ins, err := listInstances(ctx, client, projList) if err != nil { log.Fatal(err) } instList = append(instList, ins...) cfgs, err := CreateInstanceConfigs(*dir, *useFuse, instList, *instanceSrc, client, *skipInvalidInstanceConfigs) if err != nil { log.Fatal(err) } // We only need to store connections in a ConnSet if FUSE is used; otherwise // it is not efficient to do so. var connset *proxy.ConnSet // Initialize a source of new connections to Cloud SQL instances. var connSrc <-chan proxy.Conn if *useFuse { connset = proxy.NewConnSet() c, fuse, err := fuse.NewConnSrc(*dir, *fuseTmp, connset) if err != nil { log.Fatalf("Could not start fuse directory at %q: %v", *dir, err) } connSrc = c defer fuse.Close() } else { updates := make(chan string) if *instanceSrc != "" { go func() { for { err := metadata.Subscribe(*instanceSrc, func(v string, ok bool) error { if ok { updates <- v } return nil }) if err != nil { logging.Errorf("Error on receiving new instances from metadata: %v", err) } time.Sleep(5 * time.Second) } }() } c, err := WatchInstances(*dir, cfgs, updates, client) if err != nil { log.Fatal(err) } connSrc = c } refreshCfgThrottle := *refreshCfgThrottle if refreshCfgThrottle < minimumRefreshCfgThrottle { refreshCfgThrottle = minimumRefreshCfgThrottle } logging.Infof("Ready for new connections") proxyClient := &proxy.Client{ Port: port, MaxConnections: *maxConnections, Certs: certs.NewCertSourceOpts(client, certs.RemoteOpts{ APIBasePath: *host, IgnoreRegion: !*checkRegion, UserAgent: userAgentFromVersionString(), IPAddrTypeOpts: ipAddrTypeOptsInput, }), Conns: connset, RefreshCfgThrottle: refreshCfgThrottle, } signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) go func() { <-signals logging.Infof("Received TERM signal. Waiting up to %s before terminating.", *termTimeout) err := proxyClient.Shutdown(*termTimeout) if err == nil { os.Exit(0) } os.Exit(2) }() proxyClient.Run(connSrc) } cloudsql-proxy-1.17.0/cmd/cloud_sql_proxy/proxy.go000066400000000000000000000305741365010350200223120ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main // This file contains code for supporting local sockets for the Cloud SQL Proxy. import ( "bytes" "errors" "fmt" "net" "net/http" "os" "path/filepath" "runtime" "strings" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/fuse" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/util" sqladmin "google.golang.org/api/sqladmin/v1beta4" ) // WatchInstances handles the lifecycle of local sockets used for proxying // local connections. Values received from the updates channel are // interpretted as a comma-separated list of instances. The set of sockets in // 'dir' is the union of 'instances' and the most recent list from 'updates'. func WatchInstances(dir string, cfgs []instanceConfig, updates <-chan string, cl *http.Client) (<-chan proxy.Conn, error) { ch := make(chan proxy.Conn, 1) // Instances specified statically (e.g. as flags to the binary) will always // be available. They are ignored if also returned by the GCE metadata since // the socket will already be open. staticInstances := make(map[string]net.Listener, len(cfgs)) for _, v := range cfgs { l, err := listenInstance(ch, v) if err != nil { return nil, err } staticInstances[v.Instance] = l } if updates != nil { go watchInstancesLoop(dir, ch, updates, staticInstances, cl) } return ch, nil } func watchInstancesLoop(dir string, dst chan<- proxy.Conn, updates <-chan string, static map[string]net.Listener, cl *http.Client) { dynamicInstances := make(map[string]net.Listener) for instances := range updates { // All instances were legal when we started, so we pass false below to ensure we don't skip them // later if they became unhealthy for some reason; this would be a serious enough problem. list, err := parseInstanceConfigs(dir, strings.Split(instances, ","), cl, false) if err != nil { logging.Errorf("%v", err) // If we do not have a valid list of instances, skip this update continue } stillOpen := make(map[string]net.Listener) for _, cfg := range list { instance := cfg.Instance // If the instance is specified in the static list don't do anything: // it's already open and should stay open forever. if _, ok := static[instance]; ok { continue } if l, ok := dynamicInstances[instance]; ok { delete(dynamicInstances, instance) stillOpen[instance] = l continue } l, err := listenInstance(dst, cfg) if err != nil { logging.Errorf("Couldn't open socket for %q: %v", instance, err) continue } stillOpen[instance] = l } // Any instance in dynamicInstances was not in the most recent metadata // update. Clean up those instances' sockets by closing them; note that // this does not affect any existing connections instance. for instance, listener := range dynamicInstances { logging.Infof("Closing socket for instance %v", instance) listener.Close() } dynamicInstances = stillOpen } for _, v := range static { if err := v.Close(); err != nil { logging.Errorf("Error closing %q: %v", v.Addr(), err) } } for _, v := range dynamicInstances { if err := v.Close(); err != nil { logging.Errorf("Error closing %q: %v", v.Addr(), err) } } } func remove(path string) { if err := os.Remove(path); err != nil && !os.IsNotExist(err) { logging.Infof("Remove(%q) error: %v", path, err) } } // listenInstance starts listening on a new unix socket in dir to connect to the // specified instance. New connections to this socket are sent to dst. func listenInstance(dst chan<- proxy.Conn, cfg instanceConfig) (net.Listener, error) { unix := cfg.Network == "unix" if unix { remove(cfg.Address) } l, err := net.Listen(cfg.Network, cfg.Address) if err != nil { return nil, err } if unix { if err := os.Chmod(cfg.Address, 0777|os.ModeSocket); err != nil { logging.Errorf("couldn't update permissions for socket file %q: %v; other users may not be unable to connect", cfg.Address, err) } } go func() { for { start := time.Now() c, err := l.Accept() if err != nil { logging.Errorf("Error in accept for %q on %v: %v", cfg, cfg.Address, err) if nerr, ok := err.(net.Error); ok && nerr.Temporary() { d := 10*time.Millisecond - time.Since(start) if d > 0 { time.Sleep(d) } continue } l.Close() return } logging.Verbosef("New connection for %q", cfg.Instance) switch clientConn := c.(type) { case *net.TCPConn: clientConn.SetKeepAlive(true) clientConn.SetKeepAlivePeriod(1 * time.Minute) } dst <- proxy.Conn{cfg.Instance, c} } }() logging.Infof("Listening on %s for %s", cfg.Address, cfg.Instance) return l, nil } type instanceConfig struct { Instance string Network, Address string } // loopbackForNet maps a network (e.g. tcp6) to the loopback address for that // network. It is updated during the initialization of validNets to include a // valid loopback address for "tcp". var loopbackForNet = map[string]string{ "tcp4": "127.0.0.1", "tcp6": "::1", } // validNets tracks the networks that are valid for this platform and machine. var validNets = func() map[string]bool { m := map[string]bool{ "unix": runtime.GOOS != "windows", } anyTCP := false for _, n := range []string{"tcp4", "tcp6"} { host, ok := loopbackForNet[n] if !ok { // This is effectively a compile-time error. panic(fmt.Sprintf("no loopback address found for %v", n)) } // Open any port to see if the net is valid. x, err := net.Listen(n, net.JoinHostPort(host, "0")) if err != nil { // Error is too verbose to be useful. continue } x.Close() m[n] = true if !anyTCP { anyTCP = true // Set the loopback value for generic tcp if it hasn't already been // set. (If both tcp4/tcp6 are supported the first one in the list // (tcp4's 127.0.0.1) is used. loopbackForNet["tcp"] = host } } if anyTCP { m["tcp"] = true } return m }() func parseInstanceConfig(dir, instance string, cl *http.Client) (instanceConfig, error) { var ret instanceConfig args := strings.Split(instance, "=") if len(args) > 2 { return instanceConfig{}, fmt.Errorf("invalid instance argument: must be either form - `` or `=`; invalid arg was %q", instance) } // Parse the instance connection name - everything before the "=". ret.Instance = args[0] proj, _, name := util.SplitName(ret.Instance) if proj == "" || name == "" { return instanceConfig{}, fmt.Errorf("invalid instance connection string: must be in the form `project:region:instance-name`; invalid name was %q", args[0]) } if len(args) == 1 { // Default to listening via unix socket in specified directory ret.Network = "unix" ret.Address = filepath.Join(dir, instance) } else { // Parse the instance options if present. opts := strings.SplitN(args[1], ":", 2) if len(opts) != 2 { return instanceConfig{}, fmt.Errorf("invalid instance options: must be in the form `unix:/path/to/socket`, `tcp:port`, `tcp:host:port`; invalid option was %q", strings.Join(opts, ":")) } ret.Network = opts[0] var err error if ret.Network == "unix" { if strings.HasPrefix(opts[1], "/") { ret.Address = opts[1] // Root path. } else { ret.Address = filepath.Join(dir, opts[1]) } } else { ret.Address, err = parseTCPOpts(opts[0], opts[1]) } if err != nil { return instanceConfig{}, err } } // Use the SQL Admin API to verify compatibility with the instance. sql, err := sqladmin.New(cl) if err != nil { return instanceConfig{}, err } if *host != "" { sql.BasePath = *host } inst, err := sql.Instances.Get(proj, name).Do() if err != nil { return instanceConfig{}, err } if inst.BackendType == "FIRST_GEN" { logging.Errorf("WARNING: proxy client does not support first generation Cloud SQL instances.") return instanceConfig{}, fmt.Errorf("%q is a first generation instance", instance) } // Postgres instances use a special suffix on the unix socket. // See https://www.postgresql.org/docs/11/runtime-config-connection.html if ret.Network == "unix" && strings.HasPrefix(strings.ToLower(inst.DatabaseVersion), "postgres") { // Verify the directory exists. if err := os.MkdirAll(ret.Address, 0755); err != nil { return instanceConfig{}, err } ret.Address = filepath.Join(ret.Address, ".s.PGSQL.5432") } if !validNets[ret.Network] { return ret, fmt.Errorf("invalid %q: unsupported network: %v", instance, ret.Network) } return ret, nil } // parseTCPOpts parses the instance options when specifying tcp port options. func parseTCPOpts(ntwk, addrOpt string) (string, error) { if strings.Contains(addrOpt, ":") { return addrOpt, nil // User provided a host and port; use that. } // No "host" part of the address. Be safe and assume that they want a loopback address. addr, ok := loopbackForNet[ntwk] if !ok { return "", fmt.Errorf("invalid %q:%q: unrecognized network %v", ntwk, addrOpt, ntwk) } return net.JoinHostPort(addr, addrOpt), nil } // parseInstanceConfigs calls parseInstanceConfig for each instance in the // provided slice, collecting errors along the way. There may be valid // instanceConfigs returned even if there's an error. func parseInstanceConfigs(dir string, instances []string, cl *http.Client, skipFailedInstanceConfigs bool) ([]instanceConfig, error) { errs := new(bytes.Buffer) var cfg []instanceConfig for _, v := range instances { if v == "" { continue } if c, err := parseInstanceConfig(dir, v, cl); err != nil { if skipFailedInstanceConfigs { logging.Infof("There was a problem when parsing a instance configuration but ignoring due to the configuration. Error: %v", err) } else { fmt.Fprintf(errs, "\n\t%v", err) } } else { cfg = append(cfg, c) } } var err error if errs.Len() > 0 { err = fmt.Errorf("errors parsing config:%s", errs) } return cfg, err } // CreateInstanceConfigs verifies that the parameters passed to it are valid // for the proxy for the platform and system and then returns a slice of valid // instanceConfig. It is possible for the instanceConfig to be empty if no valid // configurations were specified, however `err` will be set. func CreateInstanceConfigs(dir string, useFuse bool, instances []string, instancesSrc string, cl *http.Client, skipFailedInstanceConfigs bool) ([]instanceConfig, error) { if useFuse && !fuse.Supported() { return nil, errors.New("FUSE not supported on this system") } cfgs, err := parseInstanceConfigs(dir, instances, cl, skipFailedInstanceConfigs) if err != nil { return nil, err } if dir == "" { // Reasons to set '-dir': // - Using -fuse // - Using the metadata to get a list of instances // - Having an instance that uses a 'unix' network if useFuse { return nil, errors.New("must set -dir because -fuse was set") } else if instancesSrc != "" { return nil, errors.New("must set -dir because -instances_metadata was set") } else { for _, v := range cfgs { if v.Network == "unix" { return nil, fmt.Errorf("must set -dir: using a unix socket for %v", v.Instance) } } } // Otherwise it's safe to not set -dir } if useFuse { if len(instances) != 0 || instancesSrc != "" { return nil, errors.New("-fuse is not compatible with -projects, -instances, or -instances_metadata") } return nil, nil } // FUSE disabled. if len(instances) == 0 && instancesSrc == "" { // Failure to specifying instance can be caused by following reasons. // 1. not enough information is provided by flags // 2. failed to invoke gcloud var flags string if fuse.Supported() { flags = "-projects, -fuse, -instances or -instances_metadata" } else { flags = "-projects, -instances or -instances_metadata" } errStr := fmt.Sprintf("no instance selected because none of %s is specified", flags) return nil, errors.New(errStr) } return cfgs, nil } cloudsql-proxy-1.17.0/cmd/cloud_sql_proxy/proxy_test.go000066400000000000000000000142711365010350200233450ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "fmt" "io/ioutil" "net" "net/http" "os" "testing" ) type mockTripper struct { } func (m *mockTripper) RoundTrip(r *http.Request) (*http.Response, error) { return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader([]byte("{}")))}, nil } var mockClient = &http.Client{Transport: &mockTripper{}} func TestCreateInstanceConfigs(t *testing.T) { for _, v := range []struct { desc string //inputs dir string useFuse bool instances []string instancesSrc string // We don't need to check the []instancesConfig return value, we already // have a TestParseInstanceConfig. wantErr bool skipFailedInstanceConfig bool }{ { "setting -fuse and -dir", "dir", true, nil, "", false, false, }, { "setting -fuse", "", true, nil, "", true, false, }, { "setting -fuse, -dir, and -instances", "dir", true, []string{"proj:reg:x"}, "", true, false, }, { "setting -fuse, -dir, and -instances_metadata", "dir", true, nil, "md", true, false, }, { "setting -dir and -instances (unix socket)", "dir", false, []string{"proj:reg:x"}, "", false, false, }, { // tests for the case where invalid configs can still exist, when skipped "setting -dir and -instances (unix socket) w/ something invalid", "dir", false, []string{"proj:reg:x", "INVALID_PROJECT_STRING"}, "", false, true, }, { "Seting -instance (unix socket)", "", false, []string{"proj:reg:x"}, "", true, false, }, { "setting -instance (tcp socket)", "", false, []string{"proj:reg:x=tcp:1234"}, "", false, false, }, { "setting -instance (tcp socket) and -instances_metadata", "", false, []string{"proj:reg:x=tcp:1234"}, "md", true, false, }, { "setting -dir, -instance (tcp socket), and -instances_metadata", "dir", false, []string{"proj:reg:x=tcp:1234"}, "md", false, false, }, { "setting -dir, -instance (unix socket), and -instances_metadata", "dir", false, []string{"proj:reg:x"}, "md", false, false, }, { "setting -dir and -instances_metadata", "dir", false, nil, "md", false, false, }, { "setting -instances_metadata", "", false, nil, "md", true, false, }, } { _, err := CreateInstanceConfigs(v.dir, v.useFuse, v.instances, v.instancesSrc, mockClient, v.skipFailedInstanceConfig) if v.wantErr { if err == nil { t.Errorf("CreateInstanceConfigs passed when %s, wanted error", v.desc) } continue } if err != nil { t.Errorf("CreateInstanceConfigs gave error when %s: %v", v.desc, err) } } } func TestParseInstanceConfig(t *testing.T) { // sentinel values var ( anyLoopbackAddress = "" wantErr = instanceConfig{"", "", ""} ) tcs := []struct { // inputs dir, instance string wantCfg instanceConfig }{ { "/x", "domain.com:my-proj:my-reg:my-instance", instanceConfig{"domain.com:my-proj:my-reg:my-instance", "unix", "/x/domain.com:my-proj:my-reg:my-instance"}, }, { "/x", "my-proj:my-reg:my-instance", instanceConfig{"my-proj:my-reg:my-instance", "unix", "/x/my-proj:my-reg:my-instance"}, }, { "/x", "my-proj:my-reg:my-instance=unix:socket_name", instanceConfig{"my-proj:my-reg:my-instance", "unix", "/x/socket_name"}, }, { "/x", "my-proj:my-reg:my-instance=unix:/my/custom/sql-socket", instanceConfig{"my-proj:my-reg:my-instance", "unix", "/my/custom/sql-socket"}, }, { "/x", "my-proj:my-reg:my-instance=tcp:1234", instanceConfig{"my-proj:my-reg:my-instance", "tcp", anyLoopbackAddress}, }, { "/x", "my-proj:my-reg:my-instance=tcp4:1234", instanceConfig{"my-proj:my-reg:my-instance", "tcp4", "127.0.0.1:1234"}, }, { "/x", "my-proj:my-reg:my-instance=tcp6:1234", instanceConfig{"my-proj:my-reg:my-instance", "tcp6", "[::1]:1234"}, }, { "/x", "my-proj:my-reg:my-instance=tcp:my-host:1111", instanceConfig{"my-proj:my-reg:my-instance", "tcp", "my-host:1111"}, }, { "/x", "my-proj:my-reg:my-instance=", wantErr, }, { "/x", "my-proj:my-reg:my-instance=cool network", wantErr, }, { "/x", "my-proj:my-reg:my-instance=cool network:1234", wantErr, }, { "/x", "my-proj:my-reg:my-instance=oh:so:many:colons", wantErr, }, } for _, tc := range tcs { t.Run(fmt.Sprintf("parseInstanceConfig(%q, %q)", tc.dir, tc.instance), func(t *testing.T) { if os.Getenv("EXPECT_IPV4_AND_IPV6") != "true" { // Skip ipv4 and ipv6 if they are not supported by the machine. // (assumption is that validNets isn't buggy) if tc.wantCfg.Network == "tcp4" || tc.wantCfg.Network == "tcp6" { if !validNets[tc.wantCfg.Network] { t.Skipf("%q net not supported, skipping", tc.wantCfg.Network) } } } got, err := parseInstanceConfig(tc.dir, tc.instance, mockClient) if tc.wantCfg == wantErr { if err != nil { return // pass. an error was expected and returned. } t.Fatalf("parseInstanceConfig(%s, %s) = %+v, wanted error", tc.dir, tc.instance, got) } if err != nil { t.Fatalf("parseInstanceConfig(%s, %s) had unexpected error: %v", tc.dir, tc.instance, err) } if tc.wantCfg.Address == anyLoopbackAddress { host, _, err := net.SplitHostPort(got.Address) if err != nil { t.Fatalf("net.SplitHostPort(%v): %v", got.Address, err) } ip := net.ParseIP(host) if !ip.IsLoopback() { t.Fatalf("want loopback, got addr: %v", got.Address) } // use a placeholder address, so the rest of the config can be compared got.Address = "" tc.wantCfg.Address = got.Address } if got != tc.wantCfg { t.Errorf("parseInstanceConfig(%s, %s) = %+v, want %+v", tc.dir, tc.instance, got, tc.wantCfg) } }) } } cloudsql-proxy-1.17.0/go.mod000066400000000000000000000014571365010350200157150ustar00rootroot00000000000000module github.com/GoogleCloudPlatform/cloudsql-proxy go 1.13 require ( bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 cloud.google.com/go v0.56.0 github.com/go-sql-driver/mysql v1.5.0 github.com/golang/protobuf v1.4.0 // indirect github.com/lib/pq v1.3.0 golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect google.golang.org/api v0.21.0 google.golang.org/genproto v0.0.0-20200420144010-e5e8543f8aeb // indirect google.golang.org/grpc v1.28.1 // indirect ) replace bazil.org/fuse => bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 // pin to latest version that supports macOS. see https://github.com/bazil/fuse/issues/224 cloudsql-proxy-1.17.0/go.sum000066400000000000000000000771011365010350200157410ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 h1:FNCRpXiquG1aoyqcIWVFmpTSKVcx2bQD38uZZeGtdlw= bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0 h1:0E3eE8MX426vUOs7aHfI7aN1BrIzzzf4ccKCSfSjGmc= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo= golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY= google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200420144010-e5e8543f8aeb h1:nAFaltAMbNVA0rixtwvdnqgSVLX3HFUUvMkEklmzbYM= google.golang.org/genproto v0.0.0-20200420144010-e5e8543f8aeb/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= cloudsql-proxy-1.17.0/logging/000077500000000000000000000000001365010350200162265ustar00rootroot00000000000000cloudsql-proxy-1.17.0/logging/logging.go000066400000000000000000000030111365010350200201760ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package logging contains helpers to support log messages. If you are using // the Cloud SQL Proxy as a Go library, you can override these variables to // control where log messages end up. package logging import ( "log" "os" ) // Verbosef is called to write verbose logs, such as when a new connection is // established correctly. var Verbosef = log.Printf // Infof is called to write informational logs, such as when startup has var Infof = log.Printf // Errorf is called to write an error log, such as when a new connection fails. var Errorf = log.Printf // LogDebugToStdout updates Verbosef and Info logging to use stdout instead of stderr. func LogDebugToStdout() { logger := log.New(os.Stdout, "", log.LstdFlags) Verbosef = logger.Printf Infof = logger.Printf } // LogVerboseToNowhere updates Verbosef so verbose log messages are discarded func LogVerboseToNowhere() { Verbosef = func(string, ...interface{}) {} } cloudsql-proxy-1.17.0/proxy/000077500000000000000000000000001365010350200157615ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/README.md000066400000000000000000000022131365010350200172360ustar00rootroot00000000000000# Cloud SQL proxy dialer for Go You can also use the Cloud SQL proxy directly from a Go program. These packages are primarily used as implementation for the Cloud SQL proxy command, and may be changed in backwards incompatible ways in the future. ## To use inside a Go program: If your program is written in [Go](https://golang.org) you can use the Cloud SQL Proxy as a library, avoiding the need to start the Proxy as a companion process. ### MySQL If you're using the the MySQL [go-sql-driver](https://github.com/go-sql-driver/mysql) you can use helper functions found in the [`proxy/dialers/mysql` package](https://godoc.org/github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql). See [example usage](https://github.com/GoogleCloudPlatform/cloudsql-proxy/blob/master/tests/dialers_test.go). ### Postgres If you're using the the Postgres [lib/pq](https://github.com/lib/pq), you can use the `cloudsqlpostgres` driver from [here](https://github.com/GoogleCloudPlatform/cloudsql-proxy/tree/master/proxy/dialers/postgres). See [example usage](https://github.com/GoogleCloudPlatform/cloudsql-proxy/blob/master/proxy/dialers/postgres/hook_test.go). cloudsql-proxy-1.17.0/proxy/certs/000077500000000000000000000000001365010350200171015ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/certs/certs.go000066400000000000000000000221301365010350200205460ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package certs implements a CertSource which speaks to the public Cloud SQL API endpoint. package certs import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" "math" mrand "math/rand" "net/http" "strings" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/util" "google.golang.org/api/googleapi" sqladmin "google.golang.org/api/sqladmin/v1beta4" ) const defaultUserAgent = "custom cloud_sql_proxy version >= 1.10" // NewCertSource returns a CertSource which can be used to authenticate using // the provided client, which must not be nil. // // This function is deprecated; use NewCertSourceOpts instead. func NewCertSource(host string, c *http.Client, checkRegion bool) *RemoteCertSource { return NewCertSourceOpts(c, RemoteOpts{ APIBasePath: host, IgnoreRegion: !checkRegion, UserAgent: defaultUserAgent, }) } // RemoteOpts are a collection of options for NewCertSourceOpts. All fields are // optional. type RemoteOpts struct { // APIBasePath specifies the base path for the sqladmin API. If left blank, // the default from the autogenerated sqladmin library is used (which is // sufficient for nearly all users) APIBasePath string // IgnoreRegion specifies whether a missing or mismatched region in the // instance name should be ignored. In a future version this value will be // forced to 'false' by the RemoteCertSource. IgnoreRegion bool // A string for the RemoteCertSource to identify itself when contacting the // sqladmin API. UserAgent string // IP address type options IPAddrTypeOpts []string } // NewCertSourceOpts returns a CertSource configured with the provided Opts. // The provided http.Client must not be nil. // // Use this function instead of NewCertSource; it has a more forward-compatible // signature. func NewCertSourceOpts(c *http.Client, opts RemoteOpts) *RemoteCertSource { pkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(err) // very unexpected. } serv, err := sqladmin.New(c) if err != nil { panic(err) // Only will happen if the provided client is nil. } if opts.APIBasePath != "" { serv.BasePath = opts.APIBasePath } ua := opts.UserAgent if ua == "" { ua = defaultUserAgent } serv.UserAgent = ua // Set default value to be "PUBLIC,PRIVATE" if not specified if len(opts.IPAddrTypeOpts) == 0 { opts.IPAddrTypeOpts = []string{"PUBLIC", "PRIVATE"} } // Add "PUBLIC" as an alias for "PRIMARY" for index, ipAddressType := range opts.IPAddrTypeOpts { if strings.ToUpper(ipAddressType) == "PUBLIC" { opts.IPAddrTypeOpts[index] = "PRIMARY" } } return &RemoteCertSource{pkey, serv, !opts.IgnoreRegion, opts.IPAddrTypeOpts} } // RemoteCertSource implements a CertSource, using Cloud SQL APIs to // return Local certificates for identifying oneself as a specific user // to the remote instance and Remote certificates for confirming the // remote database's identity. type RemoteCertSource struct { // key is the private key used for certificates returned by Local. key *rsa.PrivateKey // serv is used to make authenticated API calls to Cloud SQL. serv *sqladmin.Service // If set, providing an incorrect region in their connection string will be // treated as an error. This is to provide the same functionality that will // occur when API calls require the region. checkRegion bool // a list of ip address types that users select IPAddrTypes []string } // Constants for backoffAPIRetry. These cause the retry logic to scale the // backoff delay from 200ms to around 3.5s. const ( baseBackoff = float64(200 * time.Millisecond) backoffMult = 1.618 backoffRetries = 5 ) func backoffAPIRetry(desc, instance string, do func() error) error { var err error for i := 0; i < backoffRetries; i++ { err = do() gErr, ok := err.(*googleapi.Error) switch { case !ok: // 'ok' will also be false if err is nil. return err case gErr.Code == 403 && len(gErr.Errors) > 0 && gErr.Errors[0].Reason == "insufficientPermissions": // The case where the admin API has not yet been enabled. return fmt.Errorf("ensure that the Cloud SQL API is enabled for your project (https://console.cloud.google.com/flows/enableapi?apiid=sqladmin). Error during %s %s: %v", desc, instance, err) case gErr.Code == 404 || gErr.Code == 403: return fmt.Errorf("ensure that the account has access to %q (and make sure there's no typo in that name). Error during %s %s: %v", instance, desc, instance, err) case gErr.Code < 500: // Only Server-level HTTP errors are immediately retryable. return err } // sleep = baseBackoff * backoffMult^(retries + randomFactor) exp := float64(i+1) + mrand.Float64() sleep := time.Duration(baseBackoff * math.Pow(backoffMult, exp)) logging.Errorf("Error in %s %s: %v; retrying in %v", desc, instance, err, sleep) time.Sleep(sleep) } return err } // Local returns a certificate that may be used to establish a TLS // connection to the specified instance. func (s *RemoteCertSource) Local(instance string) (ret tls.Certificate, err error) { pkix, err := x509.MarshalPKIXPublicKey(&s.key.PublicKey) if err != nil { return ret, err } p, _, n := util.SplitName(instance) req := s.serv.SslCerts.CreateEphemeral(p, n, &sqladmin.SslCertsCreateEphemeralRequest{ PublicKey: string(pem.EncodeToMemory(&pem.Block{Bytes: pkix, Type: "RSA PUBLIC KEY"})), }, ) var data *sqladmin.SslCert err = backoffAPIRetry("createEphemeral for", instance, func() error { data, err = req.Do() return err }) if err != nil { return ret, err } c, err := parseCert(data.Cert) if err != nil { return ret, fmt.Errorf("couldn't parse ephemeral certificate for instance %q: %v", instance, err) } return tls.Certificate{ Certificate: [][]byte{c.Raw}, PrivateKey: s.key, Leaf: c, }, nil } func parseCert(pemCert string) (*x509.Certificate, error) { bl, _ := pem.Decode([]byte(pemCert)) if bl == nil { return nil, errors.New("invalid PEM: " + pemCert) } return x509.ParseCertificate(bl.Bytes) } // Find the first matching IP address by user input IP address types func (s *RemoteCertSource) findIPAddr(data *sqladmin.DatabaseInstance, instance string) (ipAddrInUse string, err error) { for _, eachIPAddrTypeByUser := range s.IPAddrTypes { for _, eachIPAddrTypeOfInstance := range data.IpAddresses { if strings.ToUpper(eachIPAddrTypeOfInstance.Type) == strings.ToUpper(eachIPAddrTypeByUser) { ipAddrInUse = eachIPAddrTypeOfInstance.IpAddress return ipAddrInUse, nil } } } ipAddrTypesOfInstance := "" for _, eachIPAddrTypeOfInstance := range data.IpAddresses { ipAddrTypesOfInstance += fmt.Sprintf("(TYPE=%v, IP_ADDR=%v)", eachIPAddrTypeOfInstance.Type, eachIPAddrTypeOfInstance.IpAddress) } ipAddrTypeOfUser := fmt.Sprintf("%v", s.IPAddrTypes) return "", fmt.Errorf("User input IP address type %v does not match the instance %v, the instance's IP addresses are %v ", ipAddrTypeOfUser, instance, ipAddrTypesOfInstance) } // Remote returns the specified instance's CA certificate, address, and name. func (s *RemoteCertSource) Remote(instance string) (cert *x509.Certificate, addr, name string, err error) { p, region, n := util.SplitName(instance) req := s.serv.Instances.Get(p, n) var data *sqladmin.DatabaseInstance err = backoffAPIRetry("get instance", instance, func() error { data, err = req.Do() return err }) if err != nil { return nil, "", "", err } // TODO(chowski): remove this when us-central is removed. if data.Region == "us-central" { data.Region = "us-central1" } if data.Region != region { if region == "" { err = fmt.Errorf("instance %v doesn't provide region", instance) } else { err = fmt.Errorf(`for connection string "%s": got region %q, want %q`, instance, region, data.Region) } if s.checkRegion { return nil, "", "", err } logging.Errorf("%v", err) logging.Errorf("WARNING: specifying the correct region in an instance string will become required in a future version!") } if len(data.IpAddresses) == 0 { return nil, "", "", fmt.Errorf("no IP address found for %v", instance) } if data.BackendType == "FIRST_GEN" { logging.Errorf("WARNING: proxy client does not support first generation Cloud SQL instances.") return nil, "", "", fmt.Errorf("%q is a first generation instance", instance) } // Find the first matching IP address by user input IP address types ipAddrInUse := "" ipAddrInUse, err = s.findIPAddr(data, instance) if err != nil { return nil, "", "", err } c, err := parseCert(data.ServerCaCert.Cert) return c, ipAddrInUse, p + ":" + n, err } cloudsql-proxy-1.17.0/proxy/dialers/000077500000000000000000000000001365010350200174045ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/dialers/mysql/000077500000000000000000000000001365010350200205515ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/dialers/mysql/hook.go000066400000000000000000000060551365010350200220460ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package mysql adds a 'cloudsql' network to use when you want to access a // Cloud SQL Database via the mysql driver found at // github.com/go-sql-driver/mysql. It also exposes helper functions for // dialing. package mysql import ( "database/sql" "errors" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "github.com/go-sql-driver/mysql" ) func init() { mysql.RegisterDial("cloudsql", proxy.Dial) } // Dial logs into the specified Cloud SQL Instance using the given user and no // password. To set more options, consider calling DialCfg instead. // // The provided instance should be in the form project-name:region:instance-name. // // The returned *sql.DB may be valid even if there's also an error returned // (e.g. if there was a transient connection error). func Dial(instance, user string) (*sql.DB, error) { cfg := mysql.NewConfig() cfg.User = user cfg.Addr = instance return DialCfg(cfg) } // DialPassword is similar to Dial, but allows you to specify a password. // // Note that using a password with the proxy is not necessary as long as the // user's hostname in the mysql.user table is 'cloudsqlproxy~'. For more // information, see: // https://cloud.google.com/sql/docs/sql-proxy#user func DialPassword(instance, user, password string) (*sql.DB, error) { cfg := mysql.NewConfig() cfg.User = user cfg.Passwd = password cfg.Addr = instance return DialCfg(cfg) } // Cfg returns the effective *mysql.Config to represent connectivity to the // provided instance via the given user and password. The config can be // modified and passed to DialCfg to connect. If you don't modify the returned // config before dialing, consider using Dial or DialPassword. func Cfg(instance, user, password string) *mysql.Config { cfg := mysql.NewConfig() cfg.User = user cfg.Passwd = password cfg.Addr = instance cfg.Net = "cloudsql" return cfg } // DialCfg opens up a SQL connection to a Cloud SQL Instance specified by the // provided configuration. It is otherwise the same as Dial. // // The cfg.Addr should be the instance's connection string, in the format of: // project-name:region:instance-name. func DialCfg(cfg *mysql.Config) (*sql.DB, error) { if cfg.TLSConfig != "" { return nil, errors.New("do not specify TLS when using the Proxy") } // Copy the config so that we can modify it without feeling bad. c := *cfg c.Net = "cloudsql" dsn := c.FormatDSN() db, err := sql.Open("mysql", dsn) if err == nil { err = db.Ping() } return db, err } cloudsql-proxy-1.17.0/proxy/dialers/mysql/hook_test.go000066400000000000000000000026031365010350200231000ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mysql_test import ( "fmt" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql" ) // ExampleCfg shows how to use Cloud SQL Proxy dialer if you must update some // settings normally passed in the DSN such as the DBName or timeouts. func ExampleCfg() { cfg := mysql.Cfg("project:region:instance-name", "user", "") cfg.DBName = "DB_1" cfg.ParseTime = true const timeout = 10 * time.Second cfg.Timeout = timeout cfg.ReadTimeout = timeout cfg.WriteTimeout = timeout db, err := mysql.DialCfg(cfg) if err != nil { panic("couldn't dial: " + err.Error()) } // Close db after this method exits since we don't need it for the // connection pooling. defer db.Close() var now time.Time fmt.Println(db.QueryRow("SELECT NOW()").Scan(&now)) fmt.Println(now) } cloudsql-proxy-1.17.0/proxy/dialers/postgres/000077500000000000000000000000001365010350200212525ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/dialers/postgres/hook.go000066400000000000000000000037071365010350200225500ustar00rootroot00000000000000// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package postgres adds a 'cloudsqlpostgres' driver to use when you want // to access a Cloud SQL Database via the go database/sql library. // It is a wrapper over the driver found at github.com/lib/pq. // To use this driver, you can look at an example in // postgres_test package in the hook_test.go file package postgres import ( "database/sql" "database/sql/driver" "fmt" "net" "regexp" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "github.com/lib/pq" ) func init() { sql.Register("cloudsqlpostgres", &Driver{}) } type Driver struct{} type dialer struct{} // instanceRegexp is used to parse the addr returned by lib/pq. // lib/pq returns the format '[project:region:instance]:port' var instanceRegexp = regexp.MustCompile(`^\[(.+)\]:[0-9]+$`) func (d dialer) Dial(ntw, addr string) (net.Conn, error) { matches := instanceRegexp.FindStringSubmatch(addr) if len(matches) != 2 { return nil, fmt.Errorf("failed to parse addr: %q. It should conform to the regular expression %q", addr, instanceRegexp) } instance := matches[1] return proxy.Dial(instance) } func (d dialer) DialTimeout(ntw, addr string, timeout time.Duration) (net.Conn, error) { return nil, fmt.Errorf("timeout is not currently supported for cloudsqlpostgres dialer") } func (d *Driver) Open(name string) (driver.Conn, error) { return pq.DialOpen(dialer{}, name) } cloudsql-proxy-1.17.0/proxy/dialers/postgres/hook_test.go000066400000000000000000000025051365010350200236020ustar00rootroot00000000000000// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package postgres_test contains an example on how to use cloudsqlpostgres dialer package postgres_test import ( "database/sql" "fmt" "log" "time" _ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/postgres" ) // Example shows how to use cloudsqlpostgres dialer func Example() { // Note that sslmode=disable is required it does not mean that the connection // is unencrypted. All connections via the proxy are completely encrypted. db, err := sql.Open("cloudsqlpostgres", "host=project:region:instance user=postgres dbname=postgres password=password sslmode=disable") if err != nil { log.Fatal(err) } defer db.Close() var now time.Time fmt.Println(db.QueryRow("SELECT NOW()").Scan(&now)) fmt.Println(now) } cloudsql-proxy-1.17.0/proxy/fuse/000077500000000000000000000000001365010350200167235ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/fuse/fuse.go000066400000000000000000000236421365010350200202230ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows,!openbsd // Package fuse provides a connection source wherein the user does not need to // specify which instance they are connecting to before they start the // executable. Instead, simply attempting to access a file in the provided // directory will transparently create a proxied connection to an instance // which has that name. // // Specifically, given that NewConnSrc was called with the mounting directory // as /cloudsql: // // 1) Execute `mysql -S /cloudsql/speckle:instance` // 2) The 'mysql' executable looks up the file "speckle:instance" inside "/cloudsql" // 3) This lookup is intercepted by the code in this package. A local unix socket // located in a temporary directory is opened for listening and the lookup for // "speckle:instance" returns to mysql saying that it is a symbolic link // pointing to this new local socket. // 4) mysql dials the local unix socket, creating a new connection to the // specified instance. package fuse import ( "bytes" "errors" "fmt" "io" "net" "os" "path/filepath" "sync" "time" "bazil.org/fuse" "bazil.org/fuse/fs" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "golang.org/x/net/context" ) // Supported returns true if the current system supports FUSE. // TODO: for OSX, check to see if OSX FUSE is installed. func Supported() bool { return true } // NewConnSrc returns a source of new connections based on Lookups in the // provided mount directory. If there isn't a directory located at tmpdir one // is created. The second return parameter can be used to shutdown and release // any resources. As a result of this shutdown, or during any other fatal // error, the returned chan will be closed. // // The connset parameter is optional. func NewConnSrc(mountdir, tmpdir string, connset *proxy.ConnSet) (<-chan proxy.Conn, io.Closer, error) { if err := os.MkdirAll(tmpdir, 0777); err != nil { return nil, nil, err } if err := fuse.Unmount(mountdir); err != nil { // The error is too verbose to be useful to print out } logging.Verbosef("Mounting %v...", mountdir) c, err := fuse.Mount(mountdir, fuse.AllowOther()) if err != nil { return nil, nil, fmt.Errorf("cannot mount %q: %v", mountdir, err) } logging.Infof("Mounted %v", mountdir) if connset == nil { // Make a dummy one. connset = proxy.NewConnSet() } conns := make(chan proxy.Conn, 1) root := &fsRoot{ tmpDir: tmpdir, linkDir: mountdir, dst: conns, links: make(map[string]symlink), closers: []io.Closer{c}, connset: connset, } server := fs.New(c, &fs.Config{ Debug: func(msg interface{}) { if false { logging.Verbosef("%s", msg) } }, }) go func() { if err := server.Serve(root); err != nil { logging.Errorf("serve %q exited due to error: %v", mountdir, err) } // The server exited but we don't know whether this is because of a // graceful reason (via root.Close) or via an external force unmounting. // Closing the root will ensure the 'dst' chan is closed correctly to // signify that no new connections are possible. if err := root.Close(); err != nil { logging.Errorf("root.Close() error: %v", err) } logging.Infof("FUSE exited") }() return conns, root, nil } // symlink implements a symbolic link, returning the underlying string when // Readlink is called. type symlink string var _ interface { fs.Node fs.NodeReadlinker } = symlink("") func (s symlink) Readlink(context.Context, *fuse.ReadlinkRequest) (string, error) { return string(s), nil } // Attr helps implement fs.Node. func (symlink) Attr(ctx context.Context, a *fuse.Attr) error { *a = fuse.Attr{ Mode: 0777 | os.ModeSymlink, } return nil } type fsRoot struct { tmpDir, linkDir string connset *proxy.ConnSet // sockLock protects fields in this struct related to sockets; specifically // 'links' and 'closers'. sockLock sync.Mutex links map[string]symlink // closers holds a slice of things to close when fsRoot.Close is called. closers []io.Closer sync.RWMutex dst chan<- proxy.Conn } // Ensure that fsRoot implements the following interfaces. var _ interface { fs.FS fs.Node fs.NodeRequestLookuper fs.HandleReadDirAller } = &fsRoot{} func (r *fsRoot) newConn(instance string, c net.Conn) { r.RLock() // dst will be nil if Close has been called already. if ch := r.dst; ch != nil { ch <- proxy.Conn{instance, c} } else { logging.Errorf("Ignored new conn request to %q: system has been closed", instance) } r.RUnlock() } func (r *fsRoot) Forget() { logging.Verbosef("Forget called on %q", r.linkDir) } func (r *fsRoot) Destroy() { logging.Verbosef("Destroy called on %q", r.linkDir) } func (r *fsRoot) Close() error { r.Lock() if r.dst != nil { // Since newConn only sends on dst while holding a reader lock, holding the // writer lock is sufficient to ensure there are no pending sends on the // channel when it is closed. close(r.dst) // Setting it to nil prevents further sends. r.dst = nil } r.Unlock() logging.Infof("unmount %q", r.linkDir) if err := fuse.Unmount(r.linkDir); err != nil { return err } var errs bytes.Buffer r.sockLock.Lock() for _, c := range r.closers { if err := c.Close(); err != nil { fmt.Fprintln(&errs, err) } } r.sockLock.Unlock() if errs.Len() == 0 { return nil } logging.Errorf("Close %q: %v", r.linkDir, errs.String()) return errors.New(errs.String()) } // Root returns the fsRoot itself as the root directory. func (r *fsRoot) Root() (fs.Node, error) { return r, nil } // Attr helps implement fs.Node func (r *fsRoot) Attr(ctx context.Context, a *fuse.Attr) error { *a = fuse.Attr{ Mode: 0555 | os.ModeDir, } return nil } // Lookup helps implement fs.NodeRequestLookuper. If the requested file isn't // the README, it returns a node which is a symbolic link to a socket which // provides connectivity to a remote instance. The instance which is connected // to is determined by req.Name. func (r *fsRoot) Lookup(_ context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) { if req.Name == "README" { return readme{}, nil } instance := req.Name r.sockLock.Lock() defer r.sockLock.Unlock() if ret, ok := r.links[instance]; ok { return ret, nil } path := filepath.Join(r.tmpDir, instance) os.Remove(path) // Best effort; the following will fail if this does. sock, err := net.Listen("unix", path) if err != nil { logging.Errorf("couldn't listen at %q: %v", path, err) return nil, fuse.EEXIST } if err := os.Chmod(path, 0777|os.ModeSocket); err != nil { logging.Errorf("couldn't update permissions for socket file %q: %v; other users may be unable to connect", path, err) } go r.listenerLifecycle(sock, instance, path) ret := symlink(path) r.links[instance] = ret // TODO(chowski): memory leak when listeners exit on their own via removeListener. r.closers = append(r.closers, sock) return ret, nil } // removeListener marks that a Listener for an instance has exited and is no // longer serving new connections. func (r *fsRoot) removeListener(instance, path string) { r.sockLock.Lock() v, ok := r.links[instance] if ok && string(v) == path { delete(r.links, instance) } else { logging.Errorf("Removing a listener for %q at %q which was already replaced", instance, path) } r.sockLock.Unlock() } // listenerLifecycle calls l.Accept in a loop, and for each new connection // r.newConn is called. After the Listener returns an error it is removed. func (r *fsRoot) listenerLifecycle(l net.Listener, instance, path string) { for { start := time.Now() c, err := l.Accept() if err != nil { logging.Errorf("error in Accept for %q: %v", instance, err) if nerr, ok := err.(net.Error); ok && nerr.Temporary() { d := 10*time.Millisecond - time.Since(start) if d > 0 { time.Sleep(d) } continue } break } r.newConn(instance, c) } r.removeListener(instance, path) l.Close() if err := os.Remove(path); err != nil { logging.Errorf("couldn't remove %q: %v", path, err) } } // ReadDirAll returns a list of files contained in the root directory. // It contains a README file which explains how to use the directory. // In addition, there will be a file for each instance to which the // proxy is actively connected. func (r *fsRoot) ReadDirAll(context.Context) ([]fuse.Dirent, error) { ret := []fuse.Dirent{ {Name: "README", Type: fuse.DT_File}, } for _, v := range r.connset.IDs() { ret = append(ret, fuse.Dirent{Name: v, Type: fuse.DT_Socket}) } return ret, nil } // readme implements the REAME file found in the mounted folder. It is a // static read-only text file. type readme struct{} var _ interface { fs.Node fs.HandleReadAller } = readme{} const readmeText = ` When programs attempt to open files in this directory, a remote connection to the Cloud SQL instance of the same name will be established. That is, running : mysql -u root -S "/path/to/this/directory/project:instance-2" will open a new connection to project:instance-2, given you have the correct permissions. Listing the contents of this directory will show all instances with active connections. ` // Attr helps implement fs.Node. func (readme) Attr(ctx context.Context, a *fuse.Attr) error { *a = fuse.Attr{ Mode: 0444, Size: uint64(len(readmeText)), } return nil } // ReadAll helps implement fs.HandleReadAller. func (readme) ReadAll(context.Context) ([]byte, error) { return []byte(readmeText), nil } cloudsql-proxy-1.17.0/proxy/fuse/fuse_openbsd.go000066400000000000000000000017721365010350200217350ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package fuse is a package stub for openbsd, which isn't supported by our // fuse library. package fuse import ( "errors" "io" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" ) func Supported() bool { return false } func NewConnSrc(mountdir, tmpdir string, connset *proxy.ConnSet) (<-chan proxy.Conn, io.Closer, error) { return nil, nil, errors.New("fuse not supported on openbsd") } cloudsql-proxy-1.17.0/proxy/fuse/fuse_test.go000066400000000000000000000112031365010350200212500ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows package fuse import ( "bytes" "io" "io/ioutil" "log" "net" "os" "path/filepath" "sync" "syscall" "testing" "bazil.org/fuse" ) var ( dir = filepath.Join(os.TempDir(), "cloudsql") tmpdir = filepath.Join(os.TempDir(), "cloudsql-tmp") ) func TestFuseClose(t *testing.T) { src, fuse, err := NewConnSrc(dir, tmpdir, nil) if err != nil { t.Fatal(err) } if err := fuse.Close(); err != nil { t.Fatal(err) } if got, ok := <-src; ok { t.Fatalf("got new connection %#v, expected closed source", got) } } // TestBadDir verifies that the fuse module does not create directories, only simple files. func TestBadDir(t *testing.T) { _, fuse, err := NewConnSrc(dir, tmpdir, nil) if err != nil { t.Fatal(err) } defer fuse.Close() _, err = os.Stat(filepath.Join(dir, "dir1", "dir2")) if err == nil { t.Fatal("able to find a directory inside the mount point, expected only regular files") } if err := err.(*os.PathError); err.Err != syscall.ENOTDIR { t.Fatalf("got %#v, want ENOTDIR (%v)", err.Err, syscall.ENOTDIR) } } func TestReadme(t *testing.T) { _, fuse, err := NewConnSrc(dir, tmpdir, nil) if err != nil { t.Fatal(err) } defer fuse.Close() data, err := ioutil.ReadFile(filepath.Join(dir, "README")) if err != nil { t.Fatal(err) } // We just care that the file exists. Print out the contents for // informational purposes. t.Log(string(data)) } func TestSingleInstance(t *testing.T) { src, fuse, err := NewConnSrc(dir, tmpdir, nil) if err != nil { t.Fatal(err) } defer fuse.Close() const want = "test:instance" path := filepath.Join(dir, want) fi, err := os.Stat(path) if err != nil { t.Fatal(err) } if fi.Mode()&os.ModeType != os.ModeSocket { t.Fatalf("%q had mode %v (%X), expected a socket file", path, fi.Mode(), uint32(fi.Mode())) } c, err := net.Dial("unix", path) if err != nil { t.Fatal(err) } defer c.Close() got, ok := <-src if !ok { t.Fatal("connection source was closed, expected a connection") } else if got.Instance != want { t.Fatalf("got %q, want %q", got.Instance, want) } else if got.Conn == nil { t.Fatal("got nil connection, wanted a connection") } const sent = "test string" go func() { if _, err := c.Write([]byte(sent)); err != nil { t.Error(err) } if err := c.Close(); err != nil { t.Error(err) } }() gotData := new(bytes.Buffer) if _, err := io.Copy(gotData, got.Conn); err != nil { t.Fatal(err) } else if gotData.String() != sent { t.Fatalf("got %q, want %v", gotData.String(), sent) } } func BenchmarkNewConnection(b *testing.B) { src, fuse, err := NewConnSrc(dir, tmpdir, nil) if err != nil { b.Fatal(err) } const want = "X" incomingCount := 0 var incoming sync.Mutex // Is unlocked when the following goroutine exits. go func() { incoming.Lock() defer incoming.Unlock() for c := range src { c.Conn.Write([]byte(want)) c.Conn.Close() incomingCount++ } }() const instance = "test:instance" path := filepath.Join(dir, instance) b.ResetTimer() for i := 0; i < b.N; i++ { c, err := net.Dial("unix", path) if err != nil { b.Errorf("couldn't dial: %v", err) } data, err := ioutil.ReadAll(c) if err != nil { b.Errorf("got read error: %v", err) } else if got := string(data); got != want { b.Errorf("read %q, want %q", string(data), want) } } if err := fuse.Close(); err != nil { b.Fatal(err) } // Wait for the 'incoming' goroutine to finish. incoming.Lock() if incomingCount != b.N { b.Fatalf("got %d connections, want %d", incomingCount, b.N) } } func TestMain(m *testing.M) { // Ensure this directory exists. os.MkdirAll(dir, 0777) // Unmount before the tests start, else they won't work correctly. if err := fuse.Unmount(dir); err != nil { log.Printf("couldn't unmount fuse directory %q: %v", dir, err) } ret := m.Run() // Make sure to unmount at the end, so that we don't leave the system in an // inconsistent state in case something weird happened. if err := fuse.Unmount(dir); err != nil { log.Printf("couldn't unmount fuse directory %q: %v", dir, err) } os.Exit(ret) } cloudsql-proxy-1.17.0/proxy/fuse/fuse_windows.go000066400000000000000000000017511365010350200217720ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package fuse is a package stub for windows, which does not support FUSE. package fuse import ( "errors" "io" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" ) func Supported() bool { return false } func NewConnSrc(mountdir, tmpdir string, connset *proxy.ConnSet) (<-chan proxy.Conn, io.Closer, error) { return nil, nil, errors.New("fuse not supported on windows") } cloudsql-proxy-1.17.0/proxy/limits/000077500000000000000000000000001365010350200172625ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/limits/limits.go000066400000000000000000000057131365010350200211200ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows,!freebsd // Package limits provides routines to check and enforce certain resource // limits on the Cloud SQL client proxy process. package limits import ( "fmt" "syscall" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" ) var ( // For overriding in unittests. syscallGetrlimit = syscall.Getrlimit syscallSetrlimit = syscall.Setrlimit ) // Each connection handled by the proxy requires two file descriptors, one // for the local end of the connection and one for the remote. So, the proxy // process should be able to open at least 8K file descriptors if it is to // handle 4K connections to one instance. const ExpectedFDs = 8500 // SetupFDLimits ensures that the process running the Cloud SQL proxy can have // at least wantFDs number of open file descriptors. It returns an error if it // cannot ensure the same. func SetupFDLimits(wantFDs uint64) error { rlim := &syscall.Rlimit{} if err := syscallGetrlimit(syscall.RLIMIT_NOFILE, rlim); err != nil { return fmt.Errorf("failed to read rlimit for max file descriptors: %v", err) } if rlim.Cur >= wantFDs { logging.Verbosef("current FDs rlimit set to %d, wanted limit is %d. Nothing to do here.", rlim.Cur, wantFDs) return nil } // Linux man page: // The soft limit is the value that the kernel enforces for the corre‐ // sponding resource. The hard limit acts as a ceiling for the soft limit: // an unprivileged process may set only its soft limit to a value in the // range from 0 up to the hard limit, and (irreversibly) lower its hard // limit. A privileged process (under Linux: one with the CAP_SYS_RESOURCE // capability in the initial user namespace) may make arbitrary changes to // either limit value. if rlim.Max < wantFDs { // When the hard limit is less than what is requested, let's just give it a // shot, and if we fail, we fallback and try just setting the softlimit. rlim2 := &syscall.Rlimit{} rlim2.Max = wantFDs rlim2.Cur = wantFDs if err := syscallSetrlimit(syscall.RLIMIT_NOFILE, rlim2); err == nil { logging.Verbosef("Rlimits for file descriptors set to {%v}", rlim2) return nil } } rlim.Cur = wantFDs if err := syscallSetrlimit(syscall.RLIMIT_NOFILE, rlim); err != nil { return fmt.Errorf("failed to set rlimit {%v} for max file descriptors: %v", rlim, err) } logging.Verbosef("Rlimits for file descriptors set to {%v}", rlim) return nil } cloudsql-proxy-1.17.0/proxy/limits/limits_freebsd.go000066400000000000000000000057461365010350200226200ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build freebsd // Package limits provides routines to check and enforce certain resource // limits on the Cloud SQL client proxy process. package limits import ( "fmt" "syscall" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" ) var ( // For overriding in unittests. syscallGetrlimit = syscall.Getrlimit syscallSetrlimit = syscall.Setrlimit ) // Each connection handled by the proxy requires two file descriptors, one // for the local end of the connection and one for the remote. So, the proxy // process should be able to open at least 8K file descriptors if it is to // handle 4K connections to one instance. const ExpectedFDs = 8500 // SetupFDLimits ensures that the process running the Cloud SQL proxy can have // at least wantFDs number of open file descriptors. It returns an error if it // cannot ensure the same. func SetupFDLimits(wantFDs uint64) error { rlim := &syscall.Rlimit{} if err := syscallGetrlimit(syscall.RLIMIT_NOFILE, rlim); err != nil { return fmt.Errorf("failed to read rlimit for max file descriptors: %v", err) } if uint64(rlim.Cur) >= wantFDs { logging.Verbosef("current FDs rlimit set to %d, wanted limit is %d. Nothing to do here.", rlim.Cur, wantFDs) return nil } // Linux man page: // The soft limit is the value that the kernel enforces for the corre‐ // sponding resource. The hard limit acts as a ceiling for the soft limit: // an unprivileged process may set only its soft limit to a value in the // range from 0 up to the hard limit, and (irreversibly) lower its hard // limit. A privileged process (under Linux: one with the CAP_SYS_RESOURCE // capability in the initial user namespace) may make arbitrary changes to // either limit value. if uint64(rlim.Max) < wantFDs { // When the hard limit is less than what is requested, let's just give it a // shot, and if we fail, we fallback and try just setting the softlimit. rlim2 := &syscall.Rlimit{} rlim2.Max = int64(wantFDs) rlim2.Cur = int64(wantFDs) if err := syscallSetrlimit(syscall.RLIMIT_NOFILE, rlim2); err == nil { logging.Verbosef("Rlimits for file descriptors set to {%v}", rlim2) return nil } } rlim.Cur = int64(wantFDs) if err := syscallSetrlimit(syscall.RLIMIT_NOFILE, rlim); err != nil { return fmt.Errorf("failed to set rlimit {%v} for max file descriptors: %v", rlim, err) } logging.Verbosef("Rlimits for file descriptors set to {%v}", rlim) return nil } cloudsql-proxy-1.17.0/proxy/limits/limits_test.go000066400000000000000000000062721365010350200221600ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows package limits import ( "errors" "math" "syscall" "testing" ) type rlimitFunc func(int, *syscall.Rlimit) error func TestSetupFDLimits(t *testing.T) { tests := []struct { desc string getFunc rlimitFunc setFunc rlimitFunc wantFDs uint64 wantErr bool }{ { desc: "Getrlimit fails", getFunc: func(_ int, _ *syscall.Rlimit) error { return errors.New("failed to read rlimit for max file descriptors") }, setFunc: func(_ int, _ *syscall.Rlimit) error { panic("shouldn't be called") }, wantFDs: 0, wantErr: true, }, { desc: "Getrlimit max is less than wantFDs", getFunc: func(_ int, rlim *syscall.Rlimit) error { rlim.Cur = 512 rlim.Max = 512 return nil }, setFunc: func(_ int, rlim *syscall.Rlimit) error { if rlim.Cur != 1024 || rlim.Max != 1024 { return errors.New("setrlimit called with unexpected value") } return nil }, wantFDs: 1024, wantErr: false, }, { desc: "Getrlimit returns rlim_infinity", getFunc: func(_ int, rlim *syscall.Rlimit) error { rlim.Cur = math.MaxUint64 rlim.Max = math.MaxUint64 return nil }, setFunc: func(_ int, _ *syscall.Rlimit) error { panic("shouldn't be called") }, wantFDs: 1024, wantErr: false, }, { desc: "Getrlimit cur is greater than wantFDs", getFunc: func(_ int, rlim *syscall.Rlimit) error { rlim.Cur = 512 rlim.Max = 512 return nil }, setFunc: func(_ int, _ *syscall.Rlimit) error { panic("shouldn't be called") }, wantFDs: 256, wantErr: false, }, { desc: "Setrlimit fails", getFunc: func(_ int, rlim *syscall.Rlimit) error { rlim.Cur = 128 rlim.Max = 512 return nil }, setFunc: func(_ int, _ *syscall.Rlimit) error { return errors.New("failed to set rlimit for max file descriptors") }, wantFDs: 256, wantErr: true, }, { desc: "Success", getFunc: func(_ int, rlim *syscall.Rlimit) error { rlim.Cur = 128 rlim.Max = 512 return nil }, setFunc: func(_ int, _ *syscall.Rlimit) error { return nil }, wantFDs: 256, wantErr: false, }, } for _, test := range tests { oldGetFunc := syscallGetrlimit syscallGetrlimit = test.getFunc defer func() { syscallGetrlimit = oldGetFunc }() oldSetFunc := syscallSetrlimit syscallSetrlimit = test.setFunc defer func() { syscallSetrlimit = oldSetFunc }() gotErr := SetupFDLimits(test.wantFDs) if (gotErr != nil) != test.wantErr { t.Errorf("%s: limits.SetupFDLimits(%d) returned error %v, wantErr %v", test.desc, test.wantFDs, gotErr, test.wantErr) } } } cloudsql-proxy-1.17.0/proxy/limits/limits_windows.go000066400000000000000000000017621365010350200226720ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package limits is a package stub for windows, and we currently don't support // setting limits in windows. package limits import "errors" // We don't support limit on the number of file handles in windows. const ExpectedFDs = 0 func SetupFDLimits(wantFDs uint64) error { if wantFDs != 0 { return errors.New("setting limits on the number of file handles is not supported") } return nil } cloudsql-proxy-1.17.0/proxy/proxy/000077500000000000000000000000001365010350200171425ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/proxy/client.go000066400000000000000000000265571365010350200207660ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proxy import ( "crypto/tls" "crypto/x509" "errors" "fmt" "net" "sync" "sync/atomic" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" "golang.org/x/net/proxy" ) const ( DefaultRefreshCfgThrottle = time.Minute keepAlivePeriod = time.Minute ) var ( // errNotCached is returned when the instance was not found in the Client's // cache. It is an internal detail and is not actually ever returned to the // user. errNotCached = errors.New("instance was not found in cache") refreshCertBuffer = 30 * time.Second ) // Conn represents a connection from a client to a specific instance. type Conn struct { Instance string Conn net.Conn } // CertSource is how a Client obtains various certificates required for operation. type CertSource interface { // Local returns a certificate that can be used to authenticate with the // provided instance. Local(instance string) (tls.Certificate, error) // Remote returns the instance's CA certificate, address, and name. Remote(instance string) (cert *x509.Certificate, addr, name string, err error) } // Client is a type to handle connecting to a Server. All fields are required // unless otherwise specified. type Client struct { // Port designates which remote port should be used when connecting to // instances. This value is defined by the server-side code, but for now it // should always be 3307. Port int // Required; specifies how certificates are obtained. Certs CertSource // Optionally tracks connections through this client. If nil, connections // are not tracked and will not be closed before method Run exits. Conns *ConnSet // Dialer should return a new connection to the provided address. It is // called on each new connection to an instance. net.Dial will be used if // left nil. Dialer func(net, addr string) (net.Conn, error) // RefreshCfgThrottle is the amount of time to wait between configuration // refreshes. If not set, it defaults to 1 minute. // // This is to prevent quota exhaustion in the case of client-side // malfunction. RefreshCfgThrottle time.Duration // The cfgCache holds the most recent connection configuration keyed by // instance. Relevant functions are refreshCfg and cachedCfg. It is // protected by cacheL. cfgCache map[string]cacheEntry cacheL sync.RWMutex // refreshCfgL prevents multiple goroutines from contacting the Cloud SQL API at once. refreshCfgL sync.Mutex // MaxConnections is the maximum number of connections to establish // before refusing new connections. 0 means no limit. MaxConnections uint64 // ConnectionsCounter is used to enforce the optional maxConnections limit ConnectionsCounter uint64 } type cacheEntry struct { lastRefreshed time.Time // If err is not nil, the addr and cfg are not valid. err error addr string cfg *tls.Config } // Run causes the client to start waiting for new connections to connSrc and // proxy them to the destination instance. It blocks until connSrc is closed. func (c *Client) Run(connSrc <-chan Conn) { for conn := range connSrc { go c.handleConn(conn) } if err := c.Conns.Close(); err != nil { logging.Errorf("closing client had error: %v", err) } } func (c *Client) handleConn(conn Conn) { active := atomic.AddUint64(&c.ConnectionsCounter, 1) // Deferred decrement of ConnectionsCounter upon connection closing defer atomic.AddUint64(&c.ConnectionsCounter, ^uint64(0)) if c.MaxConnections > 0 && active > c.MaxConnections { logging.Errorf("too many open connections (max %d)", c.MaxConnections) conn.Conn.Close() return } server, err := c.Dial(conn.Instance) if err != nil { logging.Errorf("couldn't connect to %q: %v", conn.Instance, err) conn.Conn.Close() return } if false { // Log the connection's traffic via the debug connection if we're in a // verbose mode. Note that this is the unencrypted traffic stream. conn.Conn = dbgConn{conn.Conn} } c.Conns.Add(conn.Instance, conn.Conn) copyThenClose(server, conn.Conn, conn.Instance, "local connection on "+conn.Conn.LocalAddr().String()) if err := c.Conns.Remove(conn.Instance, conn.Conn); err != nil { logging.Errorf("%s", err) } } // refreshCfg uses the CertSource inside the Client to find the instance's // address as well as construct a new tls.Config to connect to the instance. It // caches the result. func (c *Client) refreshCfg(instance string) (addr string, cfg *tls.Config, err error) { c.refreshCfgL.Lock() defer c.refreshCfgL.Unlock() throttle := c.RefreshCfgThrottle if throttle == 0 { throttle = DefaultRefreshCfgThrottle } c.cacheL.Lock() if c.cfgCache == nil { c.cfgCache = make(map[string]cacheEntry) } old, oldok := c.cfgCache[instance] c.cacheL.Unlock() if oldok && time.Since(old.lastRefreshed) < throttle { logging.Errorf("Throttling refreshCfg(%s): it was only called %v ago", instance, time.Since(old.lastRefreshed)) // Refresh was called too recently, just reuse the result. return old.addr, old.cfg, old.err } defer func() { c.cacheL.Lock() c.cfgCache[instance] = cacheEntry{ lastRefreshed: time.Now(), err: err, addr: addr, cfg: cfg, } c.cacheL.Unlock() }() mycert, err := c.Certs.Local(instance) if err != nil { return "", nil, err } scert, addr, name, err := c.Certs.Remote(instance) if err != nil { return "", nil, err } certs := x509.NewCertPool() certs.AddCert(scert) cfg = &tls.Config{ ServerName: name, Certificates: []tls.Certificate{mycert}, RootCAs: certs, // We need to set InsecureSkipVerify to true due to // https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/194 // https://tip.golang.org/doc/go1.11#crypto/x509 // // Since we have a secure channel to the Cloud SQL API which we use to retrieve the // certificates, we instead need to implement our own VerifyPeerCertificate function // that will verify that the certificate is OK. InsecureSkipVerify: true, VerifyPeerCertificate: genVerifyPeerCertificateFunc(name, certs), } expire := mycert.Leaf.NotAfter now := time.Now() timeToRefresh := expire.Sub(now) - refreshCertBuffer if timeToRefresh <= 0 { err = fmt.Errorf("new ephemeral certificate expires too soon: current time: %v, certificate expires: %v", expire, now) logging.Errorf("ephemeral certificate (%+v) error: %v", mycert, err) return "", nil, err } go c.refreshCertAfter(instance, timeToRefresh) return fmt.Sprintf("%s:%d", addr, c.Port), cfg, nil } // refreshCertAfter refreshes the epehemeral certificate of the instance after timeToRefresh. func (c *Client) refreshCertAfter(instance string, timeToRefresh time.Duration) { <-time.After(timeToRefresh) logging.Verbosef("ephemeral certificate for instance %s will expire soon, refreshing now.", instance) if _, _, err := c.refreshCfg(instance); err != nil { logging.Errorf("failed to refresh the ephemeral certificate for %s before expiring: %v", instance, err) } } // genVerifyPeerCertificateFunc creates a VerifyPeerCertificate func that verifies that the peer // certificate is in the cert pool. We need to define our own because of our sketchy non-standard // CNs. func genVerifyPeerCertificateFunc(instanceName string, pool *x509.CertPool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { if len(rawCerts) == 0 { return fmt.Errorf("no certificate to verify") } cert, err := x509.ParseCertificate(rawCerts[0]) if err != nil { return fmt.Errorf("x509.ParseCertificate(rawCerts[0]) returned error: %v", err) } opts := x509.VerifyOptions{Roots: pool} if _, err = cert.Verify(opts); err != nil { return err } if cert.Subject.CommonName != instanceName { return fmt.Errorf("certificate had CN %q, expected %q", cert.Subject.CommonName, instanceName) } return nil } } func isExpired(cfg *tls.Config) bool { return time.Now().After(cfg.Certificates[0].Leaf.NotAfter) } func (c *Client) cachedCfg(instance string) (string, *tls.Config) { c.cacheL.RLock() ret, ok := c.cfgCache[instance] c.cacheL.RUnlock() // Don't waste time returning an expired/invalid cert. if !ok || ret.err != nil || isExpired(ret.cfg) { return "", nil } return ret.addr, ret.cfg } // Dial uses the configuration stored in the client to connect to an instance. // If this func returns a nil error the connection is correctly authenticated // to connect to the instance. func (c *Client) Dial(instance string) (net.Conn, error) { if addr, cfg := c.cachedCfg(instance); cfg != nil { ret, err := c.tryConnect(addr, cfg) if err == nil { return ret, err } } addr, cfg, err := c.refreshCfg(instance) if err != nil { return nil, err } return c.tryConnect(addr, cfg) } func (c *Client) tryConnect(addr string, cfg *tls.Config) (net.Conn, error) { d := c.Dialer if d == nil { d = proxy.FromEnvironment().Dial } conn, err := d("tcp", addr) if err != nil { return nil, err } type setKeepAliver interface { SetKeepAlive(keepalive bool) error SetKeepAlivePeriod(d time.Duration) error } if s, ok := conn.(setKeepAliver); ok { if err := s.SetKeepAlive(true); err != nil { logging.Verbosef("Couldn't set KeepAlive to true: %v", err) } else if err := s.SetKeepAlivePeriod(keepAlivePeriod); err != nil { logging.Verbosef("Couldn't set KeepAlivePeriod to %v", keepAlivePeriod) } } else { logging.Verbosef("KeepAlive not supported: long-running tcp connections may be killed by the OS.") } ret := tls.Client(conn, cfg) if err := ret.Handshake(); err != nil { ret.Close() return nil, err } return ret, nil } // NewConnSrc returns a chan which can be used to receive connections // on the passed Listener. All requests sent to the returned chan will have the // instance name provided here. The chan will be closed if the Listener returns // an error. func NewConnSrc(instance string, l net.Listener) <-chan Conn { ch := make(chan Conn) go func() { for { start := time.Now() c, err := l.Accept() if err != nil { logging.Errorf("listener (%#v) had error: %v", l, err) if nerr, ok := err.(net.Error); ok && nerr.Temporary() { d := 10*time.Millisecond - time.Since(start) if d > 0 { time.Sleep(d) } continue } l.Close() close(ch) return } ch <- Conn{instance, c} } }() return ch } // Shutdown waits up to a given amount of time for all active connections to // close. Returns an error if there are still active connections after waiting // for the whole length of the timeout. func (c *Client) Shutdown(termTimeout time.Duration) error { termTime := time.Now().Add(termTimeout) for termTime.After(time.Now()) && atomic.LoadUint64(&c.ConnectionsCounter) > 0 { time.Sleep(1) } active := atomic.LoadUint64(&c.ConnectionsCounter) if active == 0 { return nil } return fmt.Errorf("%d active connections still exist after waiting for %v", active, termTimeout) } cloudsql-proxy-1.17.0/proxy/proxy/client_test.go000066400000000000000000000147161365010350200220170ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proxy import ( "crypto/tls" "crypto/x509" "errors" "fmt" "net" "sync" "sync/atomic" "testing" "time" ) const instance = "instance-name" var ( errFakeDial = errors.New("this error is returned by the dialer") forever = time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC) ) type fakeCerts struct { sync.Mutex called int } type blockingCertSource struct { values map[string]*fakeCerts validUntil time.Time } func (cs *blockingCertSource) Local(instance string) (tls.Certificate, error) { v, ok := cs.values[instance] if !ok { return tls.Certificate{}, fmt.Errorf("test setup failure: unknown instance %q", instance) } v.Lock() v.called++ v.Unlock() // Returns a cert which is valid forever. return tls.Certificate{ Leaf: &x509.Certificate{ NotAfter: cs.validUntil, }, }, nil } func (cs *blockingCertSource) Remote(instance string) (cert *x509.Certificate, addr, name string, err error) { return &x509.Certificate{}, "fake address", "fake name", nil } func TestClientCache(t *testing.T) { b := &fakeCerts{} c := &Client{ Certs: &blockingCertSource{ map[string]*fakeCerts{ instance: b, }, forever, }, Dialer: func(string, string) (net.Conn, error) { return nil, errFakeDial }, } for i := 0; i < 5; i++ { if _, err := c.Dial(instance); err != errFakeDial { t.Errorf("unexpected error: %v", err) } } b.Lock() if b.called != 1 { t.Errorf("called %d times, want called 1 time", b.called) } b.Unlock() } func TestConcurrentRefresh(t *testing.T) { b := &fakeCerts{} c := &Client{ Certs: &blockingCertSource{ map[string]*fakeCerts{ instance: b, }, forever, }, Dialer: func(string, string) (net.Conn, error) { return nil, errFakeDial }, } ch := make(chan error) b.Lock() const numDials = 20 for i := 0; i < numDials; i++ { go func() { _, err := c.Dial(instance) ch <- err }() } b.Unlock() for i := 0; i < numDials; i++ { if err := <-ch; err != errFakeDial { t.Errorf("unexpected error: %v", err) } } b.Lock() if b.called != 1 { t.Errorf("called %d times, want called 1 time", b.called) } b.Unlock() } func TestMaximumConnectionsCount(t *testing.T) { const maxConnections = 10 const numConnections = maxConnections + 1 var dials uint64 = 0 b := &fakeCerts{} certSource := blockingCertSource{ map[string]*fakeCerts{}, forever, } firstDialExited := make(chan struct{}) c := &Client{ Certs: &certSource, Dialer: func(string, string) (net.Conn, error) { atomic.AddUint64(&dials, 1) // Wait until the first dial fails to ensure the max connections count is reached by a concurrent dialer <-firstDialExited return nil, errFakeDial }, MaxConnections: maxConnections, } // Build certSource.values before creating goroutines to avoid concurrent map read and map write instanceNames := make([]string, numConnections) for i := 0; i < numConnections; i++ { // Vary instance name to bypass config cache and avoid second call to Client.tryConnect() in Client.Dial() instanceName := fmt.Sprintf("%s-%d", instance, i) certSource.values[instanceName] = b instanceNames[i] = instanceName } var wg sync.WaitGroup var firstDialOnce sync.Once for _, instanceName := range instanceNames { wg.Add(1) go func(instanceName string) { defer wg.Done() conn := Conn{ Instance: instanceName, Conn: &dummyConn{}, } c.handleConn(conn) firstDialOnce.Do(func() { close(firstDialExited) }) }(instanceName) } wg.Wait() switch { case dials > maxConnections: t.Errorf("client should have refused to dial new connection on %dth attempt when the maximum of %d connections was reached (%d dials)", numConnections, maxConnections, dials) case dials == maxConnections: t.Logf("client has correctly refused to dial new connection on %dth attempt when the maximum of %d connections was reached (%d dials)\n", numConnections, maxConnections, dials) case dials < maxConnections: t.Errorf("client should have dialed exactly the maximum of %d connections (%d connections, %d dials)", maxConnections, numConnections, dials) } } func TestShutdownTerminatesEarly(t *testing.T) { b := &fakeCerts{} c := &Client{ Certs: &blockingCertSource{ map[string]*fakeCerts{ instance: b, }, forever, }, Dialer: func(string, string) (net.Conn, error) { return nil, nil }, } shutdown := make(chan bool, 1) go func() { c.Shutdown(1) shutdown <- true }() shutdownFinished := false // In case the code is actually broken and the client doesn't shut down quickly, don't cause the test to hang until it times out. select { case <-time.After(100 * time.Millisecond): case shutdownFinished = <-shutdown: } if !shutdownFinished { t.Errorf("shutdown should have completed quickly because there are no active connections") } } func TestRefreshTimer(t *testing.T) { oldRefreshCertBuffer := refreshCertBuffer defer func() { refreshCertBuffer = oldRefreshCertBuffer }() refreshCertBuffer = time.Second timeToExpire := 5 * time.Second b := &fakeCerts{} certCreated := time.Now() c := &Client{ Certs: &blockingCertSource{ map[string]*fakeCerts{ instance: b, }, certCreated.Add(timeToExpire), }, Dialer: func(string, string) (net.Conn, error) { return nil, errFakeDial }, RefreshCfgThrottle: 20 * time.Millisecond, } // Call Dial to cache the cert. if _, err := c.Dial(instance); err != errFakeDial { t.Fatalf("Dial(%s) failed: %v", instance, err) } c.cacheL.Lock() cfg, ok := c.cfgCache[instance] c.cacheL.Unlock() if !ok { t.Fatalf("expected instance to be cached") } time.Sleep(timeToExpire - time.Since(certCreated)) // Check if cert was refreshed in the background, without calling Dial again. c.cacheL.Lock() newCfg, ok := c.cfgCache[instance] c.cacheL.Unlock() if !ok { t.Fatalf("expected instance to be cached") } if !newCfg.lastRefreshed.After(cfg.lastRefreshed) { t.Error("expected cert to be refreshed.") } } cloudsql-proxy-1.17.0/proxy/proxy/common.go000066400000000000000000000124461365010350200207700ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package proxy implements client and server code for proxying an unsecure connection over SSL. package proxy import ( "bytes" "errors" "fmt" "io" "net" "sync" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" ) // SQLScope is the Google Cloud Platform scope required for executing API // calls to Cloud SQL. const SQLScope = "https://www.googleapis.com/auth/sqlservice.admin" type dbgConn struct { net.Conn } func (d dbgConn) Write(b []byte) (int, error) { x, y := d.Conn.Write(b) logging.Verbosef("write(%q) => (%v, %v)", b, x, y) return x, y } func (d dbgConn) Read(b []byte) (int, error) { x, y := d.Conn.Read(b) logging.Verbosef("read: (%v, %v) => %q", x, y, b[:x]) return x, y } func (d dbgConn) Close() error { err := d.Conn.Close() logging.Verbosef("close: %v", err) return err } // myCopy is similar to io.Copy, but reports whether the returned error was due // to a bad read or write. The returned error will never be nil func myCopy(dst io.Writer, src io.Reader) (readErr bool, err error) { buf := make([]byte, 4096) for { n, err := src.Read(buf) if n > 0 { if _, werr := dst.Write(buf[:n]); werr != nil { if err == nil { return false, werr } // Read and write error; just report read error (it happened first). return true, err } } if err != nil { return true, err } } } func copyError(readDesc, writeDesc string, readErr bool, err error) { var desc string if readErr { desc = "Reading data from " + readDesc } else { desc = "Writing data to " + writeDesc } logging.Errorf("%v had error: %v", desc, err) } func copyThenClose(remote, local io.ReadWriteCloser, remoteDesc, localDesc string) { firstErr := make(chan error, 1) go func() { readErr, err := myCopy(remote, local) select { case firstErr <- err: if readErr && err == io.EOF { logging.Verbosef("Client closed %v", localDesc) } else { copyError(localDesc, remoteDesc, readErr, err) } remote.Close() local.Close() default: } }() readErr, err := myCopy(local, remote) select { case firstErr <- err: if readErr && err == io.EOF { logging.Verbosef("Instance %v closed connection", remoteDesc) } else { copyError(remoteDesc, localDesc, readErr, err) } remote.Close() local.Close() default: // In this case, the other goroutine exited first and already printed its // error (and closed the things). } } // NewConnSet initializes a new ConnSet and returns it. func NewConnSet() *ConnSet { return &ConnSet{m: make(map[string][]net.Conn)} } // A ConnSet tracks net.Conns associated with a provided ID. // A nil ConnSet will be a no-op for all methods called on it. type ConnSet struct { sync.RWMutex m map[string][]net.Conn } // String returns a debug string for the ConnSet. func (c *ConnSet) String() string { if c == nil { return "" } var b bytes.Buffer c.RLock() for id, conns := range c.m { fmt.Fprintf(&b, "ID %s:", id) for i, c := range conns { fmt.Fprintf(&b, "\n\t%d: %v", i, c) } } c.RUnlock() return b.String() } // Add saves the provided conn and associates it with the given string // identifier. func (c *ConnSet) Add(id string, conn net.Conn) { if c == nil { return } c.Lock() c.m[id] = append(c.m[id], conn) c.Unlock() } // IDs returns a slice of all identifiers which still have active connections. func (c *ConnSet) IDs() []string { if c == nil { return nil } ret := make([]string, 0, len(c.m)) c.RLock() for k := range c.m { ret = append(ret, k) } c.RUnlock() return ret } // Conns returns all active connections associated with the provided ids. func (c *ConnSet) Conns(ids ...string) []net.Conn { if c == nil { return nil } var ret []net.Conn c.RLock() for _, id := range ids { ret = append(ret, c.m[id]...) } c.RUnlock() return ret } // Remove undoes an Add operation to have the set forget about a conn. Do not // Remove an id/conn pair more than it has been Added. func (c *ConnSet) Remove(id string, conn net.Conn) error { if c == nil { return nil } c.Lock() defer c.Unlock() pos := -1 conns := c.m[id] for i, cc := range conns { if cc == conn { pos = i break } } if pos == -1 { return fmt.Errorf("couldn't find connection %v for id %s", conn, id) } if len(conns) == 1 { delete(c.m, id) } else { c.m[id] = append(conns[:pos], conns[pos+1:]...) } return nil } // Close closes every net.Conn contained in the set. func (c *ConnSet) Close() error { if c == nil { return nil } var errs bytes.Buffer c.Lock() for id, conns := range c.m { for _, c := range conns { if err := c.Close(); err != nil { fmt.Fprintf(&errs, "%s close error: %v\n", id, err) } } } c.Unlock() if errs.Len() == 0 { return nil } return errors.New(errs.String()) } cloudsql-proxy-1.17.0/proxy/proxy/common_test.go000066400000000000000000000046031365010350200220230ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file contains tests for common.go package proxy import ( "net" "reflect" "testing" ) var c1, c2, c3 = &dummyConn{}, &dummyConn{}, &dummyConn{} type dummyConn struct{ net.Conn } func (c dummyConn) Close() error { return nil } func TestConnSetAdd(t *testing.T) { s := NewConnSet() s.Add("a", c1) aSlice := []string{"a"} if !reflect.DeepEqual(s.IDs(), aSlice) { t.Fatalf("got %v, want %v", s.IDs(), aSlice) } s.Add("a", c2) if !reflect.DeepEqual(s.IDs(), aSlice) { t.Fatalf("got %v, want %v", s.IDs(), aSlice) } s.Add("b", c3) ids := s.IDs() if len(ids) != 2 { t.Fatalf("got %d ids, wanted 2", len(ids)) } ok := ids[0] == "a" && ids[1] == "b" || ids[1] == "a" && ids[0] == "b" if !ok { t.Fatalf(`got %v, want only "a" and "b"`, ids) } } func TestConnSetRemove(t *testing.T) { s := NewConnSet() s.Add("a", c1) s.Add("a", c2) s.Add("b", c3) s.Remove("b", c3) if got := s.Conns("b"); got != nil { t.Fatalf("got %v, want nil", got) } aSlice := []string{"a"} if !reflect.DeepEqual(s.IDs(), aSlice) { t.Fatalf("got %v, want %v", s.IDs(), aSlice) } s.Remove("a", c1) if !reflect.DeepEqual(s.IDs(), aSlice) { t.Fatalf("got %v, want %v", s.IDs(), aSlice) } s.Remove("a", c2) if len(s.IDs()) != 0 { t.Fatalf("got %v, want empty set", s.IDs()) } } func TestConns(t *testing.T) { s := NewConnSet() s.Add("a", c1) s.Add("a", c2) s.Add("b", c3) got := s.Conns("b") if !reflect.DeepEqual(got, []net.Conn{c3}) { t.Fatalf("got %v, wanted only %v", got, c3) } looking := map[net.Conn]bool{ c1: true, c2: true, c3: true, } for _, v := range s.Conns("a", "b") { if _, ok := looking[v]; !ok { t.Errorf("got unexpected conn %v", v) } delete(looking, v) } if len(looking) != 0 { t.Fatalf("didn't find %v in list of Conns", looking) } } cloudsql-proxy-1.17.0/proxy/proxy/dial.go000066400000000000000000000057771365010350200204220ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proxy import ( "fmt" "net" "net/http" "sync" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs" "golang.org/x/net/context" "golang.org/x/oauth2/google" ) const port = 3307 var dialClient struct { // This client is initialized in Init/InitWithClient/InitDefault // and read in Dial. c *Client sync.Mutex } // Dial returns a net.Conn connected to the Cloud SQL Instance specified. The // format of 'instance' is "project-name:region:instance-name". // // If one of the Init functions hasn't been called yet, InitDefault is called. // // This is a network-level function; consider looking in the dialers // subdirectory for more convenience functions related to actually logging into // your database. func Dial(instance string) (net.Conn, error) { dialClient.Lock() c := dialClient.c dialClient.Unlock() if c == nil { if err := InitDefault(context.Background()); err != nil { return nil, fmt.Errorf("default proxy initialization failed; consider calling proxy.Init explicitly: %v", err) } // InitDefault initialized the client. dialClient.Lock() c = dialClient.c dialClient.Unlock() } return c.Dial(instance) } // Dialer is a convenience type to model the standard 'Dial' function. type Dialer func(net, addr string) (net.Conn, error) // Init must be called before Dial is called. This is a more flexible version // of InitDefault, but allows you to set more fields. // // The http.Client is used to authenticate API requests. // The connset parameter is optional. // If the dialer is nil, net.Conn is used. func Init(auth *http.Client, connset *ConnSet, dialer Dialer) { dialClient.Lock() dialClient.c = &Client{ Port: port, Certs: certs.NewCertSource("", auth, true), Conns: connset, Dialer: dialer, } dialClient.Unlock() } // InitClient is similar to Init, but allows you to specify the Client // directly. // Deprecated: Use InitWithClient instead. func InitClient(c Client) { dialClient.Lock() dialClient.c = &c dialClient.Unlock() } // InitWithClient specifies the Client directly. func InitWithClient(c *Client) { dialClient.Lock() dialClient.c = c dialClient.Unlock() } // InitDefault attempts to initialize the Dial function using application // default credentials. func InitDefault(ctx context.Context) error { cl, err := google.DefaultClient(ctx, "https://www.googleapis.com/auth/sqlservice.admin") if err != nil { return err } Init(cl, nil, nil) return nil } cloudsql-proxy-1.17.0/proxy/util/000077500000000000000000000000001365010350200167365ustar00rootroot00000000000000cloudsql-proxy-1.17.0/proxy/util/cloudsqlutil.go000066400000000000000000000030151365010350200220100ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package util contains utility functions for use throughout the Cloud SQL Proxy. package util import "strings" // SplitName splits a fully qualified instance into its project, region, and // instance name components. While we make the transition to regionalized // metadata, the region is optional. // // Examples: // "proj:region:my-db" -> ("proj", "region", "my-db") // "google.com:project:region:instance" -> ("google.com:project", "region", "instance") // "google.com:missing:part" -> ("google.com:missing", "", "part") func SplitName(instance string) (project, region, name string) { spl := strings.Split(instance, ":") if len(spl) < 2 { return "", "", instance } if dot := strings.Index(spl[0], "."); dot != -1 { spl[1] = spl[0] + ":" + spl[1] spl = spl[1:] } switch { case len(spl) < 2: return "", "", instance case len(spl) == 2: return spl[0], "", spl[1] default: return spl[0], spl[1], spl[2] } } cloudsql-proxy-1.17.0/proxy/util/cloudsqlutil_test.go000066400000000000000000000026521365010350200230550ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package util import "testing" func TestSplitName(t *testing.T) { table := []struct{ in, wantProj, wantRegion, wantInstance string }{ {"proj:region:my-db", "proj", "region", "my-db"}, {"google.com:project:region:instance", "google.com:project", "region", "instance"}, {"google.com:missing:part", "google.com:missing", "", "part"}, } for _, test := range table { gotProj, gotRegion, gotInstance := SplitName(test.in) if gotProj != test.wantProj { t.Errorf("splitName(%q): got %v for project, want %v", test.in, gotProj, test.wantProj) } if gotRegion != test.wantRegion { t.Errorf("splitName(%q): got %v for region, want %v", test.in, gotRegion, test.wantRegion) } if gotInstance != test.wantInstance { t.Errorf("splitName(%q): got %v for instance, want %v", test.in, gotInstance, test.wantInstance) } } } cloudsql-proxy-1.17.0/proxy/util/gcloudutil.go000066400000000000000000000061551365010350200214470ustar00rootroot00000000000000// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package util import ( "bytes" "context" "encoding/json" "fmt" "os/exec" "runtime" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" "golang.org/x/oauth2" ) // GcloudConfigData represents the data returned by `gcloud config config-helper`. type GcloudConfigData struct { Configuration struct { Properties struct { Core struct { Project string Account string } } } Credential struct { AccessToken string `json:"access_token"` TokenExpiry time.Time `json:"token_expiry"` } } func (cfg *GcloudConfigData) oauthToken() *oauth2.Token { return &oauth2.Token{ AccessToken: cfg.Credential.AccessToken, Expiry: cfg.Credential.TokenExpiry, } } type GcloudStatusCode int const ( GcloudOk GcloudStatusCode = iota GcloudNotFound // generic execution failure error not specified above. GcloudExecErr ) type GcloudError struct { GcloudError error Status GcloudStatusCode } func (e *GcloudError) Error() string { return e.GcloudError.Error() } // GcloudConfig returns a GcloudConfigData object or an error of type *GcloudError. func GcloudConfig() (*GcloudConfigData, error) { gcloudCmd := "gcloud" if runtime.GOOS == "windows" { gcloudCmd = gcloudCmd + ".cmd" } if _, err := exec.LookPath(gcloudCmd); err != nil { return nil, &GcloudError{err, GcloudNotFound} } buf, errbuf := new(bytes.Buffer), new(bytes.Buffer) cmd := exec.Command(gcloudCmd, "--format", "json", "config", "config-helper") cmd.Stdout = buf cmd.Stderr = errbuf if err := cmd.Run(); err != nil { err = fmt.Errorf("error reading config: %v; stderr was:\n%v", err, errbuf) logging.Errorf("GcloudConfig: %v", err) return nil, &GcloudError{err, GcloudExecErr} } data := &GcloudConfigData{} if err := json.Unmarshal(buf.Bytes(), data); err != nil { logging.Errorf("Failed to unmarshal bytes from gcloud: %v", err) logging.Errorf(" gcloud returned:\n%s", buf) return nil, &GcloudError{err, GcloudExecErr} } return data, nil } // gcloudTokenSource implements oauth2.TokenSource via the `gcloud config config-helper` command. type gcloudTokenSource struct { } // Token helps gcloudTokenSource implement oauth2.TokenSource. func (src *gcloudTokenSource) Token() (*oauth2.Token, error) { cfg, err := GcloudConfig() if err != nil { return nil, err } return cfg.oauthToken(), nil } func GcloudTokenSource(ctx context.Context) (oauth2.TokenSource, error) { cfg, err := GcloudConfig() if err != nil { return nil, err } return oauth2.ReuseTokenSource(cfg.oauthToken(), &gcloudTokenSource{}), nil } cloudsql-proxy-1.17.0/tests/000077500000000000000000000000001365010350200157425ustar00rootroot00000000000000cloudsql-proxy-1.17.0/tests/common.go000066400000000000000000000300211365010350200175550ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package tests contains integration tests meant to verify the Cloud SQL Proxy // works as expected on a Google Compute Engine VM. It provisions a GCE VM, // loads a newly-compiled proxy client onto that VM, and then does some // connectivity tests. // // If the VM specified by -vm_name doesn't exist already a new VM is created. // If a VM does already exist, its 'sshKeys' metadata value is set to a newly // generated key. // Required flags: // -connection_name, -project package tests import ( "bufio" "bytes" "crypto/rand" "crypto/rsa" "errors" "flag" "fmt" "io" "io/ioutil" "log" "net/http" "os" "testing" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/logging" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "strings" "golang.org/x/crypto/ssh" "golang.org/x/net/context" compute "google.golang.org/api/compute/v1" ) var ( // Required flags. project = flag.String("project", os.Getenv("GCP_PROJECT"), "Google Cloud project to create the GCE test VM in") connectionName = flag.String("connection_name", os.Getenv("INSTANCE_CONNECTION_NAME"), "Cloud SQL instance connection name, in the form of 'project:region:instance'") // Optional flags. vmName = flag.String("vm_name", "proxy-test-gce", "Name of VM to create") vmPublicIP = flag.Bool("vm_public_ip", true, "Whether the VM should have a public IP or not.") zone = flag.String("zone", "us-central1-f", "Zone in which to create the VM") osImage = flag.String("os", defaultOS, "OS image to use when creating a VM") vmNWTag = flag.String("vm_nw_tag", "ssh", "Network tag to apply to the created VM") dbUser = flag.String("db_user", "root", "Name of database user to use during test") dbPass = flag.String("db_pass", "", "Password for the database user; be careful when entering a password on the command line (it may go into your terminal's history). Also note that using a password along with the Cloud SQL Proxy is not necessary as long as you set the hostname of the user appropriately (see https://cloud.google.com/sql/docs/sql-proxy#user)") // Flags for authn/authz. credentialFile = flag.String("credential_file", "", `If provided, this json file will be used to retrieve Service Account credentials. You may set the GOOGLE_APPLICATION_CREDENTIALS environment variable for the same effect.`) token = flag.String("token", "", "When set, the proxy uses this Bearer token for authorization.") ) const defaultOS = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/family/debian-9" type logger interface { Log(args ...interface{}) Logf(format string, args ...interface{}) } func setupGCEProxy(ctx context.Context, l logger, proxyArgs []string) (*ssh.Client, error) { proxyBinary, err := compileProxy() if err != nil { return nil, err } l.Logf("Built new cloud_sql_proxy binary") defer os.Remove(proxyBinary) cl, err := clientFromCredentials(ctx) if err != nil { return nil, err } ssh, err := newOrReuseVM(l, cl) if err != nil { return nil, err } l.Logf("SSH to %s:%s succeeded", *project, *vmName) log.Printf("apt-get update...") var sout, serr bytes.Buffer if err := sshRun(ssh, "sudo apt-get update", nil, &sout, &serr); err != nil { return nil, fmt.Errorf("Failed 'sudo apt-get update' on remote machine: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr) } log.Printf("Install mysql client...") if err := sshRun(ssh, "sudo apt-get install -y mysql-client", nil, &sout, &serr); err != nil { return nil, fmt.Errorf("Failed 'sudo apt-get install -y mysql-client' on remote machine: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr) } if err = sshRun(ssh, "pkill cloud_sql_proxy", nil, &sout, &serr); err != nil { l.Logf("Failed to kill any cloud_sql_proxy process.") } else { l.Logf("Killed already running cloud_sql_proxy process.") } log.Printf("Copy binary to %s:%s...", *project, *vmName) this, err := os.Open(proxyBinary) if err != nil { return nil, fmt.Errorf("Couldn't open %v for reading: %v", proxyBinary, err) } err = sshRun(ssh, "bash -c 'cat >cloud_sql_proxy; chmod +x cloud_sql_proxy; mkdir -p cloudsql'", this, &sout, &serr) this.Close() if err != nil { return nil, fmt.Errorf("Couldn't scp to remote machine: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr) } logs, err := startProxy(ssh, "./cloud_sql_proxy -dir cloudsql -instances "+strings.Join(append([]string{*connectionName}, proxyArgs...), " ")) if err != nil { return nil, err } defer logs.Close() // TODO: Instead of discarding all of the logs, verify that certain logs // happen during connects/disconnects. go io.Copy(ioutil.Discard, logs) l.Logf("Cloud SQL Proxy started on remote host") return ssh, nil } var _ io.ReadCloser = (*process)(nil) // process wraps a remotely executing process, turning it into an // io.ReadCloser. type process struct { io.Reader sess *ssh.Session } // TODO: Return the value of 'Wait'ing on the process. ssh.Session.Signal // doesn't seem to have an effect, so calling it and then doing Wait doesn't do // anything. Closing the session is the only way to clean up until I figure out // what's wrong. func (p *process) Close() error { return p.sess.Close() } // startProxy executes the cloud_sql_proxy via ssh. The returned ReadCloser // must be serviced and closed when finished, otherwise the SSH connection may // block. func startProxy(ssh *ssh.Client, args string) (io.ReadCloser, error) { sess, err := ssh.NewSession() if err != nil { return nil, fmt.Errorf("couldn't open new session: %v", err) } pr, err := sess.StderrPipe() if err != nil { return nil, err } log.Printf("Running proxy...") if err := sess.Start(args); err != nil { return nil, err } // The proxy prints "Ready for new connections" after it starts up // correctly. Start a new goroutine looking for that value so that we can // time-out appropriately (in case something weird is going on). in := bufio.NewReader(pr) buf := new(bytes.Buffer) errCh := make(chan error, 1) go func() { for { bs, err := in.ReadBytes('\n') if err != nil { if err == io.EOF { log.Print("reading stderr gave EOF (remote process closed)") err = sess.Wait() } errCh <- fmt.Errorf("failed to run `%s`: %v", args, err) return } buf.Write(bs) if bytes.Contains(bs, []byte("Ready for new connections")) { errCh <- nil return } } }() select { case err := <-errCh: if err != nil { return nil, err } // Proxy process startup succeeded. return &process{ io.MultiReader(buf, in), sess, }, nil case <-time.After(3 * time.Second): log.Printf("Timeout starting up `%v`", args) } // Starting the proxy timed out, so we should close the SSH session and // return an error after the process exits. // TODO: the sess.Signal method doesn't seem to work... that's what we // really want to do. err = sess.Close() select { case waitErr := <-errCh: if err == nil { err = waitErr } case <-time.After(2 * time.Second): log.Printf("Timeout while waiting for process after closing SSH session.") if err == nil { err = errors.New("timeout waiting for SSH connection to close") } } return nil, fmt.Errorf("timeout waiting for `%v`: error from close: %v; output was:\n\n%s", args, err, buf) } func sshRun(ssh *ssh.Client, cmd string, stdin io.Reader, stdout, stderr io.Writer) error { sess, err := ssh.NewSession() if err != nil { return err } sess.Stdin = stdin if stderr == nil && stdout == nil { if out, err := sess.CombinedOutput(cmd); err != nil { return fmt.Errorf("`%v`: %v; combined output was:\n%s", cmd, err, out) } return nil } sess.Stdout = stdout sess.Stderr = stderr return sess.Run(cmd) } func newOrReuseVM(l logger, cl *http.Client) (*ssh.Client, error) { c, err := compute.New(cl) if err != nil { return nil, err } user := "test-user" pub, auth, err := sshKey() if err != nil { return nil, err } sshPubKey := user + ":" + pub var op *compute.Operation if inst, err := c.Instances.Get(*project, *zone, *vmName).Do(); err != nil { accessConfig := []*compute.AccessConfig{{ Name: "External NAT", Type: "ONE_TO_ONE_NAT"}} if !*vmPublicIP { accessConfig = []*compute.AccessConfig{} } l.Logf("Creating new instance (getting instance %v in project %v and zone %v failed: %v)", *vmName, *project, *zone, err) instProto := &compute.Instance{ Name: *vmName, MachineType: "zones/" + *zone + "/machineTypes/g1-small", Disks: []*compute.AttachedDisk{{ AutoDelete: true, Boot: true, InitializeParams: &compute.AttachedDiskInitializeParams{ SourceImage: *osImage, DiskSizeGb: 10, }}, }, NetworkInterfaces: []*compute.NetworkInterface{{ Network: "projects/" + *project + "/global/networks/default", AccessConfigs: accessConfig, }}, Metadata: &compute.Metadata{ Items: []*compute.MetadataItems{{ Key: "sshKeys", Value: &sshPubKey, }}, }, Tags: &compute.Tags{Items: []string{*vmNWTag}}, ServiceAccounts: []*compute.ServiceAccount{{ Email: "default", Scopes: []string{proxy.SQLScope}, }}, } op, err = c.Instances.Insert(*project, *zone, instProto).Do() if err != nil { return nil, err } } else { l.Logf("attempting to reuse instance %v (in project %v and zone %v)...", *vmName, *project, *zone) set := false md := inst.Metadata for _, v := range md.Items { if v.Key == "sshKeys" { v.Value = &sshPubKey set = true break } } if !set { md.Items = append(md.Items, &compute.MetadataItems{Key: "sshKeys", Value: &sshPubKey}) } op, err = c.Instances.SetMetadata(*project, *zone, *vmName, md).Do() if err != nil { return nil, err } } for { if op.Error != nil && len(op.Error.Errors) > 0 { return nil, fmt.Errorf("errors: %v", op.Error.Errors) } log.Printf("%v %v (%v)", op.OperationType, op.TargetLink, op.Status) if op.Status == "DONE" { break } time.Sleep(5 * time.Second) op, err = c.ZoneOperations.Get(*project, *zone, op.Name).Do() if err != nil { return nil, err } } inst, err := c.Instances.Get(*project, *zone, *vmName).Do() if err != nil { return nil, fmt.Errorf("error getting instance after it was created: %v", err) } // Use the external IP if possible. ip := inst.NetworkInterfaces[0].NetworkIP if inst.NetworkInterfaces[0].AccessConfigs[0].NatIP != "" { ip = inst.NetworkInterfaces[0].AccessConfigs[0].NatIP } var lastErr error for try := 0; try < 10; try++ { if lastErr != nil { const sleepTime = 10 * time.Second logging.Errorf("%v; sleeping for %v then retrying", lastErr, sleepTime) time.Sleep(sleepTime) } ssh, err := ssh.Dial("tcp", ip+":22", &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{auth}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), }) if err == nil { return ssh, nil } lastErr = fmt.Errorf("couldn't ssh to %v (IP=%v): %v", *vmName, ip, err) } return nil, lastErr } func sshKey() (pubKey string, auth ssh.AuthMethod, err error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return "", nil, err } signer, err := ssh.NewSignerFromKey(key) if err != nil { return "", nil, err } pub, err := ssh.NewPublicKey(&key.PublicKey) if err != nil { return "", nil, err } return string(ssh.MarshalAuthorizedKey(pub)), ssh.PublicKeys(signer), nil } func TestMain(m *testing.M) { flag.Parse() switch "" { case *project: log.Fatal("Must set -project") case *connectionName: log.Fatal("Must set -connection_name") } os.Exit(m.Run()) } cloudsql-proxy-1.17.0/tests/common_open_source.go000066400000000000000000000042371365010350200221700ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tests import ( "fmt" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" ) const buildShLocation = "cmd/cloud_sql_proxy/build.sh" func clientFromCredentials(ctx context.Context) (*http.Client, error) { if f := *credentialFile; f != "" { all, err := ioutil.ReadFile(f) if err != nil { return nil, fmt.Errorf("invalid json file %q: %v", f, err) } cfg, err := google.JWTConfigFromJSON(all, proxy.SQLScope) if err != nil { return nil, fmt.Errorf("invalid json file %q: %v", f, err) } return cfg.Client(ctx), nil } else if tok := *token; tok != "" { src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tok}) return oauth2.NewClient(ctx, src), nil } return google.DefaultClient(ctx, proxy.SQLScope) } func compileProxy() (string, error) { // Find the 'build.sh' script by looking for it in cwd, cwd/.., and cwd/../.. var buildSh string var parentPath []string for parents := 0; parents < 2; parents++ { cur := filepath.Join(append(parentPath, buildShLocation)...) if _, err := os.Stat(cur); err == nil { buildSh = cur break } parentPath = append(parentPath, "..") } if buildSh == "" { return "", fmt.Errorf("couldn't find %q; please cd into the local repository", buildShLocation) } cmd := exec.Command(buildSh) if out, err := cmd.CombinedOutput(); err != nil { return "", fmt.Errorf("error during build.sh execution: %v;\n%s", err, out) } return "cloud_sql_proxy", nil } cloudsql-proxy-1.17.0/tests/connection_limit_test.go000066400000000000000000000051531365010350200226710ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Example invocation: // go test -v -run TestConnectionLimit -args -project=my-project \ // -connection_name=my-project:the-region:sql-name package tests import ( "bytes" "context" "fmt" "log" "sync" "testing" "time" ) const ( maxConnections = 5 sleepDuration = 30 clTestTimeout = 3 * time.Minute ) // TestConnectionLimit provisions a new GCE VM and verifies that the proxy // works on it. It uses application default credentials. func TestConnectionLimit(t *testing.T) { if *project == "" { t.Skipf("Test skipped - 'GCP_PROJECT' env var not set.") } if *connectionName == "" { t.Skipf("Test skipped - 'INSTANCE_CONNECTION_NAME' env var not set.") } ctx, cancel := context.WithTimeout(context.Background(), clTestTimeout) defer cancel() ssh, err := setupGCEProxy(ctx, t, []string{"-max_connections", fmt.Sprintf("%d", maxConnections)}) if err != nil { t.Fatal(err) } cmd := fmt.Sprintf(`mysql -u %s -p%s -S cloudsql/%s -e "SELECT 1; SELECT SLEEP(%d);"`, *dbUser, *dbPass, *connectionName, sleepDuration) t.Logf("Connecting using: %s", cmd) // Use less than the sshd MaxStartups configuration (defaults to 10) var wg sync.WaitGroup for i := 0; i < maxConnections; i++ { go func() { wg.Add(1) defer wg.Done() log.Print("Starting blocking mysql command") var sout, serr bytes.Buffer if err := sshRun(ssh, cmd, nil, &sout, &serr); err != nil { t.Errorf("Error running mysql: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr) } t.Logf("Blocking command output %s", &sout) }() } time.Sleep(time.Second * 5) var sout, serr bytes.Buffer log.Print("Test connection refusal") cmd = fmt.Sprintf(`mysql -u %s -p%s -S cloudsql/%s -e "SELECT 1;"`, *dbUser, *dbPass, *connectionName) if err = sshRun(ssh, cmd, nil, &sout, &serr); err == nil { t.Fatalf("Mysql connection should have been refused:\n\nstandard out:\n%s\nstandard err:\n%s", &sout, &serr) } log.Print("Test command output: ", &serr) // Wait for all goroutines to exit, else the test panics. wg.Wait() } cloudsql-proxy-1.17.0/tests/dialers_test.go000066400000000000000000000045101365010350200207530ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // dialers_test verifies that the mysql dialers are functioning properly. It // expects a Cloud SQL Instance to already exist. // // Example invocations: // Using default credentials // go test -v -run TestDialer -args -connection_name=my-project:the-region:instance-name // Using a service account credentials json file // go test -v -run TestDialer -args -connection_name=my-project:the-region:instance-name -credential_file /path/to/credentials.json // Using an access token // go test -v -run TestDialer -args -connection_name=my-project:the-region:instance-name -token "an access token" package tests import ( "database/sql" "testing" "time" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql" "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy" "golang.org/x/net/context" ) const dialersTestTimeout = time.Minute // TestDialer verifies that the mysql dialer works as expected. It assumes that // the -connection_name flag has been set to an existing instance. func TestDialer(t *testing.T) { if *project == "" { t.Skipf("Test skipped - 'GCP_PROJECT' env var not set.") } if *connectionName == "" { t.Skipf("Test skipped - 'INSTANCE_CONNECTION_NAME' env var not set.") } ctx, cancel := context.WithTimeout(context.Background(), dialersTestTimeout) defer cancel() client, err := clientFromCredentials(ctx) if err != nil { t.Fatal(err) } proxy.Init(client, nil, nil) var db *sql.DB if *dbPass == "" { db, err = mysql.Dial(*connectionName, *dbUser) } else { db, err = mysql.DialPassword(*connectionName, *dbUser, *dbPass) } if err != nil { t.Fatal(err) } // The mysql.Dial already did a Ping, so we know the connection is valid if // there was no error returned. db.Close() } cloudsql-proxy-1.17.0/tests/gce_test.go000066400000000000000000000032041365010350200200650ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Example invocation: // go test -v -run TestGCE -args -project=my-project \ // -connection_name=my-project:the-region:sql-name package tests import ( "bytes" "context" "fmt" "testing" "time" ) const gceTestTimeout = 3 * time.Minute // TestGCE provisions a new GCE VM and verifies that the proxy works on it. func TestGCE(t *testing.T) { if *project == "" { t.Skipf("Test skipped - 'GCP_PROJECT' env var not set.") } if *connectionName == "" { t.Skipf("Test skipped - 'INSTANCE_CONNECTION_NAME' env var not set.") } ctx, cancel := context.WithTimeout(context.Background(), gceTestTimeout) defer cancel() ssh, err := setupGCEProxy(ctx, t, nil) if err != nil { t.Fatal(err) } cmd := fmt.Sprintf(`mysql -u %s -p%s -S cloudsql/%s -e "select 1\\G"`, *dbUser, *dbPass, *connectionName) t.Logf("Connecting using: %s", cmd) var sout, serr bytes.Buffer if err = sshRun(ssh, cmd, nil, &sout, &serr); err != nil { t.Fatalf("Error running mysql: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr) } t.Log(&sout) }