Envoy
and Squid
egress proxy for Google Cloud APIs that restricts API calls to specific Cloud Organizations.
This 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.
no sense in describing the usecases as you can find it in the official docs:
This tutorial describes two implementations which function in a different way though both require TLS decryption on the proxy.
A) Envoy Proxy
: client
–> envoy
–> GCP
In this mode, envoy is a forward proxy the client connects directly to envoy
Envoy will decrypt the traffic from the client and add the header.
Verified to work for both HTTPS and gRPC traffic
B) Squid Proxy
: client
–> squid
(via PROXY CONNECT
) –> GCP
This is also a forward proxy but the client uses HTTP CONNECT
(http_proxy=, https_proxy=
) to squid.
Squid dynamically issues a cert similar to the upstream GCP service except that the CA that signed it is squid.
Squid will decrypt the traffic from the client and add the HTTP header.
Verified to work only over HTTPS
The following will demonstrate configurations using curl, envoy and squid
You can find the source here
this repo is not supported by google
First just verify the basics using curl.
Assume you have access to read the pubsub topics in a given project in your organization.
# first get your orgid
$ gcloud organizations list
DISPLAY_NAME ID DIRECTORY_CUSTOMER_ID
someorganiazation.com 673208786098 C023zwcc8
# create a filew using that orgid
cat <<EOF > authorized_orgs.json
{
"resources": [
"organizations/673208786011"
],
"options": "strict"
}
EOF
# get the encoded header value
export RES=`cat authorized_orgs.json | basenc --base64url -w0`
echo $RES
export PROJECT_ID=`gcloud config get-value core/project`
# access curl
curl -s -H "X-Goog-Allowed-Resources: $RES" -H "Authorization: Bearer `gcloud auth print-access-token`" https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics
{
"topics": [
{
"name": "projects/PROJECT_ID/topics/topic1"
if you enter in any other org id, you should see a failure
{
"error": {
"code": 403,
"message": "Access denied by organization restriction. Please contact your administrator for additional information.",
"status": "PERMISSION_DENIED",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "ORG_RESTRICTION_VIOLATION",
"domain": "googleapis.com",
"metadata": {
"consumer": "projects/32555940559",
"service": "pubsub.googleapis.com"
}
}
]
}
}
or if malformed:
{
"error": {
"code": 400,
"message": "Org Restriction Header is not valid. Please pass a valid Org Restriction Header.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "ORG_RESTRICTION_HEADER_INVALID",
"domain": "googleapis.com",
"metadata": {
"consumer": "projects/32555940559",
"service": "pubsub.googleapis.com"
}
}
]
}
}
Now test with envoy:
on linux:
cd envoy/
docker cp `docker create envoyproxy/envoy-dev:latest`:/usr/local/bin/envoy /tmp/
./envoy -c envoy_server.yaml -l debug
# new window
export PROJECT_ID=`gcloud config get-value core/project`
curl -v -H "Host: pubsub.googleapis.com" \
--connect-to pubsub.googleapis.com:443:127.0.0.1:8081 \
--cacert ../certs/tls-ca.crt \
-H "Authorization: Bearer `gcloud auth application-default print-access-token`" \
https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics
Envoy natively supports GRPC and can add on the header pretty easily.
To use this mode, just run the pubsub GRPC Client:
cd client/
go run main.go --projectID $PROJECT_ID
I intentionally discretely only allowed pubsub to call the ’list’ api just to only allow that through:
virtual_hosts:
- name: pubsub_service
domains: ["pubsub.googleapis.com"]
routes:
- match:
prefix: "/v1/projects/"
route:
cluster: dynamic_forward_proxy_cluster
typed_per_filter_config:
envoy.filters.http.dynamic_forward_proxy:
'@type': type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig
- match:
path: "/google.pubsub.v1.Publisher/ListTopics"
grpc: {}
route:
cluster: dynamic_forward_proxy_cluster
typed_per_filter_config:
envoy.filters.http.dynamic_forward_proxy:
'@type': type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig
Some other references
Just note that envoy doens’t work with CONNECT and dynamic certificates/ssl_bump for CONNECT yet. see ( issue 18928).
Note that for grpc and http, you can directly inject this header too from the client similar to the curl example
for grpc:
ctx = metadata.AppendToOutgoingContext(ctx, "x-goog-allowed-resources", "ewogICJyZXNv...")
Squid can add on http headers pretty easily once it decrypts the TLS session.
Note 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)
The specific squid configuration is in squid.conf.intercept
:
visible_hostname squid.yourdomain.com
http_port 3128 ssl-bump generate-host-certificates=on cert=/apps/tls-ca.crt key=/apps/tls-ca.key
always_direct allow all
acl excluded_sites ssl::server_name .wellsfargo.com
acl httpbin_site ssl::server_name httpbin.org
acl pubsub_site ssl::server_name pubsub.googleapis.com
ssl_bump splice excluded_sites
ssl_bump bump all
request_header_add foo "bar" httpbin_site
request_header_add X-Goog-Allowed-Resources "ewogICJyZXNvdXJjZXMiOiBbCiAgICAib3JnYW5pemF0aW9ucy82NzMyMDg3ODYwOTgiCiAgXSwKICAib3B0aW9ucyI6ICJzdHJpY3QiCn0K" pubsub_site
sslproxy_cert_error deny all
sslcrtd_program /apps/squid/libexec/security_file_certgen -s /apps/squid/var/lib/ssl_db -M 4MB sslcrtd_children 8 startup=1 idle=1
If you want to test this with your own org, just replace the value of X-Goog-Allowed-Resources
.
The configuration above will add on a sample header for httpbin:
As a demo:
start squid docker daemon…i happen to have one here for a number of years:
cd squid/
export DIR=`pwd`
export PROJECT_ID=`gcloud config get-value core/project`
docker 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
Then issue a curl command in a new window
cd squid/
curl -v --proxy-cacert../certs/tls-ca.crt -x localhost:3128 https://httpbin.org/get
What 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…
then when the client does connect to squid using a new socket, squid will decode the tls session add onthe foo header key value
$ curl -v --proxy-cacert $DIR/../certs/tls-ca.crt --cacert $DIR/../certs/tls-ca.crt -x localhost:3128 https://httpbin.org/get
* Trying 127.0.0.1:3128...
* Connected to localhost (127.0.0.1) port 3128 (#0)
* allocate connect buffer
* Establish HTTP proxy tunnel to httpbin.org:443
> CONNECT httpbin.org:443 HTTP/1.1
> Host: httpbin.org:443
> User-Agent: curl/7.85.0
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Connection established
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed
* ALPN: offers h2
* ALPN: offers http/1.1
* CAfile: /home/srashid/Desktop/org_header_restrict/squid/../certs/tls-ca.crt
* CApath: /etc/ssl/certs
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=httpbin.org
* start date: Jan 9 22:05:43 2022 GMT
* expire date: Jan 9 22:05:43 2032 GMT
* subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
* issuer: C=US; O=Google; OU=Enterprise; CN=Enterprise Subordinate CA <<<<<<<<<< look, its issued by squid
* SSL certificate verify ok.
> GET /get HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.85.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 21 Nov 2022 16:55:14 GMT
< Content-Type: application/json
< Content-Length: 326
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< X-Cache: MISS from squid.yourdomain.com
< Via: 1.1 squid.yourdomain.com (squid/5.7)
< Connection: keep-alive
<
* TLSv1.2 (IN), TLS header, Supplemental data (23):
{
"args": {},
"headers": {
"Accept": "*/*",
"Cache-Control": "max-age=259200",
"Foo": "bar", <<<<<<<<<<<<< new header added in
"Host": "httpbin.org",
"User-Agent": "curl/7.85.0",
"X-Amzn-Trace-Id": "Root=1-637bad72-5c4919b62e64989759b3c7ab"
},
"origin": "127.0.0.1, 108.56.239.251",
"url": "https://httpbin.org/get"
}
then finally call pubsub
curl -v --proxy-cacert ../certs/tls-ca.crt \
--cacert ../certs/tls-ca.crt \
-x localhost:3128 \
-H "Authorization: Bearer `gcloud auth application-default print-access-token`" \
https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics
This site supports webmentions. Send me a mention via this form.