Vault Proxy
12 min read
Vault Proxy forwards a request from your backend to a third-party service, such as a payment gateway, and fills in vaulted data on the way, so sensitive data such as PII and PCI data never touches your application code or infrastructure in plaintext. Keeping that data off your network also helps you meet compliance requirements such as PCI.
Vault Proxy supports:
| Feature | Overview |
|---|---|
| Any HTTPS URL and method | Works with any HTTPS resource |
| Flexible detokenization | Detokenizes any type of request body, whatever the content type |
| Fixed IP range | Requests from Vault Proxy always come from a fixed set of IP addresses that you can allowlist at the destination |
| Custom headers | Attaches custom headers |
| Authentication secrets | Attaches custom authentication secrets |
| Client certificates (mTLS) | Attaches a certificate and key for mTLS client certificate authentication |
| Server certificate pinning | Validates the destination against one or more root CAs and leaf certificates you specify |
| Ingress vaulting | Vaults data that comes back in the response, with JSONPath rules (XPath and regex are planned) |
Configuration
You configure the proxy in one of two ways: save a configuration in the dashboard or through the admin API, or pass every setting as headers on each request ("just-in-time"). Both support the same capabilities. A saved configuration also holds the destination's authentication credentials, so you do not manage them in your requests.
Just-in-time
To proxy to any destination without a saved configuration, send this header:
| Header | Usage | Description |
|---|---|---|
x-fp-proxy-target-url | Required for just-in-time | The HTTPS URL Footprint routes the request to |
By configuration
To use a configuration you saved in the dashboard or through the API, put its id in the request path:
bash
Basics
Making a proxy request
A Vault Proxy request is an authenticated HTTP POST to /vault_proxy/jit or /vault_proxy/{proxy_config_id}.
For example, this request fills in one user's vaulted data and forwards the body to a payment API:
bash1 2curl https://api.onefootprint.com/vault_proxy/jit \ 3 -u sk_test_CXUsbCR8j2kH6e5GeEl8eSBnQTIPCUaKpv: \ 4 -X POST \ 5 -H 'x-fp-proxy-target-url: https://payments.acmebank.com' \ 6 -H 'x-fp-proxy-fwd-custom-header: custom value' \ 7 -H 'x-fp-proxy-fwd-content-type: application/json' \ 8 --data '{ 9 "full_name": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.first_name }} {{ fp_id_tctecBEvGc98V7Vx4MhZU.id.last_name }}", 10 "last4_ssn": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.ssn4 }}", 11 "cc": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card }}", 12 "cc_exp": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card_exp }}", 13 "cc_cvc": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card_cvc }}" 14 }'
Footprint detokenizes each field in the body inside the vault enclave, then sends the updated plaintext body to the target (here https://payments.acmebank.com) with the headers 'Custom-Header: custom value' and 'Content-type: application/json'.
Control headers change how the upstream request behaves:
| Header | Required? | Function |
|---|---|---|
x-fp-proxy-fwd-<HEADER> | Optional | Sends a header named <HEADER> with the corresponding value |
x-fp-proxy-method | Optional | Selects the HTTP method (defaults to POST) |
x-fp-proxy-access-reason | Optional | The decryption reason recorded in the access and security logs during detokenization. Defaults to no reason. |
Token body template format
Vault Proxy replaces every token in the request body with the decrypted plaintext value. A token starts with {{ and ends with }}, and you can write it in one of two forms.
Fully-qualified tokens. Write {{ <footprint_user_token>.<data_identifier> }}, where <footprint_user_token> is the user's fp_id and <data_identifier> is any identity (KYC) field, prefixed id., or any custom field you defined, prefixed custom..
| Fully-qualified format |
|---|
{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.last_name }} |
{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.ssn9 }} |
{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.dob }} |
{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card }} |
Inferred-user tokens. When every token refers to one user's vault, name that user in a header instead:
| Header | Value |
|---|---|
x-fp-id | The fp_id of the user the tokens refer to |
Each token then omits the <footprint_user_token> part and becomes {{ <data_identifier> }}.
| Inferred-user format |
|---|
{{ id.last_name }} |
{{ id.ssn9 }} |
{{ id.dob }} |
{{ custom.credit_card }} |
Filter functions
Filter functions transform a value on its way into or out of the vault, as in template languages such as Jinja and Handlebars. Append zero or more filters to a token, separated by |.
| Filter function | Description |
|---|---|
to_lowercase | Converts the target UTF-8 string to lowercase |
to_uppercase | Converts the target UTF-8 string to uppercase |
to_ascii | Converts the target UTF-8 string to ASCII characters only |
prefix(n) | Returns the first n characters of the target UTF-8 string, where n is a positive integer |
suffix(n) | Returns the last n characters of the target UTF-8 string, where n is a positive integer |
replace(from,to) | Replaces every match of the string from with the string to in the target UTF-8 string |
date_format(from_format,to_format) | Parses the target string in date format from_format and converts it to date format to_format. Supports strftime format strings. Errors if the target string cannot be coded in the supplied formats |
hmac_sha256(key) | HMAC-SHA256, where the key argument is parsed as a hex-encoded string and the output is hex-encoded |
encrypt(algorithm, public_key) | Asymmetrically encrypts the value to a public key. algorithm is rsa_pkcs1v15 or ecies_p256_x963_sha256_aes_gcm, and public_key is a hex-encoded DER-formatted public key. RSA public keys must be in PKCS#8 format |
" or ' quotes.
For example:
{{ id.last_name | to_ascii | to_uppercase }}convertsDoètoDOE.{{ id.dob | date_format("%Y-%m-%d", "%A in %B of %y") }}converts1988-12-30toFriday in December of 88.{{ custom.ach_account| replace("-", "") }}converts12-1212-1212to1212121212.
Ingress vaulting
Ingress vaulting rules pick fields out of the destination's response and vault them, so the proxy tokenizes incoming data as well as detokenizing outgoing data.
You can define all of your ingress rules in the dashboard or through the admin API and invoke the configuration by its id at api.onefootprint.com/vault_proxy/{proxy_id}.
A saved configuration does not say which user's vault to write to, so a request that uses its ingress rules must carry this header:
| Header | Format | Value |
|---|---|---|
x-fp-id | <fp_id> | The user whose vault receives the values |
For example, send the header 'x-fp-id: fp_id_tctecBEvGc98V7Vx4MhZU' when the configuration's rule is custom.credit_card_number=$.data.card.number.
Just-in-time ingress vaulting
Without a saved configuration, pass each rule as an x-fp-proxy-ingress-rule header in the form <footprint_token_id>.custom.<property_name>=<path-to-value>, where the path syntax depends on the response content type. When one or more x-fp-proxy-ingress-rule headers are present, you must also send a single x-fp-proxy-ingress-content-type header. The left side of a rule uses the same token format as the body, without the {{ and }} delimiters.
JSONPath
Ingress supports JSON responses only, with JSONPath to select the value. This rule vaults a card number from the response:
bash1-H 'x-fp-proxy-ingress-rule: fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card_number=$.data.card.number'
Given the response below, it extracts 4242424242424 and vaults it as custom.credit_card_number in the vault of fp_id_tctecBEvGc98V7Vx4MhZU:
json1{ 2 "data": { 3 "card": { 4 "number": "4242424242424", 5 "expiration": { 6 "month": "04", 7 "year": "25" 8 } 9 }, 10 "processor": "amex" 11 } 12}
| More examples |
|---|
fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.number=$.data.card.number |
fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.exp_month=$.data.card.expiration_month |
fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.exp_year=$.data.card.expiration_year |
fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.cvc=$.data.card.security_code |
To reformat a value before it is vaulted, append filter functions to the right of the JSONPath selector:
<fp_id>.<data_identifier> = <json_path> | <filter_function> | <filter_function> | ...
For example, given this response body:
json1{ 2 "card": { 3 "number": "4242-4242-4242-4242" 4 } 5}
this rule strips the dashes before vaulting:
fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.number = $.card.number | replace("-", "")
The result is fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.number with the value 4242424242424242.
Reflection
The reflect endpoint runs the token substitution and returns the result to you instead of forwarding it to a destination: the "first hop" of the proxy on its own. Use it to decrypt data into a complex object or to test your Vault Proxy configurations.
For example, this request returns a sentence with two vault fields filled in:
bash1curl https://api.onefootprint.com/vault_proxy/reflect \ 2 -u sk_test_0Te2YtSveZpWLMjQgNRkCv6siiC86iMIkZ: \ 3 -H 'x-fp-id: fp_id_JHSfbHz7VdxfoPXuaOlZqb' \ 4 --data \ 5 'The name on my credit card is {{ card.primary.name | to_ascii | to_uppercase }}. I was born on a {{ id.dob | date_format("%Y-%m-%d", "%A in %B of %y") }}.' 6 7The name on my credit card is JANE DOE. I was born on a Friday in December of 88.
Send a POST to /vault_proxy/reflect with the body written in proxy token syntax, filter functions included. Add an x-fp-id header to use inferred-user tokens and omit the fp_id from each token.
Advanced proxy settings
Vault Proxy also supports mTLS (client certificates), certificate pinning (custom server certificates), and vaulting selected parts of the response. The sections below configure them with just-in-time headers. We recommend saving them in the dashboard instead: it is simpler and more secure, because you do not manage authentication secrets for the destination service yourself.
Client certificate authentication (mTLS)
To connect to the destination with mutual TLS (mTLS), supply a client certificate and key. Only a single certificate and key pair is accepted. Encode both as PEM, then percent-encode them.
Service certificate pinning and root CAs
Pinning is optional. To enable it, supply one or more server certificates or root CAs to validate the destination's server certificate against. When you supply several, the connection succeeds if any one of them validates the incoming certificate chain. Encode each as PEM, then percent-encode it.
Header and configuration reference
Every header the proxy accepts, for configuring and invoking it just-in-time. Values stored in a saved configuration do not need to be sent on each request.
| Header | Usage | Description |
|---|---|---|
x-fp-proxy-target-url | Required for just-in-time | The HTTPS URL Footprint routes the request to |
x-fp-proxy-method | Optional | Selects the HTTP method (defaults to POST). Overrides the configuration's value if present |
x-fp-proxy-fwd-<HEADER> | Optional. Multiple allowed | Sends a header named <HEADER> with the corresponding value |
x-fp-path-and-query | Optional | Adds a path and/or query parameters to the proxy target URL |
x-fp-id | Optional | The user the tokens refer to. Required when tokens in the body omit the fp_id_ prefix |
x-fp-proxy-access-reason | Optional | The decryption reason recorded in the access and security logs during detokenization. Defaults to no reason |
x-fp-proxy-client-cert | Optional. Percent-encoded PEM | The client certificate to use |
x-fp-proxy-client-key | Optional. Percent-encoded PEM | The client certificate private key to use |
x-fp-proxy-pin-cert | Optional. Percent-encoded PEM. Multiple allowed | A root CA certificate or self-signed certificate to validate the server's certificate against |
x-fp-proxy-ingress-content-type | Optional | The content type of the response to process ingress rules on. Only json is supported today; regex and xml are planned |
x-fp-proxy-ingress-rule | Optional. <token>=<path>. Multiple allowed | An ingress rule: the namespaced token to vault and the path of its value in the response |
Playground
Footprint's ditto server echoes the headers and body of any request sent to https://ditto.footprint.dev, so you can test a proxy configuration against it.
Basic usage
A direct request shows what ditto echoes:
bash1$ curl -i -X POST https://ditto.footprint.dev -H 'Test-Header: FootprintRocks' --data '{"hello": "world" }' 2 3test-header: FootprintRocks 4content-type: application/x-www-form-urlencoded 5 6{"hello": "world" }
Set https://ditto.footprint.dev as the proxy target to check that decryption and token templating produce the body you expect.
Testing with client certificates
To test mTLS with a client certificate, send the request to https://ditto.footprint.dev:8443 (port 8443). The server certificate is self-signed, so either trust it or pin it in the configuration. With a client certificate, the echo includes its serial:
bash1$ curl --cert client.crt --key client.key -i -k -X POST https://ditto.footprint.dev:8443 -H 'Test-Header: FootprintRocks' --data '{"hello": "world" }' 2 3x-ditto-client-cert-serial: 12431179266346922388 4test-header: FootprintRocks 5content-type: application/x-www-form-urlencoded 6 7{"hello": "world" }
When a client certificate is used, ditto returns its serial in the x-ditto-client-cert-serial header.