Skip to content

Commit

Permalink
use googleid token library; fix readme
Browse files Browse the repository at this point in the history
  • Loading branch information
salrashid123 committed Oct 24, 2020
1 parent 9728c56 commit bd9f305
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.11 AS build
FROM golang:1.15 AS build
ENV PROJECT gce_metadata_server
WORKDIR /src/$PROJECT
COPY go.mod go.sum ./
Expand Down
116 changes: 77 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This script acts as a GCE's internal metadata server for local testing/emulation

It returns a live access_token that can be used directly by [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) transparently.

>> This is not an officially supported Google product
For example, you can use `ComputeCredentials` on your laptop:

```python
Expand All @@ -21,14 +23,14 @@ creds.refresh(request)

session = google.auth.transport.requests.AuthorizedSession(creds)
r = session.get('https://www.googleapis.com/userinfo/v2/me').json()
print str(r)
print(str(r))
```

This is useful to test any script or code locally that my need to contact GCE's metadata server for custom, user-defined variables or access_tokens.

Another usecase for this is to verify how Application Defaults will behave while running a local docker container. A local running docker container will not have access to GCE's metadata server but by bridging your container to the emulator, you are basically allowing GCP API access directly from within a container on your local workstation (vs. running the code comprising the container directly on the workstation and relying on gcloud credentials (not metadata)).

