{"id":21355013,"url":"https://github.com/salrashid123/org_header_restrict","last_synced_at":"2025-03-16T05:23:39.028Z","repository":{"id":91310071,"uuid":"568952282","full_name":"salrashid123/org_header_restrict","owner":"salrashid123","description":"Restricting GCP API calls with X-Goog-Allowed-Resources header using Envoy and Squid","archived":false,"fork":false,"pushed_at":"2022-11-21T21:24:22.000Z","size":85,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-22T17:47:15.403Z","etag":null,"topics":["authorization","google-cloud-platform","proxy","ssl"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salrashid123.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-21T19:06:58.000Z","updated_at":"2023-06-24T19:14:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"f68cbb67-5ab5-4643-a4b6-f1ebba8dbfa6","html_url":"https://github.com/salrashid123/org_header_restrict","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Forg_header_restrict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Forg_header_restrict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Forg_header_restrict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Forg_header_restrict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salrashid123","download_url":"https://codeload.github.com/salrashid123/org_header_restrict/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243828663,"owners_count":20354536,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","google-cloud-platform","proxy","ssl"],"created_at":"2024-11-22T04:15:35.733Z","updated_at":"2025-03-16T05:23:39.018Z","avatar_url":"https://github.com/salrashid123.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n## Restricting GCP API calls with X-Goog-Allowed-Resources header using Envoy and Squid\n\n`Envoy` and `Squid` egress proxy for Google Cloud APIs that restricts API calls to specific Cloud Organizations.\n\nThis is a simple demo extending the new GCP Org policy header value that will make sure any API calls are restricted to operating only against resources within your org.\n\nno sense in describing the usecases as you can find it in the official docs:\n\n* [Configure organization restrictions](https://cloud.google.com/resource-manager/docs/organization-restrictions/configure-organization-restrictions)\n\nThis tutorial describes two implementations which function in a different way though both require TLS decryption on the proxy.\n\nA) `Envoy Proxy`: `client` --\u003e `envoy` --\u003e `GCP`\n\n   - In this mode, envoy is a [forward proxy](https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/) the client connects directly to envoy\n\n   - Envoy will decrypt the traffic from the client and add the header.\n\n   - Verified to work for both HTTPS and gRPC traffic\n\nB) `Squid Proxy`: `client` --\u003e `squid` (via `PROXY CONNECT`) --\u003e `GCP`\n\n   - This is also a forward proxy but the client uses HTTP `CONNECT` (`http_proxy=, https_proxy=`) to squid.\n\n   - Squid dynamically issues a cert similar to the upstream GCP service except that the CA that signed it is squid.\n\n   - Squid will decrypt the traffic from the client and add the HTTP header.\n\n   - Verified to work only over HTTPS\n\n---\n\nThe following will demonstrate configurations using curl, envoy and squid\n\n![images/arch.png](images/arch.png)\n\n### curl\n\nFirst just verify the basics using curl.\n\nAssume you have access to read the pubsub topics in a given project in your organization.\n\n```bash\n# first get your orgid\n$ gcloud organizations list\nDISPLAY_NAME               ID  DIRECTORY_CUSTOMER_ID\nsomeorganiazation.com  673208786098              C023zwcc8\n\n# create a filew using that orgid\ncat \u003c\u003cEOF \u003e authorized_orgs.json\n{\n  \"resources\": [\n    \"organizations/673208786011\"\n  ],\n  \"options\": \"strict\"\n}\nEOF\n \n# get the encoded header value\nexport RES=`cat authorized_orgs.json | basenc --base64url -w0`\necho $RES\nexport PROJECT_ID=`gcloud config get-value core/project`\n \n# access curl\ncurl -s -H \"X-Goog-Allowed-Resources: $RES\" -H \"Authorization: Bearer `gcloud auth print-access-token`\" https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics\n{\n  \"topics\": [\n    {\n      \"name\": \"projects/PROJECT_ID/topics/topic1\"\n```\n\nif you enter in any other org id, you should see a failure\n```json\n{\n  \"error\": {\n    \"code\": 403,\n    \"message\": \"Access denied by organization restriction. Please contact your administrator for additional information.\",\n    \"status\": \"PERMISSION_DENIED\",\n    \"details\": [\n      {\n        \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n        \"reason\": \"ORG_RESTRICTION_VIOLATION\",\n        \"domain\": \"googleapis.com\",\n        \"metadata\": {\n          \"consumer\": \"projects/32555940559\",\n          \"service\": \"pubsub.googleapis.com\"\n        }\n      }\n    ]\n  }\n}\n```\n\nor if malformed:\n\n```json\n{\n  \"error\": {\n    \"code\": 400,\n    \"message\": \"Org Restriction Header is not valid. Please pass a valid Org Restriction Header.\",\n    \"status\": \"INVALID_ARGUMENT\",\n    \"details\": [\n      {\n        \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n        \"reason\": \"ORG_RESTRICTION_HEADER_INVALID\",\n        \"domain\": \"googleapis.com\",\n        \"metadata\": {\n          \"consumer\": \"projects/32555940559\",\n          \"service\": \"pubsub.googleapis.com\"\n        }\n      }\n    ]\n  }\n}\n```\n\n### Envoy\n\nNow test with envoy:\n\n#### HTTP\n\non linux:\n\n```bash\ncd envoy/\ndocker cp `docker create envoyproxy/envoy-dev:latest`:/usr/local/bin/envoy /tmp/\n./envoy -c envoy_server.yaml -l debug\n\n# new window\nexport PROJECT_ID=`gcloud config get-value core/project`\n\ncurl -v -H \"Host: pubsub.googleapis.com\" \\\n  --connect-to pubsub.googleapis.com:443:127.0.0.1:8081 \\\n  --cacert ../certs/tls-ca.crt \\\n  -H \"Authorization: Bearer `gcloud auth application-default print-access-token`\" \\\n     https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics\n```\n\n#### GRPC\n\nEnvoy natively supports GRPC and can add on the header pretty easily.\n\nTo use this mode, just run the pubsub GRPC Client:\n\n```bash\ncd client/\ngo run main.go --projectID $PROJECT_ID\n```\n\nI intentionally discretely only allowed pubsub to call the 'list' api just to only allow that through:\n\n```yaml\n            virtual_hosts:          \n            - name: pubsub_service\n              domains: [\"pubsub.googleapis.com\"]\n              routes:\n              - match:\n                  prefix: \"/v1/projects/\"         \n                route:\n                  cluster: dynamic_forward_proxy_cluster\n                typed_per_filter_config:\n                  envoy.filters.http.dynamic_forward_proxy:\n                    '@type': type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig\n              - match:\n                  path: \"/google.pubsub.v1.Publisher/ListTopics\"  \n                  grpc: {}            \n                route:\n                  cluster: dynamic_forward_proxy_cluster\n                typed_per_filter_config:\n                  envoy.filters.http.dynamic_forward_proxy:\n                    '@type': type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig         \n```\n\nSome other references\n\n- [gRPC TLS Tunnel proxy using CONNECT](https://github.com/salrashid123/envoy_grpc_tls_bridge)\n- [Distributed HTTP Proxy on Google Cloud using Terraform](https://github.com/salrashid123/envoy_remote_proxy)\n\nJust note that envoy doens't work with CONNECT _and_ dynamic certificates/ssl_bump for CONNECT yet.  see ( [issue 18928](https://github.com/envoyproxy/envoy/issues/18928)).  \n\nNote that for grpc and http, you can directly inject this header too from the client similar to the curl example\n\nfor grpc:\n\n```golang\nctx = metadata.AppendToOutgoingContext(ctx, \"x-goog-allowed-resources\", \"ewogICJyZXNv...\")\n```\n\n\n#### Squid\n\nSquid can add on http headers pretty easily once it decrypts the TLS session.\n\nNote that squid here will not transparently forward the socket through to GCP:  it actually needs to decrypt the TLS session...which means each client needs to trust the TLS CA running on squid (well, you needed that for envoy too)\n\nThe specific squid configuration is in `squid.conf.intercept`:\n\n\n```\nvisible_hostname squid.yourdomain.com\n\nhttp_port 3128 ssl-bump generate-host-certificates=on cert=/apps/tls-ca.crt key=/apps/tls-ca.key\n\nalways_direct allow all\nacl excluded_sites ssl::server_name .wellsfargo.com\nacl httpbin_site ssl::server_name httpbin.org\nacl pubsub_site ssl::server_name pubsub.googleapis.com\nssl_bump splice excluded_sites\nssl_bump bump all\n\nrequest_header_add foo \"bar\" httpbin_site\n\nrequest_header_add X-Goog-Allowed-Resources \"ewogICJyZXNvdXJjZXMiOiBbCiAgICAib3JnYW5pemF0aW9ucy82NzMyMDg3ODYwOTgiCiAgXSwKICAib3B0aW9ucyI6ICJzdHJpY3QiCn0K\" pubsub_site\n\nsslproxy_cert_error deny all\nsslcrtd_program /apps/squid/libexec/security_file_certgen -s /apps/squid/var/lib/ssl_db -M 4MB sslcrtd_children 8 startup=1 idle=1\n```\n\n\nIf you want to test this with your own org, just replace the  value of `X-Goog-Allowed-Resources`.\n\nThe configuration above will add on a sample header for httpbin:\n\nAs a demo:\n\n\nstart squid docker daemon...i happen to have one here for a number of years:\n\n* [squid Proxy dockerfile](https://github.com/salrashid123/squid_proxy)\n\n\n```bash\ncd squid/\nexport DIR=`pwd`\nexport PROJECT_ID=`gcloud config get-value core/project`\n\ndocker run --net=host -p 3128:3128 -v $DIR/../certs/:/certs/ -v $DIR:/config/ -ti docker.io/salrashid123/squidproxy /apps/squid/sbin/squid -NsY -f /config/squid.conf.intercept\n```\n\n\nThen issue a curl command in a new window\n\n```bash\ncd squid/\ncurl -v --proxy-cacert../certs/tls-ca.crt  -x localhost:3128  https://httpbin.org/get\n```\n\nWhat you should see is a `CONNECT` to squid, then squid will download a cert that looks just like httpbin's real one but is issued locally...\n\nthen when the client does connect to squid using a new socket, squid will decode the tls session add onthe foo header key value\n\n```text\n$ curl -v --proxy-cacert $DIR/../certs/tls-ca.crt --cacert $DIR/../certs/tls-ca.crt  -x localhost:3128  https://httpbin.org/get\n*   Trying 127.0.0.1:3128...\n* Connected to localhost (127.0.0.1) port 3128 (#0)\n* allocate connect buffer\n* Establish HTTP proxy tunnel to httpbin.org:443\n\u003e CONNECT httpbin.org:443 HTTP/1.1\n\u003e Host: httpbin.org:443\n\u003e User-Agent: curl/7.85.0\n\u003e Proxy-Connection: Keep-Alive\n\u003e \n\u003c HTTP/1.1 200 Connection established\n\u003c \n\n* Proxy replied 200 to CONNECT request\n* CONNECT phase completed\n* ALPN: offers h2\n* ALPN: offers http/1.1\n*  CAfile: /home/srashid/Desktop/org_header_restrict/squid/../certs/tls-ca.crt\n*  CApath: /etc/ssl/certs\n\n* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: CN=httpbin.org\n*  start date: Jan  9 22:05:43 2022 GMT\n*  expire date: Jan  9 22:05:43 2032 GMT\n*  subjectAltName: host \"httpbin.org\" matched cert's \"httpbin.org\"\n*  issuer: C=US; O=Google; OU=Enterprise; CN=Enterprise Subordinate CA       \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c look, its issued by squid\n*  SSL certificate verify ok.\n\n\n\u003e GET /get HTTP/1.1\n\u003e Host: httpbin.org\n\u003e User-Agent: curl/7.85.0\n\u003e Accept: */*\n\u003e \n\n\u003c HTTP/1.1 200 OK\n\u003c Date: Mon, 21 Nov 2022 16:55:14 GMT\n\u003c Content-Type: application/json\n\u003c Content-Length: 326\n\u003c Server: gunicorn/19.9.0\n\u003c Access-Control-Allow-Origin: *\n\u003c Access-Control-Allow-Credentials: true\n\u003c X-Cache: MISS from squid.yourdomain.com\n\u003c Via: 1.1 squid.yourdomain.com (squid/5.7)\n\u003c Connection: keep-alive\n\u003c \n* TLSv1.2 (IN), TLS header, Supplemental data (23):\n{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept\": \"*/*\", \n    \"Cache-Control\": \"max-age=259200\", \n    \"Foo\": \"bar\",                                 \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c new header added in\n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"curl/7.85.0\", \n    \"X-Amzn-Trace-Id\": \"Root=1-637bad72-5c4919b62e64989759b3c7ab\"\n  }, \n  \"origin\": \"127.0.0.1, 108.56.239.251\", \n  \"url\": \"https://httpbin.org/get\"\n}\n\n```\n\n\n\nthen finally call pubsub\n\n```\ncurl -v --proxy-cacert ../certs/tls-ca.crt \\\n   --cacert ../certs/tls-ca.crt \\\n   -x localhost:3128 \\\n   -H \"Authorization: Bearer `gcloud auth application-default print-access-token`\" \\\n    https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics\n```\n\n\n\n---\n\n### References\n\n* [squid Proxy dockerfile](https://github.com/salrashid123/squid_proxy)\n* [Squid proxy cluster with ssl_bump on Google Cloud](https://github.com/salrashid123/squid_ssl_bump_gcp)\n* [Content Adaptation with ssl_bump](https://github.com/salrashid123/squid_proxy/tree/master/content_adaptation)\n* [Using proxy servers with Google Cloud Client Libraries](https://blog.salrashid.dev/articles/2021/using_proxy_servers/)\n* [Envoy Dynamic Forward Proxy configuration with Downstream SNI for Google APIs and httpbin](https://github.com/salrashid123/envoy_dynamic_forward_proxy_with_sni)\n* [Envoy External Processing filter for decoding Google gRPC PubSub Messages](https://github.com/salrashid123/envoy_gcp_grpc)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalrashid123%2Forg_header_restrict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalrashid123%2Forg_header_restrict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalrashid123%2Forg_header_restrict/lists"}