VGS Developer's Guide

April 14, 2021

An overview of VGS developer tools and an example integration guide.

The VGS Dashboard’s route editing UI provides a good starting point for setting up your routes, but as the config grows in complexity, we recommend integrating the VGS route into your project’s codebase. Luckily, all VGS routes are available in YAML format and can be stored in your project's version control system.

In this guide we will go through the process integrating VGS routes files into an existing dockerized application, and using existing VGS developer tools to test/deploy changes to your VGS vault in parallel with changes to the main application.


IMPORTANT

This guide assumes that you have already created your VGS account, and have completed the initial setup guides with Payment Processing - Pay Facilitation specified as a primary use case.


Structure

In this guide, we’re going to cover:

  1. How to install and set up vgs-cli and vgs-satellite;
  2. How to download your VGS vault’s routes and test them locally;
  3. How to integrate your VGS routes' changes into your existing CI/CD pipeline;

Full code for these examples can be found on our github.

Setup

Both vgs-cli and vgs-satellite are available as docker images, so I’m going to focus on the method of running these tools through docker-compose. If you’d like to install and run these apps manually, please follow the links to their respective pages.

VGS CLI

VGS CLI is a developer tool that helps you build, test, and manage your configurations in VGS from a terminal. It enables programmatic usage of VGS APIs in code or as a part of CI/CD pipelines.

Let’s start by creating a docker-compose.yaml file and adding our vgs-cli service:

version: '3.2'
services:
  vgs-cli:
    image: quay.io/verygoodsecurity/vgs-cli:${VERSION:-latest}
    command: bash
    stdin_open: true # docker run -i
    tty: true # docker run -t
    volumes:
      - ./:/data/
    ports:
      - '7745:7745'
      - '8390:8390'
      - '9056:9056'

The first half of this configuration is pretty straightforward - we are specifying the image to pull from VGS’s quay repository, which command to run upon start, and enabling interactive shell with stdin_open and tty flags.

In the volumes section we are mapping the current folder to container’s /data folder, enabling us to work with VGS vault’s routes file locally. ports section maps container’s ports to your machine’s ports, for CLI to work correctly.

Now, we can start the CLI container, and authenticate our CLI:

$ docker-compose up -d vgs-cli

Creating network "git-cli-satellite-example_default" with the default driver
Creating git-cli-satellite-example_vgs-cli_1 ... done

$ docker-compose exec vgs-cli vgs login

Can not open your browser, please open this URL in your browser: https://auth.verygoodsecurity.com/auth...

After the second command, you’ll be prompted to follow the link to verify your VGS account. You may also be asked for permission to access your OS key management system (Mac OS Keychain, Linux Secret Service, Windows Credential Vault). VGS CLI uses it for the secure storage of keys used for encryption of your access tokens.

To download your VGS vault’s routes, simply run the following command with your vault id:

docker-compose exec vgs-cli vgs get routes --vault tntfpb2cmsw > ./vgs_routes.yaml

As a result, you should see that your routes file has been downloaded to your local folder:

.
 |-docker-compose.yaml
 |-vgs_routes.yaml

VGS Satellite

VGS Satellite is a developer tool to help you build, test, and verify your integration with VGS directly from your local machine. It comes in 2 flavours: a desktop app, and a docker image. For the purposes of this guide, we’re going to use the latter.

Add the following snippet to your docker-compose.yaml file:

vgs-satellite:
  image: verygood/satellite:${VERSION:-latest}
  ports:
    - 8089:8089
    - 9098:9098
    - 9099:9099
  volumes:
    - $HOME/.vgs-satellite/:/data
    - ./:/routes
  environment:
    # path to routes file that will be loaded upon container start
    SATELLITE_ROUTES_PATH: /routes/vgs_routes.yaml

Again, we’re mapping your current directory to image’s /routes folder, and creating a folder in your $HOME directory to store satellite’s data. SATELLITE_ROUTES_PATH variable contains the location of the routes file within the container, so that it is applied to satellite’s local vault.

We can now start the satellite container by running:

$ docker-compose up -d vgs-satellite

Starting git-cli-satellite-example_vgs-satellite_1 ... done

And test that it’s working by sending a request to it:

$ curl http://localhost:9098/post \
  -H "Content-type: application/json" \
  -d '{"card_number": "4111111111111111", "card_cvc": "1111", "card_exp": "01/01/2025"}'

{
  "args": {},
  "data": "{\"card_number\": \"tok_sat_PzsyygDud8LQWY3pmvvZUb\", \"card_cvc\": \"tok_sat_Ng3mU2pmVn58oJmgq23r9A\", \"card_exp\": \"01/01/2025\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Connection": "close",
    "Content-Length": "121",
    "Content-Type": "application/json",
    "Host": "echo.apps.verygood.systems",
    "User-Agent": "curl/7.64.1"
  },
  "json": {
    "card_cvc": "tok_sat_Ng3mU2pmVn58oJmgq23r9A",
    "card_exp": "01/01/2025",
    "card_number": "tok_sat_PzsyygDud8LQWY3pmvvZUb"
  },
  "origin": "185.237.74.238, 10.22.116.69",
  "url": "https://echo.apps.verygood.systems/post"
}