For more inforamtion on the request-response characteristics:
For more information on the request-response characteristics:
* [GCE Metadata Server](https://cloud.google.com/compute/docs/storing-retrieving-metadata)

The script performs the following:
Expand Down Expand Up @@ -59,7 +61,7 @@ Overall, the proxy works to emulate the link-local address `169.254.169.254` tha
## Usage

This script runs a basic webserver and responds back as the Google Compute Engine's metadata server. A local webserver
runs on a non-privleged port (default: 8080) and uses a `serviceAccountFile` file or environment variables return an `access_token`
runs on a non-privileged port (default: 8080) and uses a `serviceAccountFile` file or environment variables return an `access_token`
and optional live project user-defined metadata.

You can run the emulator either:
Expand Down Expand Up @@ -105,6 +107,8 @@ sudo apt-get install socat
sudo socat TCP4-LISTEN:80,fork TCP4:127.0.0.1:8080
```

If you don't mind running the program on port `:80` directly, you can skip the socat and iptables and simply start the emulator on the default http port.

- ![images/setup_1.png](images/setup_1.png)

Alternatively, you can create an OUTPUT `iptables` rule to intercept and redirect the metadata traffic.
Expand All @@ -113,27 +117,30 @@ Alternatively, you can create an OUTPUT `iptables` rule to intercept and redirec
iptables -t nat -A OUTPUT -p tcp -d 169.254.169.254 --dport 80 -j REDIRECT --to-port 8080
```

* **4. Downlaod JSON ServiceAccount file**
* **4. Download JSON ServiceAccount file**

Create a GCP Service Account JSON file

```bash
export GOOGLE_PROJECT_ID=`gcloud config get-value core/project`
export GOOGLE_NUMERIC_PROJECT_ID=`gcloud projects describe $GOOGLE_PROJECT_ID --format="value(projectNumber)"`
gcloud iam service-accounts create metadata-sa
gcloud iam service-accounts keys create metdata-sa.json --iam-account=metadata-sa-@$GOOGLE_PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts keys create metdata-sa.json --iam-account=metadata-sa@$GOOGLE_PROJECT_ID.iam.gserviceaccount.com
```

You can assign IAM permissions now to the service accunt for whatever resources it may need to access

* **5. Run the metadata server**

```bash
go run main.go -logtostderr \
-alsologtostderr -v 5 \
-port :8080 \
--serviceAccountFile /path/to/metadta-sa.json \
--numericProjectId $PROJECT_NUMBER \
mkdir certs/
mv metadata-sa.json certs

go run main.go -logtostderr \
-alsologtostderr -v 5 \
-port :8080 \
--serviceAccountFile certs/metdata-sa.json \
--numericProjectId $PROJECT_NUMBER \
--tokenScopes https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform
```

Expand All @@ -142,17 +149,16 @@ or via docker
```
```bash
mkdir certs/
cp metadata-sa.json certs
docker run \
-v `pwd`/certs/:/certs/ \
-p 8080:8080 \
-t salrashid123/gcemetadataserver \
-serviceAccountFile /certs/svc_account.json \
-logtostderr -alsologtostderr -v 5 \
-port :8080 \
-numericProjectId $PROJECT_NUMBER \
docker run \
-v `pwd`/certs/:/certs/ \
-p 8080:8080 \
-t salrashid123/gcemetadataserver \
-serviceAccountFile /certs/metdata-sa.json \
-logtostderr -alsologtostderr -v 5 \
-port :8080 \
-numericProjectId $PROJECT_NUMBER \
-tokenScopes https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform
```

Expand Down Expand Up @@ -202,6 +208,36 @@ curl -v -H 'Metadata-Flavor: Google' http://169.254.169.254/computeMetadata/v1/i

You can also use the python snippet using `Application Default Credentials` to test.

The following endpoints shows how to acquire an IDToken

```bash
curl -H "Metadata-Flavor: Google" \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://foo.bar'
```

The `id_token` will be signed by google but issued by the service account you used
```json
{
"alg": "RS256",
"kid": "178ab1dc5913d929d37c23dcaa961872f8d70b68",
"typ": "JWT"
}.
{
"aud": "https://foo.bar",
"azp": "metadata-sa@$PROJECT.iam.gserviceaccount.com",
"email": "[email protected]",
"email_verified": true,
"exp": 1603550806,
"iat": 1603547206,
"iss": "https://accounts.google.com",
"sub": "117605711420724299222"
}

```


>>> Unlike the _real_ gce metadataserver, this will **NOT** return the full identity document or license info :(`&format=[FORMAT]&licenses=[LICENSES]`)
### Run the metadata server with containers

#### Access the local emulator _from_ containers
Expand Down Expand Up @@ -251,24 +287,26 @@ https://kubernetes.io/docs/concepts/services-networking/service/#services-withou
If you do not have access to certificate file or would like to specify **static** token values via env-var, the metadata server supports the following environment variables as substitutions. Once you set these environment variables, the service will not look for anything using the service Account JSON file (even if specified)

```
GOOGLE_PROJECT_ID = 'GOOGLE_PROJECT_ID'
GOOGLE_NUMERIC_PROJECT_ID = 'GOOGLE_NUMERIC_PROJECT_ID'
GOOGLE_ACCESS_TOKEN = 'GOOGLE_ACCESS_TOKEN'
GOOGLE_ACCOUNT_EMAIL = `GOOGLE_ACCOUNT_EMAIL`
export GOOGLE_PROJECT_ID=`gcloud config get-value core/project`
export GOOGLE_NUMERIC_PROJECT_ID=`gcloud projects describe $GOOGLE_PROJECT_ID --format="value(projectNumber)"`
gcloud auth activate-service-account --key-file=`pwd`/certs/metdata-sa.json
export GOOGLE_ACCESS_TOKEN=`gcloud auth print-access-token`
export GOOGLE_ACCOUNT_EMAIL=`gcloud config get-value core/account`
```

for example,
```
docker run \
-p 8080:8080 \
-t salrashid123/gcemetadataserver \
-logtostderr -alsologtostderr -v 5 \
-e GOOGLE_ACCESS_TOKEN=some_static_token \
-e GOOGLE_NUMERIC_PROJECT_ID=12345 \
-e GOOGLE_PROJECT_ID=my_project \
-e GOOGLE_ACCOUNT_EMAIL=metadata-sa-@$GOOGLE_PROJECT_ID.iam.gserviceaccount.com gcemetadataserver \
-port :8080 \
-tokenScopes https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform
docker run \
-p 8080:8080 \
-e GOOGLE_ACCESS_TOKEN=$GOOGLE_ACCESS_TOKEN \
-e GOOGLE_NUMERIC_PROJECT_ID=$GOOGLE_NUMERIC_PROJECT_ID \
-e GOOGLE_PROJECT_ID=$GOOGLE_PROJECT_ID \
-e GOOGLE_ACCOUNT_EMAIL=$GOOGLE_ACCOUNT_EMAIL \
-t salrashid123/gcemetadataserver \
-port :8080 \
-tokenScopes https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform \
-logtostderr -alsologtostderr -v 5
```

Expand Down Expand Up @@ -299,23 +337,23 @@ iptables -P OUTPUT ACCEPT
### Port mapping :80 --> :8080
Since GCE's metadata server listens on http for :80, this script relies on utilities like 'socat' to redirect port traffic. _socat_ has pretty
basic connection handling so you'd be better with iptables, gunicorn.
You are free to either run the script on port :80 directly (as root), or use a utilitity like iptables, HAProxy, nginx, etc to do this mapping.
You are free to either run the script on port :80 directly (as root), or use a utility like iptables, HAProxy, nginx, etc to do this mapping.

The following example of an iptables route 80 -> 8080 on the local interface
```
sudo iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
```

#### Extending the sample
You can extend this sample for any arbitrary metadta you are interested in emulating (eg, disks, hostname, etc).
Simply add the routes to the webserver and handle the responses accordingly. It is recomended to view the request-response format directly on the metadata server to compare against.
You can extend this sample for any arbitrary metadata you are interested in emulating (eg, disks, hostname, etc).
Simply add the routes to the webserver and handle the responses accordingly. It is recommended to view the request-response format directly on the metadata server to compare against.


### TODO

1. Directory Browsing

Instead of explictly setting routes, use the local filesystem to return the strucure for non-dynamic content or attributes. In this way, the metadata server just returns the directory and files that mimics the metadata server structure.
Instead of explicitly setting routes, use the local filesystem to return the structure for non-dynamic content or attributes. In this way, the metadata server just returns the directory and files that mimics the metadata server structure.

eg: create a directory structure similar to:

Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module github.com/salrashid123/gce_metadata_server

go 1.15

require (
github.com/coreos/go-oidc v2.1.0+incompatible // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/gorilla/mux v1.7.3
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/salrashid123/oauth2 v0.0.0-20190826032145-209a73f76d79
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
google.golang.org/api v0.9.0 // indirect
google.golang.org/grpc v1.23.0 // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
google.golang.org/api v0.33.0 // indirect
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
)
Loading

0 comments on commit bd9f305

Please sign in to comment.