├── .github └── workflows │ └── ci.yaml ├── AUTHORS ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── internal └── version │ └── version.go ├── main.go └── main_test.go /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 the Cloud Run Proxy Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: CI 16 | 17 | on: 18 | push: 19 | branches: 20 | - 'main' 21 | - 'release/**/*' 22 | pull_request: 23 | branches: 24 | - 'main' 25 | - 'release/**/*' 26 | 27 | jobs: 28 | test: 29 | name: test 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: 'actions/checkout@v4' 34 | 35 | - uses: 'actions/setup-go@v4' 36 | with: 37 | go-version: '1.21' 38 | 39 | - name: 'go-test' 40 | run: |- 41 | go test \ 42 | -count=1 \ 43 | -race \ 44 | -shuffle=on \ 45 | -timeout=5m \ 46 | -vet=all \ 47 | ./... 48 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of cloud-run-proxy authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history in source control. 6 | 7 | Google LLC 8 | Seth Vargo 9 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @sethvargo 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cloud Run Proxy 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Community Guidelines 7 | 8 | This project follows 9 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). 10 | 11 | ## Contributor License Agreement 12 | 13 | Contributions to this project must be accompanied by a Contributor License 14 | Agreement. You (or your employer) retain the copyright to your contribution; 15 | this simply gives us permission to use and redistribute your contributions as 16 | part of the project. Head over to to see 17 | your current agreements on file or to sign a new one. 18 | 19 | You generally only need to submit a CLA once, so if you've already submitted one 20 | (even if it was for a different project), you probably don't need to do it 21 | again. 22 | 23 | ## Code reviews 24 | 25 | All submissions, including submissions by project members, require review. We 26 | use GitHub pull requests for this purpose. Consult 27 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 28 | information on using pull requests. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloud Run Proxy 2 | 3 | **📣 The Cloud Run Proxy functionality is now bundled in the Cloud SDK 🎉!** 4 | 5 | ```shell 6 | gcloud beta run services proxy 7 | ``` 8 | 9 | For more information, see the [proxy documentation][gcloud-docs]. The remainder 10 | of this README is for users wishing to compile or run the proxy outside of Cloud 11 | SDK. 12 | 13 | --- 14 | 15 | Cloud Run Proxy is a small proxy to assist in authenticating as an end-user to 16 | Google Cloud Run. It leverages Cloud Run's existing Cloud IAM integration to 17 | handle access. 18 | 19 | By default, users with the Cloud Run Invoker role (`roles/run.invoker`) have 20 | permission to call services. This is demonstrated multiple times in the Cloud 21 | Run documentation: 22 | 23 | ```sh 24 | curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://my-service.a.run.app/ 25 | ``` 26 | 27 | This works great for stateless API calls, but what if you have a 28 | semi-long-running service or a web interface to access via the browser? This is 29 | where **Cloud Run Proxy** can help! 30 | 31 | Cloud Run Proxy runs a localhost proxy that behaves exactly as if you're calling 32 | the URL directly, except that it adds your local user's authentication info 33 | (from gcloud). 34 | 35 | If you're familiar with the Cloud SQL Proxy, it's like that, but for Cloud Run. 36 | 37 | **Cloud Run Proxy is not an officially supported Google product.** 38 | 39 | ## Usage 40 | 41 | Note: you must install and authenticated to the [Google Cloud 42 | SDK](https://cloud.google.com/sdk) (gcloud) for the proxy to pull your 43 | authentication token. You local user must also have Cloud Run Invoker 44 | permissions on the target service. 45 | 46 | 1. Install the proxy from the [Releases](https://github.com/GoogleCloudPlatform/cloud-run-proxy/releases) page or manually: 47 | 48 | ```sh 49 | go install github.com/GoogleCloudPlatform/cloud-run-proxy@main 50 | ``` 51 | 52 | 1. Start the proxy: 53 | 54 | ```sh 55 | cloud-run-proxy -host https://my-service.a.run.app 56 | ``` 57 | 58 | 1. Point your browser or `curl` at http://localhost:8080! 59 | 60 | 61 | ## Options 62 | 63 | Change the local bind address: 64 | 65 | ```sh 66 | cloud-run-proxy -bind "127.0.0.1:1234" 67 | ``` 68 | 69 | Obligatory security note: do not bind to 0.0.0.0 or your public IP. Anyone on 70 | your network would then be able to access your service unauthenticated. Always 71 | bind to a loopback. 72 | 73 | Override the token (useful if you don't have gcloud installed): 74 | 75 | ```sh 76 | cloud-run-proxy -token "yc..." 77 | ``` 78 | 79 | Specify a custom audience: 80 | 81 | ```sh 82 | cloud-run-proxy -audience "https://my-service-daga283.run.app" 83 | ``` 84 | 85 | Note: when running on Compute Engine or other services with a metadata service, the audience defaults to the host URL. If you are accessing your Cloud Run service through a load balancer with a vanity domain, you must specify the audience value as the non-vanity URL of your service: 86 | 87 | ```sh 88 | cloud-run-proxy -host "https://custom-domain.com" -audience "https://my-service-daga283.run.app" 89 | ``` 90 | 91 | [gcloud-docs]: https://cloud.google.com/sdk/gcloud/reference/beta/run/services/proxy 92 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/GoogleCloudPlatform/cloud-run-proxy 2 | 3 | go 1.21 4 | 5 | require ( 6 | golang.org/x/net v0.23.0 7 | golang.org/x/oauth2 v0.15.0 8 | google.golang.org/api v0.152.0 9 | ) 10 | 11 | require ( 12 | cloud.google.com/go/compute v1.23.3 // indirect 13 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 14 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 15 | github.com/golang/protobuf v1.5.3 // indirect 16 | github.com/google/s2a-go v0.1.7 // indirect 17 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 18 | go.opencensus.io v0.24.0 // indirect 19 | golang.org/x/crypto v0.31.0 // indirect 20 | golang.org/x/sys v0.28.0 // indirect 21 | golang.org/x/text v0.21.0 // indirect 22 | google.golang.org/appengine v1.6.8 // indirect 23 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect 24 | google.golang.org/grpc v1.59.0 // indirect 25 | google.golang.org/protobuf v1.33.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= 3 | cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= 4 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 5 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 8 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 9 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 13 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 14 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 15 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 16 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 17 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 18 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 19 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 20 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 21 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 22 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 23 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 24 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 25 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 26 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 27 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 28 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 29 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 30 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 31 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 32 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 33 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 34 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 35 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 36 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 37 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 38 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 41 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 42 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 43 | github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= 44 | github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 45 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 46 | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 47 | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 48 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= 49 | github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 50 | github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= 51 | github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= 52 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 53 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 55 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 56 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 57 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 58 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 59 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 60 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 61 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 62 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 63 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 64 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 65 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 66 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 67 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 68 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 69 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 70 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 71 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 72 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 73 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 74 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 75 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 76 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 77 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 78 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 79 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 80 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 81 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 82 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 83 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 84 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 85 | golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= 86 | golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 87 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 88 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 92 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 93 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 94 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 95 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 102 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 103 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 104 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 105 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 106 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 107 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 108 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 109 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 110 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 111 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 112 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 113 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 114 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 115 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 116 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 117 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 118 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 119 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 120 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 121 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 122 | google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY= 123 | google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= 124 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 125 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 126 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 127 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 128 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 129 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 130 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 131 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= 132 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= 133 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 134 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 135 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 136 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 137 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 138 | google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= 139 | google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 140 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 141 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 142 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 143 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 144 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 145 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 146 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 147 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 148 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 149 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 150 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 151 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 152 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 153 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 154 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 155 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 156 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 157 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 158 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 the Cloud Run Proxy Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | import ( 18 | "runtime" 19 | "runtime/debug" 20 | ) 21 | 22 | var ( 23 | // name is the name of the binary. 24 | name string 25 | Name = valueOrFallback(name, func() string { 26 | return "cloud-run-proxy" 27 | }) 28 | 29 | // version is the main package version. 30 | version string 31 | Version = valueOrFallback(version, func() string { 32 | if info, ok := debug.ReadBuildInfo(); ok { 33 | if v := info.Main.Version; v != "" { 34 | return v 35 | } 36 | } 37 | return "source" 38 | }) 39 | 40 | // commit is the git sha. 41 | commit string 42 | Commit = valueOrFallback(commit, func() string { 43 | if info, ok := debug.ReadBuildInfo(); ok { 44 | for _, setting := range info.Settings { 45 | if setting.Key == "vcs.revision" && setting.Value != "" { 46 | return setting.Value 47 | } 48 | } 49 | } 50 | return "unknown" 51 | }) 52 | 53 | // OSArch is the operating system and architecture combination. 54 | OSArch = runtime.GOOS + "/" + runtime.GOARCH 55 | 56 | // HumanVersion is the compiled version. 57 | HumanVersion = Name + " " + Version + " (" + Commit + ", " + OSArch + ")" 58 | ) 59 | 60 | func valueOrFallback(val string, fn func() string) string { 61 | if val != "" { 62 | return val 63 | } 64 | return fn() 65 | } 66 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 the Cloud Run Proxy Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package main is the entrypoint for cloud-run-proxy. It starts the proxy 16 | // server. 17 | package main 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "crypto/x509" 23 | "errors" 24 | "flag" 25 | "fmt" 26 | "net" 27 | "net/http" 28 | "net/http/httputil" 29 | "net/url" 30 | "os" 31 | "os/signal" 32 | "strings" 33 | "syscall" 34 | "time" 35 | 36 | "github.com/GoogleCloudPlatform/cloud-run-proxy/internal/version" 37 | "golang.org/x/net/http2" 38 | "golang.org/x/net/http2/h2c" 39 | "golang.org/x/oauth2" 40 | "golang.org/x/oauth2/google" 41 | "google.golang.org/api/idtoken" 42 | ) 43 | 44 | type contextKey string 45 | 46 | const contextKeyError = contextKey("error") 47 | 48 | const ADCHintMessage = "If you are trying to authenticate using gcloud, try running `gcloud auth login --update-adc` first then restart the proxy." 49 | 50 | var userAgent = version.Name + "/" + version.Version + " (" + version.OSArch + ")" 51 | 52 | var ( 53 | flagHost = flag.String("host", "", 54 | "Cloud Run host for which to proxy") 55 | flagBind = flag.String("bind", "127.0.0.1:8080", 56 | "local host:port on which to listen") 57 | flagAudience = flag.String("audience", "", 58 | "override JWT audience value (aud)") 59 | flagToken = flag.String("token", "", 60 | "override OIDC token") 61 | flagPrependUserAgent = flag.Bool("prepend-user-agent", true, 62 | "prepend a custom User-Agent header to requests") 63 | flagServerUpTime = flag.String("server-up-time", "", 64 | "duration the proxy server will run. For example, 1h, 1m30s - empty means forever") 65 | flagHttp2 = flag.Bool("http2", false, 66 | "handle http2 requests (allows grpc calls)") 67 | flagAuthorizationHeader = flag.String("authorization-header", "X-Serverless-Authorization", 68 | "header to provide the bearer token") 69 | ) 70 | 71 | func main() { 72 | ctx, cancel := signal.NotifyContext(context.Background(), 73 | syscall.SIGINT, syscall.SIGTERM) 74 | defer cancel() 75 | 76 | if err := realMain(ctx); err != nil { 77 | cancel() 78 | 79 | fmt.Fprintln(os.Stderr, err.Error()) 80 | os.Exit(1) 81 | } 82 | } 83 | 84 | func realMain(ctx context.Context) error { 85 | // Quick handle version and help. 86 | for _, v := range os.Args { 87 | if v == "-v" || v == "-version" || v == "--version" { 88 | fmt.Fprintln(os.Stdout, version.HumanVersion) 89 | return nil 90 | } 91 | } 92 | 93 | // Parse and validate flags. 94 | flag.Parse() 95 | var merr error 96 | if *flagHost == "" { 97 | merr = errors.Join(merr, fmt.Errorf("missing -host")) 98 | } 99 | if *flagBind == "" { 100 | merr = errors.Join(merr, fmt.Errorf("missing -bind")) 101 | } 102 | if *flagAuthorizationHeader == "" { 103 | merr = errors.Join(merr, fmt.Errorf("missing -authorization-header")) 104 | } 105 | var d time.Duration 106 | if *flagServerUpTime != "" { 107 | var err error 108 | d, err = time.ParseDuration(*flagServerUpTime) 109 | if err != nil { 110 | merr = errors.Join(merr, fmt.Errorf("error parsing -server-up-time: %w", err)) 111 | } 112 | } 113 | if merr != nil { 114 | return merr 115 | } 116 | 117 | // Build the remote host URL. 118 | host, err := smartBuildHost(*flagHost) 119 | if err != nil { 120 | return fmt.Errorf("failed to parse host URL: %w", err) 121 | } 122 | 123 | // Compute the audience, default to the host. However, there might be cases 124 | // where you want to specify a custom aud (such as when accessing through a 125 | // load balancer). 126 | audience := *flagAudience 127 | if audience == "" { 128 | audience = host.String() 129 | } 130 | 131 | // Get the best token source. Cloud Run expects the audience parameter to be 132 | // the URL of the service. 133 | tokenSource, err := findTokenSource(ctx, *flagToken, audience) 134 | if err != nil { 135 | return fmt.Errorf("failed to find token source: %w", err) 136 | } 137 | 138 | // Build the local bind URL. 139 | bindHost, bindPort, err := net.SplitHostPort(*flagBind) 140 | if err != nil { 141 | return fmt.Errorf("failed to parse bind address: %w", err) 142 | } 143 | bind := &url.URL{ 144 | Scheme: "http", 145 | Host: net.JoinHostPort(bindHost, bindPort), 146 | } 147 | 148 | // Construct the proxy. 149 | proxy := buildProxy(host, bind, tokenSource, *flagHttp2, nil) 150 | 151 | server := createServer(bind, proxy, *flagHttp2) 152 | 153 | // Start server in background. 154 | errCh := make(chan error, 1) 155 | go func() { 156 | fmt.Fprintf(os.Stderr, "%s proxies to %s\n", bind, host) 157 | if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { 158 | select { 159 | case errCh <- err: 160 | default: 161 | } 162 | } 163 | }() 164 | 165 | // Wait for stop 166 | if *flagServerUpTime != "" { 167 | select { 168 | case err := <-errCh: 169 | return fmt.Errorf("server error: %w", err) 170 | case <-time.After(d): 171 | case <-ctx.Done(): 172 | fmt.Fprint(os.Stderr, "\nserver is shutting down...\n") 173 | } 174 | } else { 175 | select { 176 | case err := <-errCh: 177 | return fmt.Errorf("server error: %w", err) 178 | case <-ctx.Done(): 179 | fmt.Fprint(os.Stderr, "\nserver is shutting down...\n") 180 | } 181 | } 182 | 183 | // Attempt graceful shutdown. 184 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 185 | defer cancel() 186 | if err := server.Shutdown(ctx); err != nil { 187 | return fmt.Errorf("failed to shutdown server: %w", err) 188 | } 189 | return nil 190 | } 191 | 192 | // buildProxy builds the reverse proxy server, forwarding requests on bind to 193 | // the provided host. 194 | func buildProxy(host, bind *url.URL, tokenSource oauth2.TokenSource, enableHttp2 bool, caCertificate *x509.Certificate) *httputil.ReverseProxy { 195 | // Build and configure the proxy. 196 | proxy := httputil.NewSingleHostReverseProxy(host) 197 | // Use http2 for outgoing connections 198 | if enableHttp2 { 199 | var tlsConfig *tls.Config 200 | if caCertificate != nil { 201 | caPool := x509.NewCertPool() 202 | caPool.AddCert(caCertificate) 203 | tlsConfig = &tls.Config{ 204 | RootCAs: caPool, 205 | } 206 | } 207 | proxy.Transport = &http2.Transport{ 208 | TLSClientConfig: tlsConfig, 209 | } 210 | } 211 | // Configure the director. 212 | originalDirector := proxy.Director 213 | proxy.Director = func(r *http.Request) { 214 | // Call the original director, which configures most of the URL bits for us. 215 | originalDirector(r) 216 | 217 | // Override host - this is not done by the default director, but Cloud Run 218 | // requires it. 219 | r.Header.Set("Host", host.Host) 220 | r.Host = host.Host 221 | 222 | ctx := r.Context() 223 | 224 | // Get the oauth token. 225 | token, err := tokenSource.Token() 226 | if err != nil { 227 | *r = *r.WithContext(context.WithValue(ctx, contextKeyError, 228 | fmt.Errorf("failed to get token: %w\n\n%s", err, ADCHintMessage))) 229 | return 230 | } 231 | 232 | // Get the id_token. 233 | idToken := token.AccessToken 234 | if idToken == "" { 235 | *r = *r.WithContext(context.WithValue(ctx, contextKeyError, 236 | fmt.Errorf("missing id_token"))) 237 | return 238 | } 239 | 240 | // Set a custom user-agent header. 241 | if *flagPrependUserAgent { 242 | ua := r.Header.Get("User-Agent") 243 | if ua == "" { 244 | ua = userAgent 245 | } else { 246 | ua = userAgent + " " + ua 247 | } 248 | 249 | r.Header.Set("User-Agent", ua) 250 | } 251 | 252 | // Set the bearer token to be the id token 253 | r.Header.Set(*flagAuthorizationHeader, "Bearer "+idToken) 254 | } 255 | 256 | // Configure error handling. 257 | proxy.ModifyResponse = func(r *http.Response) error { 258 | // In case of redirection, make sure the local address is still used for 259 | // host. If it has location header && the location url host is the proxied 260 | // host, change it to local address with http. 261 | location := r.Header.Get("Location") 262 | if location != "" { 263 | locationURL, err := url.Parse(location) 264 | if err == nil && locationURL.Host == host.Host { 265 | locationURL.Scheme = bind.Scheme 266 | locationURL.Host = bind.Host 267 | r.Header.Set("Location", locationURL.String()) 268 | } 269 | } 270 | 271 | ctx := r.Request.Context() 272 | if err, ok := ctx.Value(contextKeyError).(error); ok && err != nil { 273 | return fmt.Errorf("[PROXY ERROR] %w", err) 274 | } 275 | 276 | return nil 277 | } 278 | proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { 279 | http.Error(w, err.Error(), http.StatusInternalServerError) 280 | } 281 | 282 | return proxy 283 | } 284 | 285 | // Create server and wraps proxy with h2c handler if http2 is enabled 286 | func createServer(bind *url.URL, proxy *httputil.ReverseProxy, enableHttp2 bool) *http.Server { 287 | var handler http.Handler 288 | handler = proxy 289 | if enableHttp2 { 290 | http2server := &http2.Server{} 291 | handler = h2c.NewHandler(proxy, http2server) 292 | } 293 | // Create server. 294 | return &http.Server{ 295 | Addr: bind.Host, 296 | Handler: handler, 297 | } 298 | } 299 | 300 | // findTokenSource fetches the reusable/cached oauth2 token source. If rawToken 301 | // is provided, that token is used as a static value and the audience parameter 302 | // is ignored. Othwerise, this attempts to get the renewable token from the 303 | // environment (via Application Default Credentials). 304 | func findTokenSource(ctx context.Context, rawToken, audience string) (oauth2.TokenSource, error) { 305 | // Prefer supplied value, usually from the flag. 306 | if rawToken != "" { 307 | token := &oauth2.Token{AccessToken: rawToken} 308 | return oauth2.StaticTokenSource(token), nil 309 | } 310 | 311 | // Try to use the idtoken package, which will use the metadata service. 312 | // However, the idtoken package does not work with gcloud's ADC, so we need to 313 | // handle that case by falling back to default ADC search. However, the 314 | // default ADC has a token at a different path, so we construct a custom token 315 | // source for this edge case. 316 | tokenSource, err := idtoken.NewTokenSource(ctx, audience) 317 | if err != nil { 318 | // Return any unexpected error. 319 | if !strings.Contains(err.Error(), "credential must be service_account") { 320 | return nil, fmt.Errorf("failed to get idtoken source: %w", err) 321 | } 322 | 323 | // If we got this far, it means that we found ADC, but the ADC was supplied 324 | // by a gcloud "authorized_user" instead of a service account. Thus we 325 | // fallback to the default ADC search. 326 | tokenSource, err = google.DefaultTokenSource(ctx) 327 | if err != nil { 328 | return nil, fmt.Errorf("failed to get default token source: %w", err) 329 | } 330 | tokenSource = &idTokenFromDefaultTokenSource{TokenSource: tokenSource} 331 | } 332 | return oauth2.ReuseTokenSource(nil, tokenSource), nil 333 | } 334 | 335 | type idTokenFromDefaultTokenSource struct { 336 | TokenSource oauth2.TokenSource 337 | } 338 | 339 | // Token extracts the id_token field from ADC from a default token source and 340 | // puts the value into the AccessToken field. 341 | func (s *idTokenFromDefaultTokenSource) Token() (*oauth2.Token, error) { 342 | token, err := s.TokenSource.Token() 343 | if err != nil { 344 | return nil, err 345 | } 346 | 347 | idToken, ok := token.Extra("id_token").(string) 348 | if !ok { 349 | return nil, fmt.Errorf("missing id_token") 350 | } 351 | 352 | return &oauth2.Token{ 353 | AccessToken: idToken, 354 | Expiry: token.Expiry, 355 | }, nil 356 | } 357 | 358 | // smartBuildHost parses the URL, handling the case where it's a real URL 359 | // (https://foo.bar) or just a host (foo.bar). If it's just a host, the URL is 360 | // assumed to be TLS. 361 | func smartBuildHost(host string) (*url.URL, error) { 362 | u, err := url.Parse(host) 363 | if err != nil { 364 | return nil, fmt.Errorf("failed to parse url: %w", err) 365 | } 366 | 367 | if u.Scheme == "" { 368 | u.Scheme = "https" 369 | 370 | parts := strings.SplitN(u.Path, "/", 2) 371 | switch len(parts) { 372 | case 0: 373 | u.Host = "" 374 | u.Path = "" 375 | case 1: 376 | u.Host = parts[0] 377 | u.Path = "" 378 | case 2: 379 | u.Host = parts[0] 380 | u.Path = parts[1] 381 | } 382 | } 383 | 384 | u.Host = strings.TrimSpace(u.Host) 385 | if u.Host == "" { 386 | return nil, fmt.Errorf("invalid url %q (missing host)", host) 387 | } 388 | 389 | u.Path = strings.TrimSpace(u.Path) 390 | if u.Path == "/" { 391 | u.RawPath = "" 392 | u.Path = "" 393 | } 394 | 395 | return u, nil 396 | } 397 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 the Cloud Run Proxy Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "net" 21 | "net/http" 22 | "net/http/httptest" 23 | "net/url" 24 | "testing" 25 | ) 26 | 27 | func testRandomPort(tb testing.TB) int { 28 | tb.Helper() 29 | 30 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 31 | if err != nil { 32 | tb.Fatalf("failed to resolve tcp addr: %s", err) 33 | } 34 | 35 | l, err := net.ListenTCP("tcp", addr) 36 | if err != nil { 37 | tb.Fatalf("failed to listen on addr: %s", err) 38 | } 39 | defer l.Close() 40 | 41 | return l.Addr().(*net.TCPAddr).Port 42 | } 43 | 44 | func TestBuildProxy(t *testing.T) { 45 | t.Parallel() 46 | 47 | ctx := context.Background() 48 | 49 | mux := http.NewServeMux() 50 | called := false 51 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 52 | if got, want := r.Header.Get("X-Serverless-Authorization"), "Bearer mytoken"; got != want { 53 | t.Errorf("invalid authorization header: expected %q to be %q", got, want) 54 | } 55 | called = true 56 | }) 57 | 58 | srv := httptest.NewServer(mux) 59 | 60 | srvURL, err := url.Parse(srv.URL) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | bind := &url.URL{ 66 | Scheme: "http", 67 | Host: fmt.Sprintf("localhost:%d", testRandomPort(t)), 68 | } 69 | 70 | src, err := findTokenSource(ctx, "mytoken", "aud") 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | proxy := buildProxy(srvURL, bind, src, false, nil) 76 | 77 | t.Run("root", func(t *testing.T) { 78 | t.Parallel() 79 | 80 | w := httptest.NewRecorder() 81 | r := httptest.NewRequest(http.MethodGet, "/", nil) 82 | proxy.ServeHTTP(w, r) 83 | if !called{ 84 | t.Errorf("handler not called") 85 | } 86 | }) 87 | } 88 | 89 | func TestFindTokenSource(t *testing.T) { 90 | t.Parallel() 91 | 92 | ctx := context.Background() 93 | 94 | t.Run("static", func(t *testing.T) { 95 | t.Parallel() 96 | 97 | src, err := findTokenSource(ctx, "mytoken", "aud") 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | token, err := src.Token() 103 | if err != nil { 104 | t.Fatal(err) 105 | } 106 | 107 | if got, want := token.AccessToken, "mytoken"; got != want { 108 | t.Errorf("expected %q to be %q", got, want) 109 | } 110 | }) 111 | } 112 | 113 | func TestSmartBuildHost(t *testing.T) { 114 | t.Parallel() 115 | 116 | cases := []struct { 117 | name string 118 | in string 119 | exp string 120 | err bool 121 | }{ 122 | { 123 | name: "full_http", 124 | in: "http://my.run.app", 125 | exp: "http://my.run.app", 126 | err: false, 127 | }, 128 | { 129 | name: "full_https", 130 | in: "https://my.run.app", 131 | exp: "https://my.run.app", 132 | err: false, 133 | }, 134 | { 135 | name: "partial", 136 | in: "my.run.app", 137 | exp: "https://my.run.app", 138 | err: false, 139 | }, 140 | { 141 | name: "trailing_slash", 142 | in: "my.run.app/", 143 | exp: "https://my.run.app", 144 | err: false, 145 | }, 146 | { 147 | name: "empty", 148 | in: "", 149 | exp: "", 150 | err: true, 151 | }, 152 | } 153 | 154 | for _, tc := range cases { 155 | tc := tc 156 | 157 | t.Run(tc.name, func(t *testing.T) { 158 | t.Parallel() 159 | 160 | u, err := smartBuildHost(tc.in) 161 | if (err != nil) != tc.err { 162 | t.Fatal(err) 163 | } 164 | 165 | if u != nil { 166 | if got, want := u.String(), tc.exp; got != want { 167 | t.Errorf("expected %q to be %q", got, want) 168 | } 169 | } 170 | }) 171 | } 172 | } 173 | 174 | func TestHttp2(t *testing.T) { 175 | t.Parallel() 176 | 177 | ctx := context.Background() 178 | mux := http.NewServeMux() 179 | called := false 180 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 181 | if got, want := r.Header.Get("X-Serverless-Authorization"), "Bearer mytoken"; got != want { 182 | t.Errorf("invalid authorization header: expected %q to be %q", got, want) 183 | } 184 | called = true 185 | }) 186 | 187 | 188 | srv := httptest.NewUnstartedServer(mux) 189 | srv.EnableHTTP2 = true 190 | srv.StartTLS() 191 | 192 | srvURL, err := url.Parse(srv.URL) 193 | if err != nil { 194 | t.Fatal(err) 195 | } 196 | 197 | bind := &url.URL{ 198 | Scheme: "http", 199 | Host: fmt.Sprintf("localhost:%d", testRandomPort(t)), 200 | } 201 | 202 | src, err := findTokenSource(ctx, "mytoken", "aud") 203 | if err != nil { 204 | t.Fatal(err) 205 | } 206 | 207 | proxy := buildProxy(srvURL, bind, src, true, srv.Certificate()) 208 | 209 | t.Run("root", func(t *testing.T) { 210 | t.Parallel() 211 | 212 | w := httptest.NewRecorder() 213 | r := httptest.NewRequest(http.MethodGet, "/", nil) 214 | 215 | proxy.ServeHTTP(w, r) 216 | 217 | if !called{ 218 | t.Errorf("handler not called") 219 | } 220 | defer srv.Close() 221 | }) 222 | 223 | } 224 | --------------------------------------------------------------------------------