Working with routes

Now, let’s say, that we would like to change the format of our credit card number alias format from VGS Alias to Format Preserving, Luhn Valid (6T4), so that BIN and the last four digits are preserved (e.g. 4111111111111111 becomes something like 4111119381251111). In order to accomplish this, we would need to:

  1. Apply changes to our local routes file;
  2. Test updated routes locally;
  3. Apply updated routes to our vault;
  4. Check the vault’s logs to see if everything is working correctly;

Updating the routes

Let’s start by updating the routes. We need to find the portions where card_number is being redacted and revealed in inbound and outbound routes, and change the public_token_generator in both those locations from UUID to FPE_SIX_T_FOUR. You can check our all of the available VGS aliasing formats on our docs.

data:
- attributes:
    created_at: '2021-02-11T13:22:05'
    destination_override_endpoint: https://echo.apps.verygood.systems
    entries:
      ...
      operation: REDACT # <- remove sensitive data
      operations: null
      phase: REQUEST
      public_token_generator: UUID # <- aliasing format, change this to 'FPE_SIX_T_FOUR'
      targets:
      - body
      token_manager: PERSISTENT
      transformer: JSON_PATH
      transformer_config:
      - $.card_number # <- location of sensitive data within payload
      ...
- attributes:
    created_at: '2021-02-11T13:25:02'
    destination_override_endpoint: '*'
    entries:
      ...
      operation: ENRICH # <- restore sensitive data
      operations: null
      phase: REQUEST
      public_token_generator: UUID # <- aliasing format, change this to 'FPE_SIX_T_FOUR'
      targets:
      - body
      token_manager: PERSISTENT
      transformer: JSON_PATH
      transformer_config:
      - $.card_number # <- location of sensitive data within payload
      ...

Testing the routes

Before we can test these changes, we have to restart vgs-satellite container:

$ docker-compose kill vgs-satellite

Killing git-cli-satellite-example_vgs-satellite_1 ... done

$ docker-compose up -d vgs-satellite

Starting git-cli-satellite-example_vgs-satellite_1 ... done

Now, send a test request:

$ curl http://localhost:9098/post \
  -H "Content-type: application/json" \
  -d '{"card_number": "4111111111111111", "card_cvc": "1111", "card_exp": "01/01/2025"}'

{
  "args": {},
  "data": "{\"card_number\": \"4111115400611111\", \"card_cvc\": \"tok_sat_Ng3mU2pmVn58oJmgq23r9A\", \"card_exp\": \"01/01/2025\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Connection": "close",
    "Content-Length": "107",
    "Content-Type": "application/json",
    "Host": "echo.apps.verygood.systems",
    "User-Agent": "curl/7.64.1"
  },
  "json": {
    "card_cvc": "tok_sat_Ng3mU2pmVn58oJmgq23r9A",
    "card_exp": "01/01/2025",
    "card_number": "4111115400611111"
  },
  "origin": "185.237.74.238, 10.22.113.146",
  "url": "https://echo.apps.verygood.systems/post"
}

As you can see, card_number still passes as a valid credit card number, although different from the one we sent.

Okay, now, let’s automate the testing process by creating some simple Python tests route_tests.py that will send requests to your local vgs-satellite, and verify that the payload has been redacted/revealed:

import os
import re
import urllib3

import requests

class TestVgsRoutes:
    reverse_proxy_port = '9098'
    forward_proxy_port = '9099'
    satellite_url = os.environ.get('VGS_SATELLITE_URL', 'http://localhost')
    headers = {
        'Content-type': 'application/json',
    }
    data = {
        'card_number': '4111111111111111',
        'card_cvc': '1111',
        'card_exp': '01/01/2025'
    }

    def test_both_routes(self) -> None:
        """
        Test that the inbound data is being redacted and conforms to the specified alias format.
        Then, test that the outbound data is being revealed.
        """
        # https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
        urllib3.disable_warnings()

        inbound_response = requests.post(f'{self.satellite_url}:{self.reverse_proxy_port}/post',
                                         headers=self.headers,
                                         json=self.data)
        inbound_json = inbound_response.json()['json']

        assert inbound_json['card_cvc'] != self.data['card_cvc']
        assert inbound_json['card_number'] != self.data['card_number']

        assert re.findall(r'tok_sat_[\w\d]+', inbound_json['card_cvc'])
        assert re.findall(r'^4[0-9]{12}(?:[0-9]{3})?$', inbound_json['card_number'])

        proxies = {
            'http': f'{self.satellite_url}:{self.forward_proxy_port}',
            'https': f'{self.satellite_url}:{self.forward_proxy_port}'
        }
        outbound_response = requests.post('https://echo.apps.verygood.systems/post',
                                 headers=self.headers,
                                 json=inbound_json,
                                 proxies=proxies,
                                 verify=False)
        outbound_json = outbound_response.json()['json']

        assert outbound_json['card_cvc'] == self.data['card_cvc']
        assert outbound_json['card_number'] == self.data['card_number']

To keep everything consistent, I will wrap our tests in another docker container. For that, we need to create a requirements.txt file:

