Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 275 additions & 18 deletions docs/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ This document describes how to seal tokens and how to make requests with them.

# Sealing tokens

To seal a token you need the sealing key of your tokenizer.
* If you have the `OPEN_KEY` set in your environment you can get the seal by running `go run ./cmd/tokenizer -sealkey`.
To seal a token you need the sealing key of your tokenizer.
* If you have the `OPEN_KEY` set in your environment you can get the seal by running `go run ./cmd/tokenizer -sealkey`.
* The seal key is written by the server when the server is started on a line like `listening address="localhost:8080" seal_key=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`.

## Command line
Expand All @@ -22,10 +22,10 @@ export SEAL_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

# Using a json file
cat > token.json <<_EOF_
{
{
"inject_processor": { "token": "MY_SECRET_TOKEN" },
"allowed_hosts": ["timflyio-go-example.fly.dev"],
"fly_src_auth": {
"fly_src_auth": {
"allowed_orgs": ["tim-newsham"],
"allowed_apps": ["thenewsh"]
}
Expand Down Expand Up @@ -138,7 +138,7 @@ curl -s -x https://tokenizer.fly.dev \

### Bearer Auth

The `tokenizer.BearerAuthConfig` specifies that requests must contain a `Proxy-Authorization` header containing
The `tokenizer.BearerAuthConfig` specifies that requests must contain a `Proxy-Authorization` header containing
`Bearer secret`, `FlyV1 secret` or `Basic base64`. In the case of `Basic base64`, the base64 component must
be a base64 encoding of `user:secret`. The message is authenticated if any such header exists and contains
a secret matching the configured sha256 digest.
Expand Down Expand Up @@ -261,7 +261,7 @@ Request processors specify how requests are modified. They usually inject secret
A sealed token specifies a single processor that is applied to requests that have been authenticated and validated.
See documentation below on `tokenizer.MultiProcessorConfig` if multiple processors are needed.

### FmtProcessor
### FmtProcessor
Several processors include an optional `tokenizer.FmtProcessor` or `tokenizer.DstProcessor` to provide
flexibility in how secrets are injected into requests.

Expand Down Expand Up @@ -298,8 +298,9 @@ SEAL='{
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

PARAMS='{"fmt": "Cower %s"}'
curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED; {\"fmt\": \"Cower %s\"}" \
-H "Proxy-Tokenizer: $SEALED; $PARAMS" \
http://timflyio-go-example.fly.dev
```

Expand Down Expand Up @@ -335,8 +336,9 @@ SEAL='{
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

PARAMS='{"dst": "Z-Auth"}'
curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED; {\"dst\": \"Z-Auth\"}" \
-H "Proxy-Tokenizer: $SEALED; $PARAMS" \
http://timflyio-go-example.fly.dev
```

Expand All @@ -347,6 +349,8 @@ By default the token is injected as an `Authoriation` header as `Bearer %s`.
Note that if the format and destination are left unspecified, the requester is free to choose any format and destination.
This may pose a security risk, and it is a best practice to limit the allowed formats and destinations.

Params: "fmt", "dst".

The json for the injection processor is:

```json
Expand Down Expand Up @@ -379,7 +383,7 @@ curl -s -x https://tokenizer.fly.dev \
### InjectHMACProcessorConfig

The `tokenizer.InjectHMACProcessorConfig` injects computes the HMAC of the request body and injects it as a secret.
It includes an optional format and destination.
It includes an optional format and destination.
By default the token is injected as an `Authoriation` header as `Bearer %x`.
The processor includes the name of a hash algorithm to use, and an HMAC key. "sha256" is the only supported
algorithm.
Expand All @@ -389,6 +393,8 @@ If the request includes the "msg" parameter in the `Proxy-Tokenizer` header, the
Note that if the format and destination are left unspecified, the requester is free to choose any format and destination.
This may pose a security risk, and it is a best practice to limit the allowed formats and destinations.

Params: "msg", "fmt", "dst"

The json for an HMAC injection processor that uses the key "secret" is as follows. Note that the json value for key must be given in Base64.

```json
Expand Down Expand Up @@ -422,23 +428,273 @@ curl -s -x https://tokenizer.fly.dev \

### InjectBodyProcessorConfig

TBD replaces included token into the body by replacing a template placeholder...
The `tokenizer.InjectBodyProcessorConfig` processor rewrites a pattern in the request body with a token.
The configuration includes the placeholder string that will be replaced. The requester can choose
a different placeholder using the "placeholder" parameter in the `Proxy-Tokenizer` header.
If no placeholder is specified, it uses `{{ACCESS_TOKEN}}` as the placeholder.

Params: "placeholder"

```json
"inject_body_processor": {
"token": "secret",
"placeholder": "TokenHere"
}
```

Example:

```bash
SEAL='{
"inject_body_processor": {
"token": "secret",
"placeholder": "TokenHere"
},
"allowed_hosts": ["timflyio-go-example.fly.dev"],
"no_auth": {}
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED" \
http://timflyio-go-example.fly.dev/test -d "hello TokenHere there."
```

### OAuthProcessorConfig

TBD holds two tokens. usually the access token is injected. the requester can ask for the refresh token to be injected instead.
The `tokenizer.OAuthProcessorConfig` processor holds an OAuth access token and refresh token and can
inject either one. It normally expands the access token in the same way that the injection processor does.
The requester can specify which of the two tokens to expand by providing an "st" parameter alongside the
sealed token. When "st" is given as "refresh", the refresh token will be injected. When "st" is given as "access"
the access token will be injected.

If the requester specifies the "placeholder" parameter in the `Proxy-Tokenizer` header, the selected token replaces
the specified placeholder string in the request body. Otherwise the selected token is injected into the
`Authorization` header as a bearer token.

Params: "placeholder", "st"

```json
"oauth2_processor": {
"token": {
"access_token": "MY ACCESS TOKEN",
"refresh_token": "MY REFRESH TOKEN"
}
}
```

This example injects the refresh token into the request's authorization header. If the parameter in the `Proxy-Tokenizer` header was
omitted, it would have injected the access token.

```bash
SEAL='{
"oauth2_processor": {
"token": {
"access_token": "MY ACCESS TOKEN",
"refresh_token": "MY REFRESH TOKEN"
}
},
"allowed_hosts": ["timflyio-go-example.fly.dev"],
"no_auth": {}
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

PARAM='{"st":"refresh"}'
curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED; $PARAM" \
http://timflyio-go-example.fly.dev
```


### OAuthBodyProcessorConfig

TBD
The `tokenizer.OAuthBodyProcessorConfig` processor seals an access token and a refresh token
and rewrites a pattern in the request body with one of the two tokens.
The configuration includes the placeholder string that will be replaced. The requester can choose
a different placeholder using the "placeholder" parameter in the `Proxy-Tokenizer` header.
If no placeholder is specified, it uses `{{ACCESS_TOKEN}}` as the placeholder.

The requester can specify which of the two tokens to expand by providing an "st" parameter alongside the
sealed token. When "st" is given as "refresh", the refresh token will be injected. When "st" is given as "access"
the access token will be injected.

Params: "placeholder", "st"

```json
"oauth2_body_processor": {
"token": {
"access_token": "MY ACCESS TOKEN",
"refresh_token": "MY REFRESH TOKEN"
},
"placeholder": "TokenHere"
}
```

This example injects the refresh token into the request's body in place of the "TokenHere" placeholder.
If the parameter in the `Proxy-Tokenizer` header was omitted, it would have injected the access token.

```bash
SEAL='{
"oauth2_body_processor": {
"token": {
"access_token": "MY ACCESS TOKEN",
"refresh_token": "MY REFRESH TOKEN"
},
"placeholder": "TokenHere"
},
"allowed_hosts": ["timflyio-go-example.fly.dev"],
"no_auth": {}
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

PARAM='{"st":"refresh"}'
curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED; $PARAM" \
http://timflyio-go-example.fly.dev/test -d "hello TokenHere there."
```

### Sigv4ProcessorConfig

TBD
The `tokenizer.Sigv4ProcessorConfig` seals an AWS access key and secret key and re-signs AWS v4 requests
with them. It does this by using the request's `Authorization` header, extracting the date, region, and
service components from the `Credential` attribute, and generating a new authorization header using
the credentials and this information. If the request has an `X-Amz-Date` header, it uses the date
from this header instead of the one in the `Credentials` attribute.

The original implementation accidentally swapped the region and service that was extracted from
the `Credentials` attribute. To address this without breaking compatibility with existing sealed
credentials, the optional `no_swap` field was added, which should be set to `true` in all newly
sealed credentials.

It strips the following headers from the request prior to signing:

* `Proxy-Tokenizer`
* `Proxy-Authorization`
* `Accept-Encoding`
* `Proxy-Connection`
* `Proxy-Authenticate`
* `Proxy-Authorization`
* `Via`
* `X-Forwarded-For`
* `X-Forwarded-Port`
* `X-Forwarded-Proto`
* `X-Forwarded-Ssl`
* `X-Request-Start`

```json
"sigv4_processor": {
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"no_swap": true
}
```

Example

```bash
SEAL='{
"sigv4_processor": {
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"no_swap": true
},
"allowed_hosts": ["timflyio-go-example.fly.dev"],
"no_auth": {}
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED" \
-H "Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20260406/us-east-1/iam/aws4_request, Signature=xxx" \
http://timflyio-go-example.fly.dev
```

### JWTProcessorConfig

The `tokenizer.JWTProcessorConfig` processor is used to support JWT code exchanges.
It seals parameters needed to make a JWT code exchange request, and replaces the
request body with a code exchange request. The original body is not processed.
It processes the response by extracting
the access token, sealing it using a `tokenizer.InjectProcessorConfig`, and replacing
the response body with JSON carrying the newly sealed token. This token can be used
to make inject the resulting JWT into future requests using the tokenizer.

TODO: this section could be made clearer and could use a working example.
See the documentation for `JWTProcessorConfig` in `processor.go` for more details.

Params: "sub", "scopes"

The sealed parameters include the private key and URL needed to make the code exchange request,
and the "email", "sub", and "scopes" that will be used when constructing a sealed JWT.
Scopes are specified as a space-separated list.
If the token URL is not specified, it defaults to `https://oauth2.googleapis.com/token`.
The private key must be base64 encoded and must be an RSA256 key in PKCS1 format, or
a PKCS8 key in RSA256, ECDSA, or Ed25519 format. ECDSA keys must use the P256, P384, or P521 curve.
If the "sub" or "scopes" parameters are provided in the `Proxy-Tokenizer` header, they override
the sealed "sub" and "scopes" parameters.

```json
"jwt_processor": {
"private_key": "xxxbase64xxx",
"email": "jwtsRus@service.com",
"sub": "user@host.com",
"scopes": "read:user read:project",
"token_url": "https://github.com/login/oauth/access_token"
}
```

When processing requests, the original request body is ignored and replaced with a token request in the following form:

```json
{
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": "signed-jwt-here"
}
```

The JWT in the request body is formed using the sealed parameters and is signed with the sealed private key.

```json
{
"iss": "from-sealed-email",
"scope": "from-sealed-scopes",
"aud": "from-sealed-token-url",
"iat": from-current-time,
"exp": from-current-time-plus-one-hour,
}
```

The response from the target is expected to be in the following format:

```json
{
"access_token": "access-token-here",
"expires_in": access-token-expiration,
"token_type": "access-token-type"
}
```

This response is processed by sealing the access token as a `tokenizer.InjectProcessorConfig`.
The sealed token carries the same request authentication and request validator requirements
as the `tokenizer.JWTProcessorConfig`.
The response is replaced with a response body in the following format.
Note: the expiration of the original access token is adjusted down by 60 seconds.

```json
{
"sealed_token": "sealed-token-here",
"expires_in": sealed-token-expiration,
"token_type": "sealed"
}
```

TBD
Include a working example.

### ClientCredentialsProcessorConfig

TBD
See the documentation for `ClientCredentialsProcessorConfig` in `processor.go` for details.

### MultiProcessorConfig
The `tokenizer.MultiProcessorConfig` processes requests with a list of request processors. It is encoded as follows:
Expand Down Expand Up @@ -511,8 +767,9 @@ SEAL='{
}'
SEALED=$(go run cmd/sealtoken/main.go -json "$SEAL")

PARAMS='{"fmt": "Cower %s", "dst": "X-Auth"}'
curl -s -x https://tokenizer.fly.dev \
-H "Proxy-Tokenizer: $SEALED; {\"fmt\": \"Cower %s\", \"dst\": \"X-Auth\"}" \
-H "Proxy-Tokenizer: $SEALED; $PARAMS" \
http://timflyio-go-example.fly.dev
```

Expand All @@ -523,8 +780,8 @@ The following parameters are supported:
* `fmt` overrides the default format in several injection processors.
* `dst` overrides the default destination header in several injection processors.
* `msg` specifies a message to compute the HMAC over instead of the request body in the HMAC injection processor.
* `st` selects which of several subtokens to inject, and is used by the OAuth processor to choose to inject the refresh token instead of the access token.
* `placeholder` TBD
* `sub` TBD
* `scopes` TBD
* `st` selects which of several subtokens to inject, and is used by the OAuth processor and the OAuth body processor to choose to inject the refresh token instead of the access token.
* `placeholder` selects a placeholder string that will be replaced with an injected token. It overrides any placeholder in the sealed token configuration. It is supported by the oauth processor, the body injection processor, and the oauth body injection processor.
* `sub` overrides the sealed subject to use when constructing signed JWTs in the JWT processor.
* `scopes` overrides the sealed scopes to use when constructing signed JWTs in the JWT processor.

Loading
Loading