├── .gitignore
├── README.md
├── dynamic-secrets-k8s
├── README.md
├── config
│ ├── postgres.yml
│ ├── web-policy.hcl
│ └── web.yml
├── configure_vault.sh
├── images
│ ├── 1_deploydb.svg
│ ├── 2_createrole.svg
│ ├── 3_createconnection.svg
│ ├── 4_rotatecreds.svg
│ ├── 5_authentication.svg
│ ├── 6_policy.svg
│ ├── 7_secrets.svg
│ ├── install.svg
│ ├── vault-db.png
│ ├── vault-k8s-auth.png
│ ├── vault-policy-workflow-1.png
│ ├── vault-policy-workflow-1.svg
│ ├── vault-policy-workflow-2.png
│ ├── vault-policy-workflow-2.svg
│ ├── vault-policy-workflow-3.png
│ └── vault-workflow-illustration-policy.png
└── setup.sh
├── static_secrets
├── README.md
├── config
│ ├── web-policy.hcl
│ └── web.yml
├── configure_vault.sh
└── setup.sh
└── transform
├── Makefile
├── README.md
├── blueprint
├── README.md
├── app.hcl
├── docs.hcl
├── docs
│ ├── images
│ │ ├── api.png
│ │ └── card.png
│ └── index.md
├── files
│ ├── agent_config.hcl
│ ├── db_setup.sql
│ ├── setup_approle.sh
│ └── setup_transform.sh
├── network.hcl
├── postgres.hcl
├── secrets
│ ├── role_id
│ └── secret_id
└── vault.hcl
├── transform-engine-go
├── .realize.yaml
├── Dockerfile
├── bin
│ └── server
├── data
│ └── postgres.go
├── go.mod
├── go.sum
├── handlers
│ ├── health.go
│ └── payment.go
├── main.go
└── vault
│ └── vault.go
└── transform-engine-java
├── .classpath
├── .project
├── .settings
├── org.eclipse.buildship.core.prefs
└── org.eclipse.jdt.core.prefs
├── Dockerfile
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
└── main
├── java
└── payments
│ ├── Application.java
│ ├── HealthResponse.java
│ ├── PaymentRequest.java
│ ├── PaymentResponse.java
│ ├── PaymentsController.java
│ ├── model
│ └── Order.java
│ ├── repository
│ └── OrderRepository.java
│ └── vault
│ ├── TokenRequest.java
│ ├── TokenResponse.java
│ └── VaultClient.java
└── resources
└── application.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | ca.crt
2 | .git
3 | vscode.code-workspace
4 | transform/transform-engine-java/build
5 | transform/transform-engine-java/bin
6 | transform/transform-engine-java/.gradle
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HashiCorp Vault Demos
2 |
3 | Examples and demos for HashiCorp Vault
4 |
5 | ## [Dynamic Database Secrets with Kubernetes](./dynamic-secrets-k8s/)
6 |
7 | This demo shows how to dynamically generate PostgreSQL credentials using Vault and how to inject them into a pod using the Kubnernetes integration
8 |
9 | [https://www.vaultproject.io/docs/platform/k8s/injector/index.html](https://www.vaultproject.io/docs/platform/k8s/injector/index.html)
10 |
11 | ## [Static Secrets with Kubernetes](./static-secrets-k8s/)
12 |
13 | This demo shows how to use and inject Static Vault secrets into a Kubernetes pod
14 |
15 | [https://www.vaultproject.io/docs/platform/k8s/injector/index.html](https://www.vaultproject.io/docs/platform/k8s/injector/index.html)
16 |
17 | ## [Format Preserving Encryption with the Transform Secrets Engine](./transform/)
18 |
19 | This demo shows how to encrypt data while preserving data formatting using the Transform secrets engine
20 |
21 | [https://www.vaultproject.io/docs/secrets/transform](https://www.vaultproject.io/docs/secrets/transform)
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/config/postgres.yml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: postgres
6 | labels:
7 | app: postgres
8 | spec:
9 | type: ClusterIP
10 | ports:
11 | - port: 5432
12 | targetPort: 5432
13 | selector:
14 | app: postgres
15 |
16 | ---
17 | apiVersion: apps/v1
18 | kind: Deployment
19 | metadata:
20 | name: postgres
21 | spec:
22 | replicas: 1
23 | selector:
24 | matchLabels:
25 | service: postgres
26 | app: postgres
27 | template:
28 | metadata:
29 | labels:
30 | service: postgres
31 | app: postgres
32 | spec:
33 | containers:
34 | - name: postgres
35 | image: hashicorpdemoapp/postgres:11.6
36 | ports:
37 | - containerPort: 5432
38 | env:
39 | - name: POSTGRES_DB
40 | value: wizard
41 | - name: POSTGRES_USER
42 | value: postgres
43 | - name: POSTGRES_PASSWORD
44 | value: password
45 | volumeMounts:
46 | - mountPath: "/var/lib/postgresql/data"
47 | name: "pgdata"
48 | volumes:
49 | - name: pgdata
50 | emptyDir: {}
51 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/config/web-policy.hcl:
--------------------------------------------------------------------------------
1 | path "database/creds/db-app" {
2 | capabilities = ["read"]
3 | }
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/config/web.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Service to expose web frontend
3 |
4 | apiVersion: v1
5 | kind: Service
6 | metadata:
7 | name: web-service
8 | spec:
9 | selector:
10 | app: web
11 | ports:
12 | - name: http
13 | protocol: TCP
14 | port: 9090
15 | targetPort: 9090
16 |
17 | ---
18 | # Service account to allow pod access to Vault via K8s auth
19 |
20 | apiVersion: v1
21 | kind: ServiceAccount
22 | metadata:
23 | name: web
24 | automountServiceAccountToken: true
25 |
26 | ---
27 | # Web frontend
28 |
29 | apiVersion: apps/v1
30 | kind: Deployment
31 | metadata:
32 | name: web-deployment
33 | labels:
34 | app: web
35 | spec:
36 | replicas: 2
37 | selector:
38 | matchLabels:
39 | app: web
40 | template:
41 | metadata:
42 | labels:
43 | app: web
44 | annotations:
45 | vault.hashicorp.com/agent-inject: "true"
46 | vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/db-app"
47 | vault.hashicorp.com/agent-inject-template-db-creds: |
48 | {
49 | {{ with secret "database/creds/db-app" -}}
50 | "db_connection": "host=postgres port=5432 user={{ .Data.username }} password={{ .Data.password }} dbname=wizard sslmode=disable"
51 | {{- end }}
52 | }
53 | vault.hashicorp.com/role: "web"
54 | spec:
55 | serviceAccountName: web
56 | containers:
57 | - name: web
58 | image: hashicorpdemoapp/product-api:v0.0.4
59 | ports:
60 | - containerPort: 9090
61 | env:
62 | - name: "CONFIG_FILE"
63 | value: "/vault/secrets/db-creds"
64 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/configure_vault.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Enable and configure Kubernetes Authentication
4 | vault auth enable kubernetes
5 |
6 | kubectl exec $(kubectl get pods --selector "app.kubernetes.io/instance=vault,component=server" -o jsonpath="{.items[0].metadata.name}") -c vault -- \
7 | sh -c ' \
8 | vault write auth/kubernetes/config \
9 | token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
10 | kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
11 | kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
12 |
13 | # Enable and configure PostgresSQL Dynamic secrets
14 | vault secrets enable database
15 |
16 | vault write database/config/wizard \
17 | plugin_name=postgresql-database-plugin \
18 | verify_connection=false \
19 | allowed_roles="*" \
20 | connection_url="postgresql://{{username}}:{{password}}@postgres:5432/wizard?sslmode=disable" \
21 | username="postgres" \
22 | password="password"
23 |
24 | # Rotate the database root password
25 | vault write --force database/rotate-root/wizard
26 |
27 | # Create a role allowing credentials to be created with access for all tables in the DB
28 | vault write database/roles/db-app \
29 | db_name=wizard \
30 | creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
31 | GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
32 | revocation_statements="ALTER ROLE \"{{name}}\" NOLOGIN;"\
33 | default_ttl="1h" \
34 | max_ttl="24h"
35 |
36 | # Write the policy to allow read access to the role
37 | vault policy write web-dynamic ./config/web-policy.hcl
38 |
39 | # Assign the policy to users who authenticate with Kubernetes service accounts called web
40 | vault write auth/kubernetes/role/web \
41 | bound_service_account_names=web \
42 | bound_service_account_namespaces=default \
43 | policies=web-dynamic \
44 | ttl=1h
45 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/3_createconnection.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/4_rotatecreds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
76 |
99 |
100 |
101 |
102 |
103 |
104 | demo-vault-kubernetes/dynamic-secrets-k8s on master [✘!?] via 🐹 v1.13.1 at ☸ default➜ ➜ c➜ cl➜ cle➜ clea➜ clear➜ clear➜ clear➜ vault write --force /database/rotate-root/wizard ➜ vault write --force /database/rotate-root/wizard➜ vault write --force /database/rotate-root/wizard Success! Data written to: database/rotate-root/wizard➜ kubectl exec -it $(kubectl get pods --selector "app=postgres" -o jsonpath="{.items[0].metadata.name}") -c postgres -- bash -c 'PGPASSWORD=password psql -U postgres' ➜ kubectl exec -it $(kubectl get pods --selector "app=postgres" -o jsonpath="{.items[0].metadata.name}") -c postgres -- bash -c 'PGPASSWORD=password psql -U postgres'") -c postgres -- bash -c 'PGPASSWORD=password psql -U postgres'psql: FATAL: password authentication failed for user "postgres"command terminated with exit code 2➜ ➜ vault read database/creds/db-app ➜ vault read database/creds/db-app➜ vault read database/creds/db-app Key Value--- -----lease_id database/creds/db-app/CpglqjGSr6CW5VOripx78rUvlease_duration 1hlease_renewable truepassword A1a-CluAj9zv6fr2zA3Susername v-token-db-app-y72kjuPuv4Gp4XP74zZd-1576779550➜
105 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/5_authentication.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
73 |
96 |
97 |
98 |
99 |
100 |
101 | demo-vault-kubernetes/dynamic-secrets-k8s on master [✘!?] via 🐹 v1.13.1 at ☸ default➜ ➜ c➜ cl➜ cle➜ clea➜ clear➜ clear➜ clear➜ vault auth enable kubernetes ➜ vault auth enable kubernetes➜ vault auth enable kubernetes Success! Enabled kubernetes auth method at: kubernetes/➜ kubectl exec $(kubectl get pods --selector "app.kubernetes.io/instance=vault,component=server" -o jsonpath="{.items[0].metadata.name}") -c vault -- \ sh -c ' \ vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' ➜ kubectl exec $(kubectl get pods --selector "app.kubernetes.io/instance=vault,component=server" -o jsonpath="{.items[0].metadata.name}") -c vault -- \ sh -c ' \ vault write auth/kubernetes/config \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \ kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'➜ kubectl exec $(kubectl get pods --selector "app.kubernetes.io/instance=vault,component=server" -o jsonpath="{.items[0].metadata.name}") -c vault -- \ sh -c ' \ vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'Success! Data written to: auth/kubernetes/config➜
102 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/6_policy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
68 |
91 |
92 |
93 |
94 |
95 |
96 | demo-vault-kubernetes/dynamic-secrets-k8s on master [✘!?] via 🐹 v1.13.1 at ☸ default➜ ➜ c➜ cl➜ cle➜ clea➜ clear➜ clear➜ vault policy write web ./config/web-policy.hcl ➜ vault policy write web ./config/web-policy.hcl➜ vault policy write web ./config/web-policy.hclSuccess! Uploaded policy: web➜ vault write auth/kubernetes/role/web \ bound_service_account_names=web \ bound_service_account_namespaces=default \ policies=web \ ttl=1h ➜ vault write auth/kubernetes/role/web \ bound_service_account_names=web \ bound_service_account_namespaces=default \ policies=web \ ttl=1h ttl=1h Success! Data written to: auth/kubernetes/role/web➜
97 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/dynamic-secrets-k8s/images/vault-db.png
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-k8s-auth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/dynamic-secrets-k8s/images/vault-k8s-auth.png
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-policy-workflow-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/dynamic-secrets-k8s/images/vault-policy-workflow-1.png
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-policy-workflow-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/dynamic-secrets-k8s/images/vault-policy-workflow-2.png
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-policy-workflow-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-policy-workflow-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/dynamic-secrets-k8s/images/vault-policy-workflow-3.png
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/images/vault-workflow-illustration-policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/dynamic-secrets-k8s/images/vault-workflow-illustration-policy.png
--------------------------------------------------------------------------------
/dynamic-secrets-k8s/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 | echo "##################################################"
3 | echo "Install shipyard if you do not have it installed"
4 | echo "curl https://shipyard.demo.gs/install.sh | bash"
5 | echo "##################################################"
6 |
7 | yard up --enable-consul false
--------------------------------------------------------------------------------
/static_secrets/README.md:
--------------------------------------------------------------------------------
1 | # Static Secrets with Vault and Kubernetes
2 |
3 | README coming soon,
4 |
5 | ## Enable and configure Kubernetes Authentication
6 |
7 | ```shell
8 | vault auth enable kubernetes
9 |
10 | kubectl exec $(kubectl get pods --selector "app.kubernetes.io/instance=vault,component=server" -o jsonpath="{.items[0].metadata.name}") -c vault -- \
11 | sh -c ' \
12 | vault write auth/kubernetes/config \
13 | token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
14 | kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
15 | kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
16 | ```
17 |
18 | ## Write a secret to the Vault
19 |
20 | ```shell
21 | vault kv put secret/web api_key=abc123fd
22 | ```
23 |
24 | ## Write policy allowing read access to that secret
25 |
26 | ```shell
27 | vault policy write web-static ./config/web-policy.hcl
28 | ```
29 |
30 | ## Assign the policy to users who authenticate with Kubernetes service accounts called web
31 |
32 | ```shell
33 | vault write auth/kubernetes/role/web \
34 | bound_service_account_names=web \
35 | bound_service_account_namespaces=default \
36 | policies=web-static \
37 | ttl=1h
38 | ```
39 |
40 | ## Apply the Config with annotations to inject secrets
41 |
42 | ```yaml
43 | annotations:
44 | vault.hashicorp.com/agent-inject: "true"
45 | vault.hashicorp.com/agent-inject-secret-web: secret/data/web
46 | vault.hashicorp.com/agent-inject-template-web: |
47 | {
48 | {{ with secret "secret/data/web" -}}
49 | "api_key": "{{ .Data.data.api_key }}"
50 | {{- end }}
51 | }
52 | vault.hashicorp.com/role: "web"
53 | ```
54 |
55 | ## Check the result
56 |
57 | ```shell
58 | kubectl exec -it $(kubectl get pods --selector "app=web" -o jsonpath="{.items[0].metadata.name}") -c web -- cat /vault/secrets/web
59 | ```
--------------------------------------------------------------------------------
/static_secrets/config/web-policy.hcl:
--------------------------------------------------------------------------------
1 | path "secret/data/web" {
2 | capabilities = ["read"]
3 | }
--------------------------------------------------------------------------------
/static_secrets/config/web.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Service to expose web frontend
3 |
4 | apiVersion: v1
5 | kind: Service
6 | metadata:
7 | name: web-service
8 | spec:
9 | selector:
10 | app: web
11 | ports:
12 | - name: http
13 | protocol: TCP
14 | port: 9090
15 | targetPort: 9090
16 |
17 | ---
18 | # Service account to allow pod access to Vault via K8s auth
19 |
20 | apiVersion: v1
21 | kind: ServiceAccount
22 | metadata:
23 | name: web
24 | automountServiceAccountToken: true
25 |
26 | ---
27 | # Web frontend
28 |
29 | apiVersion: apps/v1
30 | kind: Deployment
31 | metadata:
32 | name: web-deployment
33 | labels:
34 | app: web
35 | spec:
36 | replicas: 2
37 | selector:
38 | matchLabels:
39 | app: web
40 | template:
41 | metadata:
42 | labels:
43 | app: web
44 | annotations:
45 | vault.hashicorp.com/agent-inject: "true"
46 | vault.hashicorp.com/agent-inject-secret-web: secret/data/web
47 | vault.hashicorp.com/agent-inject-template-web: |
48 | {
49 | {{ with secret "secret/data/web" -}}
50 | "api_key": "{{ .Data.data.api_key }}"
51 | {{- end }}
52 | }
53 | vault.hashicorp.com/role: "web"
54 | spec:
55 | serviceAccountName: web
56 | containers:
57 | - name: web
58 | image: hashicorpdemoapp/product-api:v0.0.4
59 | ports:
60 | - containerPort: 9090
61 | env:
62 | - name: "CONFIG_FILE"
63 | value: "/vault/secrets/web"
64 |
--------------------------------------------------------------------------------
/static_secrets/configure_vault.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Enable and configure Kubernetes Authentication
4 | vault auth enable kubernetes
5 |
6 | kubectl exec $(kubectl get pods --selector "app.kubernetes.io/instance=vault,component=server" -o jsonpath="{.items[0].metadata.name}") -c vault -- \
7 | sh -c ' \
8 | vault write auth/kubernetes/config \
9 | token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
10 | kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
11 | kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
12 |
13 | # Write a secret to the database
14 | vault kv put secret/web api_key=abc123fd
15 |
16 | # Write policy allowing read access to that secret
17 | vault policy write web-static ./config/web-policy.hcl
18 |
19 | # Assign the policy to users who authenticate with Kubernetes service accounts called web
20 | vault write auth/kubernetes/role/web \
21 | bound_service_account_names=web \
22 | bound_service_account_namespaces=default \
23 | policies=web-static \
24 | ttl=1h
--------------------------------------------------------------------------------
/static_secrets/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 | echo "##################################################"
3 | echo "Install shipyard if you do not have it installed"
4 | echo "curl https://shipyard.demo.gs/install.sh | bash"
5 | echo "##################################################"
6 |
7 | yard up --enable-consul false
--------------------------------------------------------------------------------
/transform/Makefile:
--------------------------------------------------------------------------------
1 | image_name="nicholasjackson/transform-demo"
2 |
3 | build_docker_go:
4 | docker build -t ${image_name}:go -f transform-engine-go/Dockerfile ./transform-engine-go
5 |
6 | build_docker_java:
7 | docker build -t ${image_name}:java -f transform-engine-java/Dockerfile ./transform-engine-java
8 |
9 | build_docker_all: build_docker_go build_docker_java
10 |
11 | push_all: build_docker_all
12 | docker push ${image_name}:go
13 | docker push ${image_name}:java
--------------------------------------------------------------------------------
/transform/README.md:
--------------------------------------------------------------------------------
1 | # Format preserving Encryption with the Vault Transform Secrets Engine
2 | This demo shows how to use the Transform secrets engine to encrypt data while preserving data formatting.
3 |
4 | Note: This demo uses Vault Enterprise 1.4, Vault Enterprise in trial mode is limited to sessions of 30 minutes before the Vault server will enter sealed state. You will need to restart the server to reactivate the trial.
5 |
6 | ## Requirements:
7 | * Docker []()
8 | * Shipyard [https://shipyard.run/docs/install](https://shipyard.run/docs/install)
9 |
10 | ## Running the demo
11 | The demo uses Shipyard to start a Vault server and example application in Docker on your local machine. To run the demo use the following command:
12 |
13 | ```shell
14 | ➜ shipyard run ./blueprint
15 | Running configuration from: ./blueprint
16 |
17 | 2020-06-02T07:10:00.190+0100 [DEBUG] Statefile does not exist
18 | 2020-06-02T07:10:00.191+0100 [INFO] Creating Network: ref=local
19 | ```
20 |
21 | Once started interactive documentation can be run for the demo at [http://docs.docs.shipyard.run:8080/docs/index](http://docs.docs.shipyard.run:8080/docs/index). The Vault server, Postgres server and example applications are also accessible from your terminal at the following locations:
22 |
23 | * Vault `localhost:8200`
24 | * PostgreSQL `localhost:5432` DB: `payments`, User: `root`, Pass: `password`
25 | * Java example application `localhost:9092`
26 | * Go example application `localhost:9091`
27 |
28 | ## Stopping the demo
29 | To stop the demo and clean up resources, run the following command:
30 |
31 | ```shell
32 | demo-vault/transform on master [!] via 🐹 v1.13.8 on 🐳 v19.03.10 ()
33 | ➜ shipyard destroy
34 | 2020-06-02T07:08:14.044+0100 [INFO] Destroy Container: ref=vault
35 | 2020-06-02T07:08:14.045+0100 [INFO] Destroy Container: ref=payments_go
36 | 2020-06-02T07:08:14.045+0100 [INFO] Destroy Container: ref=payments_java
37 | 2020-06-02T07:08:14.045+0100 [INFO] Destroy Container: ref=postgres
38 | 2020-06-02T07:08:14.045+0100 [INFO] Destroy Documentation: ref=docs
39 | 2020-06-02T07:08:15.506+0100 [INFO] Destroy Network: ref=local
40 | ```
41 |
42 | ## Source Code
43 | Source code for the example applications can be found in this repository at the following locations:
44 | * Go [./transform-engine-go](./transform-engine-go)
45 | * Java [./transform-engine-java](./transform-engine-java)
--------------------------------------------------------------------------------
/transform/blueprint/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Vault Transform Example
3 | author: Nic Jackson
4 | slug: vault_transform
5 | env:
6 | - VAULT_ADDR=http://localhost:8200
7 | - VAULT_TOKEN=root
8 | ---
9 |
10 | # Environment Variables
11 | To interact with the Vault server using the Vault CLI, set the following environment variables:
12 |
13 | ```shell
14 | export VAULT_ADDR="http://localhost:8200"
15 | export VAULT_TOKEN="root"
16 | ```
17 |
18 | # Vault Server Shell
19 | If you do not have the Vault CLI installed, you can use Shipyard to create an interactive shell
20 | on the Vault server.
21 |
22 | ```shell
23 | shipyard exec container.vault
24 | ```
25 |
26 | # Using the Transform Secrets engine
27 |
28 | After the Vault container starts the `transform` is automatically
29 | configured using the script `./files/setup_vault.sh`. This sets up the
30 | role and transform values.
31 |
32 | To write values using the `transform` engine:
33 |
34 | ```shell
35 | vault write transform/encode/payments value=1111-2222-3333-4444
36 |
37 | Key Value
38 |
39 | encoded_value 9300-3376-4943-8903
40 | ```
41 |
42 | To read values using the `transform` secrets:
43 |
44 | ```shell
45 | vault write transform/decode/payments value=9300-3376-4943-8903
46 | Key Value
47 |
48 | decoded_value 1111-2222-3333-4444
49 | ```
--------------------------------------------------------------------------------
/transform/blueprint/app.hcl:
--------------------------------------------------------------------------------
1 | container "payments_go" {
2 | image {
3 | name = "nicholasjackson/transform-demo:go"
4 | }
5 |
6 | command = ["/app/server"]
7 |
8 | port {
9 | local = 9090
10 | remote = 9090
11 | host = 9091
12 | }
13 |
14 | env {
15 | key = "POSTGRES_HOST"
16 | value = "postgres.container.shipyard.run"
17 | }
18 |
19 | env {
20 | key = "POSTGRES_PORT"
21 | value = "5432"
22 | }
23 |
24 | env {
25 | key = "VAULT_TOKEN"
26 | value = "root"
27 | }
28 |
29 | env {
30 | key = "VAULT_ADDR"
31 | value = "http://vault.container.shipyard.run:8200"
32 | }
33 |
34 | network {
35 | name = "network.local"
36 | }
37 | }
38 |
39 | container "payments_java" {
40 | image {
41 | name = "nicholasjackson/transform-demo:java"
42 | }
43 |
44 | command = [
45 | "java",
46 | "-jar",
47 | "/app/spring-boot-payments-0.1.0.jar",
48 | ]
49 |
50 | port {
51 | local = 9090
52 | remote = 9090
53 | host = 9092
54 | }
55 |
56 | env {
57 | key = "spring_datasource_url"
58 | value = "jdbc:postgresql://postgres.container.shipyard.run:5432/payments"
59 | }
60 |
61 | env {
62 | key = "vault_token"
63 | value = "root"
64 | }
65 |
66 | env {
67 | key = "vault_addr"
68 | value = "http://vault.container.shipyard.run:8200"
69 | }
70 |
71 | network {
72 | name = "network.local"
73 | }
74 | }
--------------------------------------------------------------------------------
/transform/blueprint/docs.hcl:
--------------------------------------------------------------------------------
1 | docs "docs" {
2 | path = "./docs"
3 | port = 8080
4 | open_in_browser = true
5 |
6 | network {
7 | name = "network.local"
8 | }
9 |
10 | index_title = "Transform"
11 | index_pages = ["index"]
12 | }
--------------------------------------------------------------------------------
/transform/blueprint/docs/images/api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/transform/blueprint/docs/images/api.png
--------------------------------------------------------------------------------
/transform/blueprint/docs/images/card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/transform/blueprint/docs/images/card.png
--------------------------------------------------------------------------------
/transform/blueprint/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: index
3 | title: Encrypting Data while Preserving Formatting with the Vault Enterprise Transform Secrets Engine
4 | sidebar_label: Introduction
5 | ---
6 |
7 | import Tabs from '@theme/Tabs';
8 | import TabItem from '@theme/TabItem';
9 |
10 | Vault 1.4 Enterprise introduced a new secrets engine called Transform. Transform is a secrets engine that allows Vault to encode and decode sensitive values residing in external systems such as databases or file systems. The Transform engine allows you to ensure that when a system is compromised, and its data is leaked, that the encoded secrets remain uncompromised even when held by an adversary. Unlike the Transit secrets engine, with Transform you can encrypt data while preserving the original formatting.
11 |
12 | This post shows you how to implement Transform secrets into a simple API; source code is provided for both the Java and Go programming languages.
13 |
14 | For information on the technical detail behind the Transform engine please see Andy's excelent article [https://www.hashicorp.com/blog/transform-secrets-engine](https://www.hashicorp.com/blog/transform-secrets-engine).
15 |
16 |
17 | ### Video demonstration
18 |
19 |
20 |
21 |
22 | ## API Structure
23 |
24 | 
25 |
26 | Our example application is a simple RESTful payment service, backed by a PostgreSQL database, there is a single route which accepts a POST request.
27 |
28 | ```
29 | POST /order
30 | Content-Type: application/json
31 | ```
32 |
33 | ### Request
34 | The data for the API is sent as JSON and has three fields for the `card number`, the `expiration` and the `cv2`.
35 |
36 | ```json
37 | {
38 | "card_number": "1234-1234-1234-1234",
39 | "expiration": "10/12",
40 | "cv2": "123"
41 | }
42 | ```
43 |
44 | ### Response
45 |
46 | On a succesfull call to the API, the data is saved to the database and a transaction ID is returned to the caller.
47 |
48 | ```json
49 | {
50 | "transaction_id": "1234"
51 | }
52 | ```
53 |
54 | ## Security Requirements
55 | The security requirements for the API are:
56 |
57 | * Credit card details must be stored in the database in an encrypted format at rest
58 | * It must be possible to infer the bank and type of a card number without decrypting it
59 |
60 | The first requirement is relatively trivial to solve using Vault and the Transit Secrets Engine. Transit secrets can be used as encryption as service to encrypt the credit card details before they are written to the database.
61 |
62 | To satisfy the second requirement, you need to be able to query the type of the card and the bank which issued it. You can get this data from the card number as not all the data in a credit card number is unique. A credit card number is composed of three parts: the Issuer Number, the Account Number and the Checksum.
63 |
64 | 
65 |
66 | `Issuer Number` relates to the type of the card (first digit), and the issuers code this is the information which you would like to query to satisfy the second requirement.
67 |
68 | `Account Number` is the unique identifier assigned to the holder of the card
69 |
70 | `Checksum` is not a secret part of the card number instead it is desined for quick error checking. The checksum is generated from the card number, before processing the checksum is regenerated using the Luhn algorithm if the given and the computed checksums differ then the card number has been entered incorrectly.
71 |
72 | To be able to query the card issuer you realistically have two options:
73 |
74 | 1. Partially encrypt the card number in the databse
75 | 1. Store metadata for the card along with the encrypted values
76 |
77 | To implement this requirement in code, the developers have the responsiblity for managing the complexity of partially encrypting the credit card data, and information security need to worry about the correct implementation of this.
78 |
79 | ## Transform Secrets Engine
80 | Vault's Transform Secrets Engine can be used to simplify the process while still satisfying the second requirement. Transform allows you to encrypt data while preserving formatting or to partially encrypt data based on a user-configurable formula. The benefits of this are that the info security team can centrally manage the definition for the encryption process, and the developers don’t need to worry about the implementation, they can use the Transform API to encrypt the card numbers.
81 |
82 | In our use case where there is a need to partially encrypt the credit card numbers leaving the issuer as queryable data, a transform could be defined. This Transform takes a card number and encrypts the sensitive parts while retaining the formatting and ability to infer information about the card type and issuing bank.
83 |
84 | Transforms are defined as regular expressions; the capture groups inside the expression are replaced with ciphertext, and anything outside the match groups is left in the original format.
85 |
86 | To encrypt only the account number and checksum for a credit card number, you could use the following regular expression.
87 |
88 | ```
89 | \d{4}-\d{2}(\d{2})-(\d{4})-(\d{4})
90 | ```
91 |
92 | Given an input credit card number:
93 |
94 | ```
95 | 1234-5611-1111-1111
96 | ```
97 |
98 | Vault would return the cyphertext:
99 |
100 | ```
101 | 1234-5672-6649-0472
102 | ```
103 |
104 | Note the first 6 digits have not been replaced with cyphertext as there are no capture groups in the regular expression for this text, the formatting of the data is also preserved as this was outside the capture groups.
105 |
106 |
107 | ## Real world impact of partially encrypting data
108 |
109 | You may be wondering, by only encrypting the account number and cv2 data, are you reducing the security of the encrypted card number?
110 |
111 | The short answer is yes, but in real terms, it probably does not make a difference.
112 |
113 | A number containing 16 digits has a possibility of 16^16 combinations, including the CV2 number. This roughly equates to 10 quintillion different permutations.
114 |
115 | If you only store 10 digits of the card number plus the CV2, this is 10^13, or about 10 trillion combinations.
116 |
117 | In reality, since the first 6 digits of a card number are the issuer and card type, there are not 1 million different issuers. Let's say there are 10,000, storing the full 16 digits would give you roughly 100 quadrillion combinations. In both cases, we need to remove the checksum, so we get 10 quadrillion combinations if you encrypt the account number and 1 trillion if you do not.
118 |
119 | Yes, not encrypting the issuer means someone can make fewer guesses to determine the number, but they still need to make 1 trillion guesses. Assuming someone managed to obtain your database containing partially encrypted card numbers. If you had an average API request time of 100ms to accept or reject a payment, it would take about 190258 years for someone to brute force a payment. Even if the attacker was running parallel attacks, the odds are stacked heavily against them.
120 |
121 | Fun math to one side, since we have determined it is secure to encrypt these credit card numbers partially, let's see how to do it.
122 |
123 |
124 | ## Configuring Transform Secrets
125 |
126 | The Transform secrets engine is only available with **Vault Enterprise version 1.4** and above. With all versions of Vault, only the the Key/Value engine and the Cubbyhole secrets engines are enabled by default. To use the Transform Secrets Engine it first needs to be enabled, you can use the following command.
127 |
128 | ```shell
129 | vault secrets enable transform
130 | ```
131 |
132 |
133 |
134 |
135 | To encrypt data with the transform secrets engine, there are several resources which encapsulating different aspects of the transform process that need to be configured. These are:
136 |
137 | * **Roles** - Roles are the basic high-level construct that holds the set of transformation that it is allowed to performed. The role name is provided when performing encode and decode operations.
138 |
139 | * **Transformations** - Transformations hold information about a particular transformation. It contains information about the type of transformation that we want to perform, the template that it should use for value detection, and other transformation-specific values such as the tweak source or the masking character to use.
140 |
141 | * **Templates** - Templates allow us to determine what and how to capture the value that we want to transform.
142 |
143 | * **Alpahbets** - Alphabets provide the set of valid UTF-8 character contained within both the input and transformed value on FPE transformations.
144 |
145 | Let's walk through each of the steps.
146 |
147 | ### Roles
148 |
149 | First, we need to create a role called payments, when creating the role you provide the list of transformations that can be used from this role using the transformations parameter. The transformation ccn-fpe in the example role below does not yet exist. The transformations parameter is a `"soft"` constraint, while a role requires transforms to encode and decode data, they do not need to exist when creating the role.
150 |
151 | ```shell
152 | vault write transform/role/payments transformations=ccn-fpe
153 | ```
154 |
155 |
156 |
157 |
158 | ### Transformations
159 |
160 | Next we create a transformation called `ccn-fpe`, this is the same name that was referenced when you create the role in the previous step. The parameter `type` defines the transform operation you would like to perform, this has two possible values:
161 |
162 | * `fpe` - use Format Preserving Encryption using the FF3-1 algoryrthm
163 | * `masking` - this process replaces the sensitive characters in a transformation with a desired character but is not reversable.
164 |
165 | `tweak_source` is a non-confidential value which is stored alongside the ciphertext used when performing encryption and decryption operations. This parameter takes one of three permissible values, `supplied`, `generated`, `internal`. This example uses the `internal` value which delegates the creation and storage of the tweak value to Vault. More information on tweak source can be found in the Vault documentation, [https://www.vaultproject.io/docs/secrets/transform#tweak-source](https://www.vaultproject.io/docs/secrets/transform#tweak-source).
166 |
167 | The `template` parameter relates to the template which will be used by the transform, you can define templates and reuse them across multiple different transformations. Vault has two built in Templates which can be used `builtin/creditcardnumber` and `builtin/socialsecuritynumber`.
168 |
169 | Finally, you specify the `allowed_roles`, specifying allowed roles ensures that the creator of the role is allowed to use this transformation.
170 |
171 | ```
172 | vault write transform/transformation/ccn-fpe \
173 | type=fpe \
174 | tweak_source=internal \
175 | template=ccn \
176 | allowed_roles=payments
177 | ```
178 |
179 |
180 |
181 |
182 | ### Templates
183 |
184 | The template defines what in the data is encrypted by the transform, templates are specfied as regular expressions, the capture groups in the expression define
185 | the elements of the input which will be replaced with cyphertext.
186 |
187 | The below example creates a template called `ccn`. The `type` parameter is set to `regex` which is currently the only option supported by the backend. Then you specify a `pattern` as a valid regular expression. For `fpe` transformations you need to specify the `alphabet`, the `alphabet` is a custom character set which will be used in the outputed cyphertext. `alphabet` can either be a custom alphabet like the example below or one of the [built in](https://www.vaultproject.io/docs/secrets/transform#alphabets) values.
188 |
189 | ```shell
190 | vault write transform/template/ccn \
191 | type=regex \
192 | pattern='\d{4}-\d{2}(\d{2})-(\d{4})-(\d{4})' \
193 | alphabet=numerics
194 | ```
195 |
196 |
197 |
198 | ### Alphabets
199 |
200 | Creating custom alphabets is an optional step for a transform, Vault has a number of built in alphabets covering common usecases however you wish your cypher text to be composed of a specific set of unicode characters. To define a custom alphabet you use the following command, this command creates a custom alphabet called `numerics` using the characters `0-9`.
201 |
202 | ```shell
203 | vault write transform/alphabet/numerics \
204 | alphabet="0123456789"
205 | ```
206 |
207 |
208 |
209 |
210 | ## Testing the transform
211 |
212 | Now all of the components have been configured you can test the setup by writing data to the path `transform/encode/payments`, the part of the path `payments` refers to the name of your transform created in the previous steps.
213 |
214 | ```
215 | vault write transform/encode/payments value=1111-2222-3333-4444
216 | ```
217 |
218 |
219 |
220 | You will see an output which looks similar to the following. Note that the first 6 digits of the returne cyphertext are the same as the original data.
221 |
222 | ```
223 | Key Value
224 | --- -----
225 | encoded_value 1111-2200-1452-4879
226 | ```
227 |
228 | To decode this cyphertext and reverse the operation, you write data to the `transform/decode/payments` path.
229 |
230 | ```
231 | vault write transform/decode/payments value=
232 |
233 | vault write transform/decode/payments value=1111-2200-1452-4879
234 | ```
235 |
236 | You will see output which looks similar to the below example:
237 |
238 | ```shell
239 | Key Value
240 | --- -----
241 | decoded_value 1111-2222-3333-4444
242 | ```
243 |
244 |
245 |
246 |
247 |
248 |
249 | ## Using Transform in your application
250 |
251 | So far you have seen how you can use the Transform engine using the CLI, to use the transform engine from your application you need to use Vault's API, everything possible using the CLI is also possible using the RESTful API. To interact with the Vault API you have three options:
252 |
253 | 1. Use one of the [Client libraries](https://www.vaultproject.io/api/libraries.html)
254 | 1. Code generate your own client using the [OpenAPI v3 specifications](https://www.vaultproject.io/api-docs/system/internal-specs-openapi)
255 | 1. Manually interact with the HTTP API
256 |
257 | This example is going to demonstrate the third option, as this demonstrates the simplicity with interacting with Vault's API.
258 |
259 | The example application only needs to encode data, and not manage the configuration for Transform, to do this it only needs to interact with a single API endpoint which is Encode.
260 |
261 | https://www.vaultproject.io/api-docs/secret/transform#encode
262 |
263 |
264 | ## Using the Transform Encode API
265 |
266 | The application only needs to encode data and not manage the configuration for Transform; to do this, it only needs to interact with a single API endpoint, which is Encode.
267 |
268 | [https://www.vaultproject.io/api-docs/secret/transform#encode](https://www.vaultproject.io/api-docs/secret/transform#encode)
269 |
270 | To encode data using transform secrets engine, you POST a JSON payload to the path `/v1/transform/encode/:role_name`, in this example `:role_name` is payments, which is the name of the role created earlier.
271 |
272 | The API requires that you have a valid Vault token, and that token has the right policy allocated to it to operate. The Vault token is sent to the request using the `X-Vault-Token` HTTP header.
273 |
274 | The payload for the request is a simple JSON structure with a single field `value`, you can see an example below
275 |
276 | ```json
277 | {
278 | "value": "1111-2222-3333-4444"
279 | }
280 | ```
281 |
282 | If you were to use cURL to interact with the API and encode some data you could use the following command. You post the JSON payload to the path `v1/transform/encode/payments` along with the Vault token in an HTTP header.
283 |
284 | ```shell
285 | curl localhost:8200/v1/transform/encode/payments \
286 | -H 'X-Vault-Token: root' \
287 | -d '{"value": "1111-2222-3333-4444"}'
288 | ```
289 |
290 | The ciphertext for the submitted data is returned in the JSON response at `.data.encoded_value`. As you will see later on in the post it is a fairly trivial exercise to extract this information.
291 |
292 | ```json
293 | {
294 | "request_id": "0f170922-d7c1-0137-391b-932a2025beb4",
295 | "lease_id": "",
296 | "renewable": false,
297 | "lease_duration": 0,
298 | "data": {
299 | "encoded_value": "1111-2208-4340-0589"
300 | },
301 | "wrap_info": null,
302 | "warnings": null,
303 | "auth": null
304 | }
305 | ```
306 |
307 | Now you understand the basics with interacting with the API let's see how this can be done from your applications code.
308 |
309 | ## Interacting with the Vault API from Java and Go
310 |
311 | The first thing we need to do is to construct a byte array which holds a JSON formatted string for your payload.
312 |
313 |
320 |
321 |
322 |
323 | ```go
324 | // create the JSON request as a byte array
325 | req := TokenRequest{Value: cc}
326 | data, _ := json.Marshal(req)
327 | ```
328 |
329 |
330 |
331 |
332 |
333 | ```java
334 | // create the request
335 | TokenRequest req = new TokenRequest(cardNumber);
336 |
337 | // convert the POJO to a byte array
338 | ObjectMapper mapper = new ObjectMapper();
339 | mapper.enable(SerializationFeature.INDENT_OUTPUT);
340 | byte[] byteRequest = mapper.writeValueAsBytes(req);
341 | ```
342 |
343 |
344 |
345 |
346 |
347 | You can then construct the request, setting this payload as part of the request body.
348 |
349 |
356 |
357 |
358 |
359 | ```go
360 | url := fmt.Sprintf("http://%s/v1/transform/encode/payments", c.uri)
361 | r, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
362 | r.Header.Add("X-Vault-Token", "root")
363 |
364 | resp, err := http.DefaultClient.Do(r)
365 | if err != nil {
366 | return "", err
367 | }
368 | defer resp.Body.Close()
369 |
370 | if resp.StatusCode != http.StatusOK {
371 | return "", fmt.Errorf("Vault returned reponse code %d, expected status code 200", resp.StatusCode)
372 | }
373 | ```
374 |
375 |
376 |
377 |
378 |
379 | ```java
380 | // make a call to vault to process the request
381 | URL url = new URL(this.url);
382 | HttpURLConnection con = (HttpURLConnection)url.openConnection();
383 | con.setDoOutput(true);
384 | con.setRequestMethod("POST");
385 | con.setRequestProperty("Content-Type", "application/json; utf-8");
386 | con.setRequestProperty("Accept", "application/json");
387 | con.setRequestProperty("X-Vault-Token", this.token);
388 |
389 | // write the body
390 | try(OutputStream os = con.getOutputStream()) {
391 | os.write(byteRequest, 0, byteRequest.length);
392 | }
393 | ```
394 |
395 |
396 |
397 |
398 |
399 | To read the JSON response, you can parse the response body from the HTTP client into a simple structure.
400 |
401 |
408 |
409 |
410 |
411 | ```go
412 | // process the response
413 | tr := &TokenResponse{}
414 | err = json.NewDecoder(resp.Body).Decode(tr)
415 | if err != nil {
416 | return "", err
417 | }
418 | ```
419 |
420 |
421 |
422 |
423 |
424 | ```java
425 | // read the response
426 | TokenResponse resp = new ObjectMapper()
427 | .readerFor(TokenResponse.class)
428 | .readValue(con.getInputStream());
429 | ```
430 |
431 |
432 |
433 |
434 |
435 | Full source code for both examples can be found at: [https://github.com/nicholasjackson/demo-vault/tree/master/transform](https://github.com/nicholasjackson/demo-vault/tree/master/transform)
436 |
437 | ## Testing the service
438 |
439 | Let's test the service, the demo has both the Java and the Go code running, so you can use `curl` to test it.
440 |
441 |
448 |
449 |
450 |
451 | ```shell
452 | curl payments-go.container.shipyard.run:9090 -H "content-type: application/json" -d '{"card_number": "1234-1234-1234-1234"}'
453 | ```
454 |
455 |
456 |
457 |
458 |
459 | ```shell
460 | curl payments-java.container.shipyard.run:9090 -H "content-type: application/json" -d '{"card_number": "1234-1234-1234-1234"}'
461 | ```
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 | You should see a response something like the following
471 |
472 | ```json
473 | {"transaction_id": 11}
474 | ```
475 |
476 | If you query the orders table on the database you will be able to see the encrypted value for this transaction
477 |
478 | ```
479 | PGPASSWORD=password psql -h localhost -p 5432 -U root -d payments -c 'SELECT * from orders;'
480 | ```
481 |
482 |
483 |
484 |
485 |
486 | You can validate that this cyphertext is correct using the CLI like in the earlier example:
487 |
488 | ```shell
489 | vault write transform/decode/payments value=
490 | ```
491 |
492 |
493 |
494 | ## Summary
495 |
496 | In this post, you have seen how the new Transform secrets engine can be used to partially encrypt credit card numbers at rest while preserving the formatting and ability to query the card issuer.
497 |
498 | This example only covers one of the possibilities for the Transform secrets engine; if you have an interesting use case for Transform let us know, we would love to feature this in a future post.
--------------------------------------------------------------------------------
/transform/blueprint/files/agent_config.hcl:
--------------------------------------------------------------------------------
1 | auto_auth {
2 | method {
3 | type = "approle"
4 | config = {
5 | role_id_file_path = "/secrets/role_id"
6 | secret_id_file_path = "/secrets/secret_id"
7 | remove_secret_id_file_after_reading = true
8 | }
9 | }
10 | }
11 |
12 | cache {
13 | use_auto_auth_token = true
14 | }
15 |
16 | listener "tcp" {
17 | address = "127.0.0.1:8200"
18 | tls_disable = true
19 | }
20 |
21 | vault {
22 | address = "http://vault.container.shipyard.run:8200"
23 | }
--------------------------------------------------------------------------------
/transform/blueprint/files/db_setup.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE orders (
2 | id SERIAL PRIMARY KEY,
3 | card_number VARCHAR (255) NOT NULL,
4 | created_at TIMESTAMP NOT NULL,
5 | updated_at TIMESTAMP NOT NULL,
6 | deleted_at TIMESTAMP
7 | );
--------------------------------------------------------------------------------
/transform/blueprint/files/setup_approle.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | vault auth enable approle
4 |
5 | vault write auth/approle/role/payments_app \
6 | secret_id_ttl=10m \
7 | token_num_uses=10 \
8 | token_ttl=20m \
9 | token_max_ttl=30m \
10 | secret_id_num_uses=40
11 |
12 | output=$(dirname "$0")/../secrets/
13 |
14 | # fetch the role-id from vault and parse the response
15 | vault read auth/approle/role/payments_app/role-id -format=json | sed -E -n 's/.*"role_id": "([^"]*).*/\1/p' > ${output}/role_id
16 |
17 | # fetch the secret from vault and parse the response
18 | vault write -f auth/approle/role/payments_app/secret-id -format=json | sed -E -n 's/.*"secret_id": "([^"]*).*/\1/p' > ${output}/secret_id
--------------------------------------------------------------------------------
/transform/blueprint/files/setup_transform.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | vault secrets enable transform
4 |
5 | vault write transform/role/payments transformations=ccn-fpe
6 |
7 | vault write transform/transformation/ccn-fpe \
8 | type=fpe \
9 | template=ccn \
10 | tweak_source=internal \
11 | allowed_roles=payments
12 |
13 | vault write transform/template/ccn \
14 | type=regex \
15 | pattern='\d{4}-\d{2}(\d{2})-(\d{4})-(\d{4})' \
16 | alphabet=numerics
17 |
18 | vault write transform/alphabet/numerics \
19 | alphabet="0123456789"
--------------------------------------------------------------------------------
/transform/blueprint/network.hcl:
--------------------------------------------------------------------------------
1 | network "local" {
2 | subnet = "10.5.0.0/16"
3 | }
--------------------------------------------------------------------------------
/transform/blueprint/postgres.hcl:
--------------------------------------------------------------------------------
1 | container "postgres" {
2 | image {
3 | name = "postgres:11.6"
4 | }
5 |
6 | port {
7 | local = 5432
8 | remote = 5432
9 | host = 5432
10 | }
11 |
12 | env {
13 | key = "POSTGRES_DB"
14 | value = "payments"
15 | }
16 |
17 | env {
18 | key = "POSTGRES_USER"
19 | value = "root"
20 | }
21 |
22 | env {
23 | key = "POSTGRES_PASSWORD"
24 | value = "password"
25 | }
26 |
27 | # Mount the volume for the DB setup script which runs when
28 | # the container starts
29 | volume {
30 | source = "./files/db_setup.sql"
31 | destination = "/docker-entrypoint-initdb.d/db_setup.sql"
32 | }
33 |
34 | network {
35 | name = "network.local"
36 | }
37 | }
--------------------------------------------------------------------------------
/transform/blueprint/secrets/role_id:
--------------------------------------------------------------------------------
1 | 929a5f41-555c-e481-cce9-1a17d970e921
2 |
--------------------------------------------------------------------------------
/transform/blueprint/secrets/secret_id:
--------------------------------------------------------------------------------
1 | 98c30678-dbb3-9d81-4be3-1d608b56afa2
2 |
--------------------------------------------------------------------------------
/transform/blueprint/vault.hcl:
--------------------------------------------------------------------------------
1 | container "vault" {
2 | image {
3 | name = "hashicorp/vault-enterprise:1.4.0-rc1_ent"
4 | }
5 |
6 | command = [
7 | "vault",
8 | "server",
9 | "-dev",
10 | "-dev-root-token-id=root",
11 | "-dev-listen-address=0.0.0.0:8200",
12 | ]
13 |
14 | port {
15 | local = 8200
16 | remote = 8200
17 | host = 8200
18 | }
19 |
20 | # Wait for Vault to start
21 | health_check {
22 | timeout = "30s"
23 | http = "http://localhost:8200/v1/sys/health"
24 | }
25 |
26 | volume {
27 | source = "./files"
28 | destination = "/files"
29 | }
30 |
31 | env {
32 | key = "VAULT_ADDR"
33 | value = "http://localhost:8200"
34 | }
35 |
36 | env {
37 | key = "VAULT_TOKEN"
38 | value = "root"
39 | }
40 |
41 | network {
42 | name = "network.local"
43 | }
44 | }
45 |
46 | #exec_remote "setup" {
47 | # target = "container.vault"
48 | #
49 | # cmd = "/files/setup_vault.sh"
50 | #}
--------------------------------------------------------------------------------
/transform/transform-engine-go/.realize.yaml:
--------------------------------------------------------------------------------
1 | settings:
2 | legacy:
3 | force: false
4 | interval: 0s
5 | server:
6 | status: true
7 | open: false
8 | host: 0.0.0.0
9 | port: 5002
10 | schema:
11 | - name: files
12 | path: .
13 | commands:
14 | install:
15 | status: true
16 | method: go build -o ./bin/server
17 | run:
18 | status: true
19 | method: ./bin/server
20 | watcher:
21 | paths:
22 | - /
23 | extensions:
24 | - go
25 | ignored_paths:
26 | - .git
27 | - .realize
--------------------------------------------------------------------------------
/transform/transform-engine-go/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.13 as build
2 |
3 |
4 | WORKDIR /go/src/github.com/nicholasjackson/demo-vault/transform-engine-go
5 | COPY . .
6 |
7 | RUN go get -d -v ./... && \
8 | CGO_ENABLED=0 go build -o /bin/server ./
9 |
10 | FROM alpine:latest
11 |
12 | RUN apk add curl
13 |
14 | RUN mkdir /app
15 | COPY --from=build /bin/server /app/server
16 |
17 | ENTRYPOINT [ "/app/server" ]
--------------------------------------------------------------------------------
/transform/transform-engine-go/bin/server:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/transform/transform-engine-go/bin/server
--------------------------------------------------------------------------------
/transform/transform-engine-go/data/postgres.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/jmoiron/sqlx"
9 | "github.com/lib/pq"
10 | )
11 |
12 | // Order defines an order object which is stored in the DB orders table
13 | type Order struct {
14 | ID int `db:"id" json:"id"`
15 | CardNumber string `db:"card_number" json:"card_number"`
16 | CreatedAt string `db:"created_at" json:"-"`
17 | UpdatedAt string `db:"updated_at" json:"-"`
18 | DeletedAt sql.NullString `db:"deleted_at" json:"-"`
19 | }
20 |
21 | // PostgreSQL is a database client for PostgresSQL
22 | type PostgreSQL struct {
23 | db *sqlx.DB
24 | }
25 |
26 | // NewPostgreSQLClient creates a new SQL client
27 | func NewPostgreSQLClient(connection string) (*PostgreSQL, error) {
28 | db, err := sqlx.Connect("postgres", connection)
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | ps := &PostgreSQL{db}
34 | _, err = ps.IsConnected()
35 |
36 | if err != nil {
37 | return nil, err
38 | }
39 |
40 | return ps, nil
41 | }
42 |
43 | // IsConnected checks the connection to the database and returns an error if not connected
44 | func (c *PostgreSQL) IsConnected() (bool, error) {
45 | err := c.db.Ping()
46 | if err != nil {
47 | return false, err
48 | }
49 |
50 | return true, nil
51 | }
52 |
53 | // SaveOrder saves the order into the datbase
54 | func (c *PostgreSQL) SaveOrder(o Order) (int64, error) {
55 | var id int64
56 |
57 | err := c.db.QueryRow(
58 | `INSERT INTO orders (card_number, created_at, updated_at) VALUES ($1, $2, $3) RETURNING id`,
59 | o.CardNumber,
60 | pq.FormatTimestamp(time.Now()),
61 | pq.FormatTimestamp(time.Now()),
62 | ).Scan(&id)
63 |
64 | if err != nil {
65 | return -1, fmt.Errorf("Unable to insert record into DB. Error: %s", err)
66 | }
67 |
68 | fmt.Println("id", id)
69 |
70 | return id, nil
71 | }
72 |
--------------------------------------------------------------------------------
/transform/transform-engine-go/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/nicholasjackson/demo-vault/transform-engine-go
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/google/martian v2.1.0+incompatible // indirect
7 | github.com/gorilla/mux v1.7.4
8 | github.com/hajimehoshi/oto v0.5.4
9 | github.com/hashicorp/go-hclog v0.12.2
10 | github.com/jmoiron/sqlx v1.2.0
11 | github.com/lib/pq v1.3.0
12 | github.com/nicholasjackson/env v0.6.0
13 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
14 | google.golang.org/appengine v1.6.5 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/transform/transform-engine-go/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
4 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
5 | github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
6 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
7 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
8 | github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
9 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
10 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
11 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
12 | github.com/hajimehoshi/oto v0.5.4 h1:Dn+WcYeF310xqStKm0tnvoruYUV5Sce8+sfUaIvWGkE=
13 | github.com/hajimehoshi/oto v0.5.4/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
14 | github.com/hashicorp/go-hclog v0.12.2 h1:F1fdYblUEsxKiailtkhCCG2g4bipEgaHiDc8vffNpD4=
15 | github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
16 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
17 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
18 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
19 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
20 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
21 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
22 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
23 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
24 | github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
25 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
26 | github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
27 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
28 | github.com/nicholasjackson/demo-vault v0.0.0-20200103191244-3870cf29c73c h1:/d/IX3ovqMe0QOIu1wPNc27auMYZ6XL2FDR3NIhRRwo=
29 | github.com/nicholasjackson/env v0.6.0 h1:6xdio52m7cKRtgZPER6NFeBZxicR88rx5a+5Jl4/qus=
30 | github.com/nicholasjackson/env v0.6.0/go.mod h1:/GtSb9a/BDUCLpcnpauN0d/Bw5ekSI1vLC1b9Lw0Vyk=
31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
33 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
34 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
35 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
36 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
37 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
38 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
39 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
41 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
42 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
43 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
44 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
45 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
46 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
48 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
50 | google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
51 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
52 |
--------------------------------------------------------------------------------
/transform/transform-engine-go/handlers/health.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/hashicorp/go-hclog"
8 | "github.com/nicholasjackson/demo-vault/transform-engine-go/data"
9 | "github.com/nicholasjackson/demo-vault/transform-engine-go/vault"
10 | )
11 |
12 | // Health is a http.Handler for API status
13 | type Health struct {
14 | pc *data.PostgreSQL
15 | vc *vault.Client
16 | log hclog.Logger
17 | }
18 |
19 | // NewHealth creates a new health handler
20 | func NewHealth(pc *data.PostgreSQL, vc *vault.Client, log hclog.Logger) *Health {
21 | return &Health{pc, vc, log}
22 | }
23 |
24 | // HealthResponse is returned by the handler
25 | type HealthResponse struct {
26 | Vault string `json:"vault"`
27 | DB string `json:"db"`
28 | }
29 |
30 | func (h *Health) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
31 | hr := &HealthResponse{
32 | Vault: "OK",
33 | DB: "OK",
34 | }
35 |
36 | status := http.StatusOK
37 |
38 | // check health of Vault
39 | if ok, _ := h.pc.IsConnected(); !ok {
40 | hr.DB = "Fail"
41 | status = http.StatusInternalServerError
42 | }
43 |
44 | if !h.vc.IsOK() {
45 | hr.Vault = "Fail"
46 | status = http.StatusInternalServerError
47 | }
48 |
49 | rw.WriteHeader(status)
50 | json.NewEncoder(rw).Encode(hr)
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/transform/transform-engine-go/handlers/payment.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/hashicorp/go-hclog"
9 | "github.com/nicholasjackson/demo-vault/transform-engine-go/data"
10 | "github.com/nicholasjackson/demo-vault/transform-engine-go/vault"
11 | )
12 |
13 | // Payment is a http.Handler for payment routes
14 | type Payment struct {
15 | pc *data.PostgreSQL
16 | vc *vault.Client
17 | log hclog.Logger
18 | }
19 |
20 | // NewPayment creates a new payment handler
21 | func NewPayment(pc *data.PostgreSQL, vc *vault.Client, log hclog.Logger) *Payment {
22 | return &Payment{pc, vc, log}
23 | }
24 |
25 | // PaymentRequest is sent as part of the HTTP request
26 | type PaymentRequest struct {
27 | CardNumber string `json:"card_number"`
28 | Expiration string `json:"expiration"`
29 | CV2 string `json:"cv2"`
30 | }
31 |
32 | // PaymentResponse is sent with a successful payment
33 | type PaymentResponse struct {
34 | TransactionID int64 `json:"transaction_id"`
35 | }
36 |
37 | // PaymentError is returned when an error occurs
38 | type PaymentError struct {
39 | Message string `json:"message"`
40 | }
41 |
42 | // ServeHTTP implement http.Handler interface
43 | func (p *Payment) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
44 | // parse the request
45 | pr := &PaymentRequest{}
46 | err := json.NewDecoder(r.Body).Decode(pr)
47 | if err != nil {
48 | p.log.Error("Unable to parse request", "error", err)
49 |
50 | presp := &PaymentError{fmt.Sprintf("Unable to parse request: %s", err)}
51 | rw.WriteHeader(http.StatusBadRequest)
52 | json.NewEncoder(rw).Encode(presp)
53 | return
54 | }
55 |
56 | // tokenize the card number
57 | encoded, err := p.vc.TokenizeCCNumber(pr.CardNumber)
58 | if err != nil {
59 | p.log.Error("Unable to tokenize record", "error", err)
60 |
61 | presp := &PaymentError{fmt.Sprintf("Uable to tokenize record: %s", err)}
62 | rw.WriteHeader(http.StatusInternalServerError)
63 | json.NewEncoder(rw).Encode(presp)
64 | return
65 | }
66 |
67 | // save the data into the database
68 | o := data.Order{CardNumber: encoded}
69 | id, err := p.pc.SaveOrder(o)
70 | if err != nil {
71 | p.log.Error("Unable to save record", "error", err)
72 |
73 | presp := &PaymentError{fmt.Sprintf("Uable to save data to db: %s", err)}
74 | rw.WriteHeader(http.StatusInternalServerError)
75 | json.NewEncoder(rw).Encode(presp)
76 | return
77 | }
78 |
79 | presp := PaymentResponse{id}
80 | json.NewEncoder(rw).Encode(presp)
81 | }
82 |
83 | // Example VISA number
84 | //o := data.Order{CardNumber: "4024-2322-1235-9245"}
85 | // Example MasterCard number
86 | //o = data.Order{CardNumber: "5355-6853-9451-3461"}
87 |
--------------------------------------------------------------------------------
/transform/transform-engine-go/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/gorilla/mux"
9 | "github.com/hashicorp/go-hclog"
10 | "github.com/nicholasjackson/demo-vault/transform-engine-go/data"
11 | "github.com/nicholasjackson/demo-vault/transform-engine-go/handlers"
12 | "github.com/nicholasjackson/demo-vault/transform-engine-go/vault"
13 | "github.com/nicholasjackson/env"
14 | )
15 |
16 | var postgresHost = env.String("POSTGRES_HOST", false, "localhost", "PostgreSQL server location")
17 | var postgresPort = env.Int("POSTGRES_PORT", false, 5432, "PostgreSQL port")
18 | var vaultHost = env.String("VAULT_ADDR", false, "http://localhost:8200", "Vault server location")
19 |
20 | func main() {
21 | env.Parse()
22 |
23 | var err error
24 | log := hclog.Default()
25 |
26 | // Create the postgres DB client
27 | var pc *data.PostgreSQL
28 | for {
29 | pc, err = data.NewPostgreSQLClient(fmt.Sprintf("host=%s port=%d user=root password=password dbname=payments sslmode=disable", *postgresHost, *postgresPort))
30 | if err == nil {
31 | break
32 | }
33 |
34 | log.Error("Unable to connect to database", "error", err)
35 | time.Sleep(5 * time.Second)
36 | }
37 |
38 | // Create the Vault client
39 | vc := vault.NewClient("root", *vaultHost)
40 |
41 | // create the HTTP handler
42 | ph := handlers.NewPayment(pc, vc, log)
43 | hh := handlers.NewHealth(pc, vc, log)
44 |
45 | r := mux.NewRouter()
46 | r.Handle("/", ph).Methods(http.MethodPost)
47 | r.Handle("/health", hh).Methods(http.MethodGet)
48 |
49 | log.Info("Starting server", "bind", ":9090")
50 |
51 | http.ListenAndServe(":9090", r)
52 | }
53 |
--------------------------------------------------------------------------------
/transform/transform-engine-go/vault/vault.go:
--------------------------------------------------------------------------------
1 | package vault
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | type Client struct {
11 | token string
12 | uri string
13 | }
14 |
15 | type TokenRequest struct {
16 | Value string `json:"value"`
17 | }
18 |
19 | type TokenResponse struct {
20 | Data TokenReponseData `json:"data"`
21 | }
22 |
23 | type TokenReponseData struct {
24 | EncodedValue string `json:"encoded_value"`
25 | }
26 |
27 | // NewClient creates a new Vault client
28 | func NewClient(token, serverURI string) *Client {
29 | return &Client{token, serverURI}
30 | }
31 |
32 | // IsOK returns true if Vault is unsealed and can accept requests
33 | func (c *Client) IsOK() bool {
34 | url := fmt.Sprintf("%s/v1/sys/health", c.uri)
35 |
36 | r, _ := http.NewRequest(http.MethodGet, url, nil)
37 | r.Header.Add("X-Vault-Token", c.token)
38 |
39 | resp, err := http.DefaultClient.Do(r)
40 | if err != nil {
41 | return false
42 | }
43 |
44 | defer resp.Body.Close()
45 |
46 | if resp.StatusCode != http.StatusOK {
47 | return false
48 | }
49 |
50 | return true
51 | }
52 |
53 | // TokenizeCCNumber uses the Vault API to tokenize the given string
54 | func (c *Client) TokenizeCCNumber(cc string) (string, error) {
55 | // create the JSON request as a byte array
56 | req := TokenRequest{Value: cc}
57 | data, _ := json.Marshal(req)
58 |
59 | // call the api
60 | url := fmt.Sprintf("%s/v1/transform/encode/payments", c.uri)
61 | r, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
62 | r.Header.Add("X-Vault-Token", c.token)
63 |
64 | resp, err := http.DefaultClient.Do(r)
65 | if err != nil {
66 | return "", err
67 | }
68 | defer resp.Body.Close()
69 |
70 | if resp.StatusCode != http.StatusOK {
71 | return "", fmt.Errorf("Vault returned reponse code %d, expected status code 200", resp.StatusCode)
72 | }
73 |
74 | // process the response
75 | tr := &TokenResponse{}
76 | err = json.NewDecoder(resp.Body).Decode(tr)
77 | if err != nil {
78 | return "", err
79 | }
80 |
81 | return tr.Data.EncodedValue, nil
82 | }
83 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | transform-engine-java
4 | Project transform-engine-java created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/usr/lib/jvm/java-11-openjdk-amd64
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
3 | org.eclipse.jdt.core.compiler.compliance=1.8
4 | org.eclipse.jdt.core.compiler.source=1.8
5 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:12 as build
2 |
3 | WORKDIR /src
4 | COPY . /src
5 |
6 | RUN ./gradlew build
7 |
8 | FROM openjdk:12
9 |
10 | RUN mkdir /app
11 | WORKDIR /app
12 |
13 | COPY --from=build /src/build/libs/spring-boot-payments-0.1.0.jar /app/spring-boot-payments-0.1.0.jar
14 |
15 | ENTRYPOINT ["java", "-jar", "/app/spring-boot-payments-0.1.0.jar"]
--------------------------------------------------------------------------------
/transform/transform-engine-java/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | dependencies {
6 | classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.6.RELEASE")
7 | }
8 | }
9 |
10 | apply plugin: 'java'
11 | apply plugin: 'eclipse'
12 | apply plugin: 'idea'
13 | apply plugin: 'org.springframework.boot'
14 | apply plugin: 'io.spring.dependency-management'
15 |
16 | bootJar {
17 | baseName = 'spring-boot-payments'
18 | version = '0.1.0'
19 | }
20 |
21 | repositories {
22 | mavenCentral()
23 | }
24 |
25 | sourceCompatibility = 1.8
26 | targetCompatibility = 1.8
27 |
28 | dependencies {
29 | compile("org.springframework.boot:spring-boot-starter-web")
30 | compile('org.springframework.boot:spring-boot-starter-log4j2')
31 |
32 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.10.3'
33 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.3'
34 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.10.3'
35 |
36 |
37 | implementation('org.springframework.boot:spring-boot-starter-data-jpa')
38 | implementation('org.postgresql:postgresql')
39 |
40 | testCompile("junit:junit")
41 | }
42 |
43 | configurations {
44 | all {
45 | exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
46 | }
47 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholasjackson/demo-vault/0e6bd10b886fb72b83f017895dfa4d2c9eac120c/transform/transform-engine-java/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/transform/transform-engine-java/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 | @rem Execute Gradle
88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
89 |
90 | :end
91 | @rem End local scope for the variables with windows NT shell
92 | if "%ERRORLEVEL%"=="0" goto mainEnd
93 |
94 | :fail
95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
96 | rem the _cmd.exe /c_ return code!
97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
98 | exit /b 1
99 |
100 | :mainEnd
101 | if "%OS%"=="Windows_NT" endlocal
102 |
103 | :omega
104 |
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/Application.java:
--------------------------------------------------------------------------------
1 | package payments;
2 |
3 | import java.util.Arrays;
4 |
5 | import org.springframework.boot.CommandLineRunner;
6 | import org.springframework.boot.SpringApplication;
7 | import org.springframework.boot.autoconfigure.SpringBootApplication;
8 | import org.springframework.context.ApplicationContext;
9 | import org.springframework.context.annotation.Bean;
10 |
11 | @SpringBootApplication
12 | public class Application {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(Application.class, args);
16 | }
17 |
18 | @Bean
19 | public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
20 | return args -> {
21 |
22 | System.out.println("Let's inspect the beans provided by Spring Boot:");
23 |
24 | String[] beanNames = ctx.getBeanDefinitionNames();
25 | Arrays.sort(beanNames);
26 | for (String beanName : beanNames) {
27 | System.out.println(beanName);
28 | }
29 |
30 | };
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/HealthResponse.java:
--------------------------------------------------------------------------------
1 | package payments;
2 |
3 | import com.fasterxml.jackson.annotation.JsonGetter;
4 |
5 | public class HealthResponse {
6 | private String vault;
7 | private String db;
8 |
9 | @JsonGetter("vault")
10 | public String getVault() {
11 | return vault;
12 | }
13 |
14 | @JsonGetter("db")
15 | public String getDB() {
16 | return db;
17 | }
18 |
19 | HealthResponse(String vault, String db) {
20 | this.db = db;
21 | this.vault = vault;
22 | }
23 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/PaymentRequest.java:
--------------------------------------------------------------------------------
1 | package payments;
2 |
3 | import com.fasterxml.jackson.annotation.JsonGetter;
4 | import com.fasterxml.jackson.annotation.JsonSetter;
5 |
6 | class PaymentRequest {
7 |
8 | private String cardNumber;
9 | private String expiration;
10 | private String cv2;
11 |
12 | @JsonGetter("card_number")
13 | public String getCardNumber() {
14 | return this.cardNumber;
15 | }
16 |
17 | @JsonSetter("card_number")
18 | public void setCardNumber(final String number) {
19 | this.cardNumber = number;
20 | }
21 |
22 | @JsonGetter("expiration")
23 | public String getExpiration() {
24 | return this.expiration;
25 | }
26 |
27 | @JsonSetter("expiration")
28 | public void setExpiration(final String exp) {
29 | this.expiration = exp;
30 | }
31 |
32 | @JsonGetter("cv2")
33 | public String getCV2() {
34 | return this.cv2;
35 | }
36 |
37 | @JsonSetter("cv2")
38 | public void setCV2(final String cv2) {
39 | this.cv2 = cv2;
40 | }
41 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/PaymentResponse.java:
--------------------------------------------------------------------------------
1 | package payments;
2 |
3 | import java.util.UUID;
4 |
5 | import com.fasterxml.jackson.annotation.JsonGetter;
6 |
7 | class PaymentResponse {
8 |
9 | private Integer transactionid;
10 |
11 | @JsonGetter("transaction_id")
12 | public Integer getId() {
13 | return transactionid;
14 | }
15 |
16 | PaymentResponse(Integer id) {
17 | this.transactionid = id;
18 | }
19 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/PaymentsController.java:
--------------------------------------------------------------------------------
1 | package payments;
2 |
3 | import java.io.IOException;
4 | import java.sql.SQLException;
5 |
6 | import javax.sql.DataSource;
7 |
8 | import java.sql.Connection;
9 |
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.core.env.Environment;
14 | import org.springframework.web.bind.annotation.GetMapping;
15 | import org.springframework.web.bind.annotation.PostMapping;
16 | import org.springframework.web.bind.annotation.RequestBody;
17 | import org.springframework.web.bind.annotation.ResponseBody;
18 | import org.springframework.web.bind.annotation.RestController;
19 |
20 | import payments.model.Order;
21 | import payments.repository.OrderRepository;
22 | import payments.vault.VaultClient;
23 |
24 | @RestController
25 | public class PaymentsController {
26 | @Autowired
27 | OrderRepository repository;
28 |
29 | @Autowired
30 | DataSource dataSource;
31 |
32 | private Environment env;
33 |
34 | Logger logger = LoggerFactory.getLogger(PaymentsController.class);
35 | VaultClient vaultClient;
36 |
37 | PaymentsController(Environment env) {
38 | this.env = env;
39 | vaultClient = new VaultClient(env.getProperty("vault.token"), env.getProperty("vault.addr"));
40 | }
41 |
42 | @PostMapping("/")
43 | @ResponseBody
44 | public PaymentResponse pay(@RequestBody PaymentRequest request) throws IOException {
45 | // tokenize the thing
46 | String tokenizedNumber = vaultClient.TokenizeCCNumber(request.getCardNumber());
47 |
48 | /*
49 | * Those who came before me
50 | * Lived through their vocations
51 | * From the past until completion
52 | * They'll turn away no more
53 | * And I still find it so hard
54 | * To say what I need to say
55 | * But I'm quite sure that you'll tell me
56 | * Just how I should feel today
57 | */
58 | Order newOrder = repository.save(new Order(tokenizedNumber));
59 |
60 | logger.info("New payment");
61 | logger.info("CC Number: {}", request.getCardNumber());
62 | logger.info("Tokenized Number: {}", tokenizedNumber);
63 | logger.info("DB record ID: {}", newOrder.getId());
64 |
65 | return new PaymentResponse(newOrder.getId());
66 | }
67 |
68 | @GetMapping("/health")
69 | @ResponseBody
70 | public HealthResponse health() throws IOException {
71 | logger.info("Health Check");
72 |
73 | String dbHealth = "OK";
74 | String vaultHealth = "OK";
75 |
76 | try {
77 | Connection con = dataSource.getConnection();
78 |
79 | if (con.isClosed()) {
80 | dbHealth = "Fail";
81 | }
82 | } catch (SQLException e) {
83 | dbHealth = "Fail";
84 | }
85 |
86 | if (!vaultClient.IsOK()) {
87 | vaultHealth = "Fail";
88 | }
89 |
90 | return new HealthResponse(vaultHealth, dbHealth);
91 | }
92 |
93 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/model/Order.java:
--------------------------------------------------------------------------------
1 | package payments.model;
2 |
3 | import java.io.Serializable;
4 | import java.math.BigInteger;
5 | import java.time.LocalDateTime;
6 |
7 | import javax.persistence.Column;
8 | import javax.persistence.Entity;
9 | import javax.persistence.GeneratedValue;
10 | import javax.persistence.GenerationType;
11 | import javax.persistence.Id;
12 | import javax.persistence.Table;
13 |
14 | @Entity
15 | @Table(name = "orders")
16 | public class Order implements Serializable {
17 |
18 | private static final long serialVersionUID = -2343243243242432341L;
19 | @Id
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | private Integer id;
22 |
23 | @Column(name = "card_number")
24 | private String cardNumber;
25 |
26 | @Column(name = "created_at")
27 | private LocalDateTime createdAt;
28 |
29 | @Column(name = "updated_at")
30 | private LocalDateTime updatedAt;
31 |
32 | @Column(name = "deleted_at")
33 | private LocalDateTime deletedAt;
34 |
35 | protected Order() {
36 | }
37 |
38 | public Order(String cardNumber) {
39 | this.cardNumber = cardNumber;
40 | this.createdAt = LocalDateTime.now();
41 | this.updatedAt = LocalDateTime.now();
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return String.format("Customer[id=%d, cardNumber='%s']", id, cardNumber);
47 | }
48 |
49 | public Integer getId() {
50 | return this.id;
51 | }
52 |
53 | public String getCardNumber() {
54 | return cardNumber;
55 | }
56 |
57 | public void setCardNumber(String cardNumber) {
58 | this.cardNumber = cardNumber;
59 | }
60 |
61 | public LocalDateTime getCreatedAt() {
62 | return createdAt;
63 | }
64 |
65 | public void setCreatedAt(LocalDateTime createdAt) {
66 | this.createdAt = createdAt;
67 | }
68 |
69 | public LocalDateTime getUpdatedAt() {
70 | return updatedAt;
71 | }
72 |
73 | public void setUpdatedAt(LocalDateTime updatedAt) {
74 | this.updatedAt = updatedAt;
75 | }
76 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/repository/OrderRepository.java:
--------------------------------------------------------------------------------
1 | package payments.repository;
2 |
3 | import java.util.List;
4 |
5 | import payments.model.Order;
6 |
7 | import org.springframework.data.repository.CrudRepository;
8 |
9 | public interface OrderRepository extends CrudRepository{
10 | List findById(int ID);
11 | List findAll();
12 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/vault/TokenRequest.java:
--------------------------------------------------------------------------------
1 | package payments.vault;
2 |
3 | import com.fasterxml.jackson.annotation.JsonGetter;
4 | import com.fasterxml.jackson.annotation.JsonSetter;
5 |
6 | public class TokenRequest {
7 | private String value;
8 |
9 | public TokenRequest(final String value) {
10 | super();
11 |
12 | this.value = value;
13 | }
14 |
15 | public void setValue(final String value) {
16 | this.value = value;
17 | }
18 |
19 | public String getValue() {
20 | return this.value;
21 | }
22 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/vault/TokenResponse.java:
--------------------------------------------------------------------------------
1 | package payments.vault;
2 |
3 | import com.fasterxml.jackson.annotation.JsonSetter;
4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
5 |
6 | @JsonIgnoreProperties(ignoreUnknown = true)
7 | public class TokenResponse {
8 | private TokenResponseData data;
9 |
10 | public TokenResponse() {
11 | super();
12 | }
13 |
14 | public TokenResponse(TokenResponseData data) {
15 | super();
16 |
17 | this.data = data;
18 | }
19 |
20 | public TokenResponseData getTokenResponseData() {
21 | return this.data;
22 | }
23 |
24 | @JsonSetter("data")
25 | public void setTokenResponseData(TokenResponseData data) {
26 | this.data = data;
27 | }
28 |
29 | @JsonIgnoreProperties(ignoreUnknown = true)
30 | public class TokenResponseData {
31 | private String encodedValue;
32 |
33 | public TokenResponseData() {
34 | super();
35 | }
36 |
37 | public TokenResponseData(String value) {
38 | super();
39 |
40 | this.encodedValue = value;
41 | }
42 |
43 | public String getEncodedValue() {
44 | return this.encodedValue;
45 | }
46 |
47 | @JsonSetter("encoded_value")
48 | public void setEncodedValue(String value) {
49 | this.encodedValue = value;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/java/payments/vault/VaultClient.java:
--------------------------------------------------------------------------------
1 | package payments.vault;
2 |
3 | import java.io.IOException;
4 | import java.net.HttpURLConnection;
5 | import java.net.URL;
6 | import java.io.OutputStream;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import com.fasterxml.jackson.core.JsonProcessingException;
12 | import com.fasterxml.jackson.databind.ObjectMapper;
13 | import com.fasterxml.jackson.databind.SerializationFeature;
14 |
15 | public class VaultClient {
16 | private String token;
17 | private String serverURI;
18 |
19 | Logger logger = LoggerFactory.getLogger(VaultClient.class);
20 |
21 | public VaultClient(String token, String serverURI) {
22 | this.token = token;
23 | this.serverURI = serverURI;
24 | }
25 |
26 | public Boolean IsOK() throws IOException {
27 | URL url = new URL(this.serverURI+ "/v1/sys/health");
28 | HttpURLConnection con = (HttpURLConnection)url.openConnection();
29 | con.setRequestMethod("GET");
30 | con.setRequestProperty("X-Vault-Token", this.token);
31 |
32 | int status = con.getResponseCode();
33 | if (status != 200) {
34 | return false;
35 | }
36 |
37 | return true;
38 | }
39 |
40 |
41 | public String TokenizeCCNumber(String cardNumber) throws IOException, JsonProcessingException {
42 | // create the request
43 | TokenRequest req = new TokenRequest(cardNumber);
44 |
45 | // convert the POJO to a byte array
46 | ObjectMapper mapper = new ObjectMapper();
47 | mapper.enable(SerializationFeature.INDENT_OUTPUT);
48 | byte[] byteRequest = mapper.writeValueAsBytes(req);
49 |
50 | // make a call to vault to process the request
51 | URL url = new URL(this.serverURI+ "/v1/transform/encode/payments");
52 | HttpURLConnection con = (HttpURLConnection)url.openConnection();
53 | con.setDoOutput(true);
54 | con.setRequestMethod("POST");
55 | con.setRequestProperty("Content-Type", "application/json; utf-8");
56 | con.setRequestProperty("Accept", "application/json");
57 | con.setRequestProperty("X-Vault-Token", this.token);
58 |
59 | // write the body
60 | try(OutputStream os = con.getOutputStream()) {
61 | os.write(byteRequest, 0, byteRequest.length);
62 | }
63 |
64 | // read the response
65 | TokenResponse resp = new ObjectMapper()
66 | .readerFor(TokenResponse.class)
67 | .readValue(con.getInputStream());
68 |
69 | return resp.getTokenResponseData().getEncodedValue();
70 | }
71 | }
--------------------------------------------------------------------------------
/transform/transform-engine-java/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 9090
3 |
4 | vault:
5 | addr: http://localhost:8200
6 | token: root
7 |
8 | spring:
9 | jpa:
10 | database: POSTGRESQL
11 | show-sql: true
12 | generate-ddl: true
13 | properties:
14 | hibernate:
15 | jdbc:
16 | lob:
17 | non_contextual_creation: true
18 | datasource:
19 | platform: postgres
20 | url: jdbc:postgresql://localhost:5432/payments
21 | username: root
22 | password: password
--------------------------------------------------------------------------------