pytest == 6.2.2
requests == 2.25.1

Add a dockerfile for building our tests image:

FROM python:3.9.1-slim-buster

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . /app

CMD ["pytest", "./route_tests.py"]

As a final touch, add the following code snippet to our docker-compose.yaml:

vgs-tests:
  build:
    context: .
    dockerfile: Dockerfile
  volumes:
    - .:/app/
  environment:
    VGS_SATELLITE_URL: http://vgs-satellite

Now, we can run our tests simply by invoking

$ docker-compose run vgs-tests

Creating git-cli-satellite-example_vgs-tests_run ... done
==================================== test session starts ==========================
platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /app
collected 1 item

route_tests.py .                                                             [100%]

===================================== 1 passed in 1.41s ===========================

If everything ran successfully, we are finally ready to apply our updated routes to our VGS vault.

Applying the changes to VGS vault

To apply the newly created routes to our vault, make sure that the VGS Cli is running and authenticated. Then, use the vgs apply command:

$ docker-compose up -d vgs-cli

Starting git-cli-satellite-example_vgs-cli_1 ... done

$ docker-compose exec vgs-cli vgs login

Can not open your browser, please open this URL in your browser: https://auth.verygoodsecurity.com/....

$ docker-compose exec vgs-cli vgs apply routes --vault tntfpb2cmsw -f /data/vgs_routes.yaml

Route 4a74170c-2638-45c8-8880-1ea3cf66f38a processed
Route 6d2135c7-f20b-46ba-bee6-e58159c42e6d processed
Routes updated successfully for vault tntfpb2cmsw

Now, we can send a couple of request to our vault, and check the access logs to see if everything worked correctly:

$ docker-compose exec vgs-cli vgs logs access --vault tntfpb2cmsw --tail 10

data:
- attributes:
    data_environment: sandbox
    expired_at: null
    http:
      method: POST
    occurred_at: '2021-02-18T12:50:56.062000+00:00'
    path: https://echo.apps.verygood.systems:443/post
    pinned: false
    protocol: http
    proxy_status: '200'
    route_type: reverse
    routes:
      data:
      - attributes:
          matched: true
          route_id: 4a74170c-2638-45c8-8880-1ea3cf66f38a
          trace_id: 2373598dce4f37d3e0c77aa7247b5f51
        id: 70ffd363-88fe-4d55-9fee-1c7b1318e94d
        type: routes
    source: ''
    tenant_id: tntfpb2cmsw
    trace_id: 2373598dce4f37d3e0c77aa7247b5f51
    upstream: echo.apps.verygood.systems:443
    upstream_status: '200'
    upstream_time_ms: 13
    user_id: ''
  id: 2373598dce4f37d3e0c77aa7247b5f51
  links:
    self: /access-logs/2373598dce4f37d3e0c77aa7247b5f51
  type: access-log
- attributes:
    data_environment: sandbox
    expired_at: null
    http:
      method: POST
    occurred_at: '2021-02-18T12:52:33.750000+00:00'
    path: https://echo.apps.verygood.systems:443/post
    pinned: false
    protocol: http
    proxy_status: '200'
    route_type: forward
    routes:
      data:
      - attributes:
          matched: true
          route_id: 6d2135c7-f20b-46ba-bee6-e58159c42e6d
          trace_id: 37cb2fb6999d1012314592d696718d97
        id: e751936f-af14-45af-be70-13e7abc3d36b
        type: routes
    source: ''
    tenant_id: tntfpb2cmsw
    trace_id: 37cb2fb6999d1012314592d696718d97
    upstream: echo.apps.verygood.systems:443
    upstream_status: '200'
    upstream_time_ms: 5
    user_id: USx6vrWo4tKMwPy25r3hYwfn
  id: 37cb2fb6999d1012314592d696718d97
  links:
    self: /access-logs/37cb2fb6999d1012314592d696718d97
  type: access-log
version: 1

VGS CLI Login Automation

If you’d like to remove the need to run vgs login and authenticate every time you work with the CLI, there’s a way to enable auto login using client credentials. Once you have received your credentials, all you have to do is add those to your docker-compose.yaml file:

vgs-cli:
  image: quay.io/verygoodsecurity/vgs-cli:${VERSION:-latest}
  stdin_open: true # docker run -i
  tty: true # docker run -t
  volumes:
    - ./:/data/
  # environment:
  # VGS_CLIENT_ID: example
  # VGS_CLIENT_SECRET: example
  command: bash
  ports:
    - '7745:7745'
    - '8390:8390'
    - '9056:9056'

Full code for these examples can be found on our github.

References

Vitalii Liashchenko Vitalii Liashchenko

Jack of all trades, master of some at VGS.

Share

You Might also be interested in...

VGS at RSA The Future of Cryptocurrency

VGS at RSA: The Future of Cryptocurrency

Kenneth Geers, PhD
Kathy Wang
April 15, 2021

kk

How Stilt Accelerated the Debit Card Issuance Process

Ryan O'Leary April 8, 2021

Designing Android Apps with Accessibility in Mind teaser image

Designing Android Apps with Accessibility in Mind

Dmytro Dmytryshyn April 5, 2021