External Masking Interface
Audience: Integration Developers
Content Summary: Building a REST Masking and Unmasking service to integrate with Immuta requires implementing a simple REST interface. This page documents this interface and provides example requests and responses.
Use Deterministic IVs/Salt
Use deterministic IVs/salt to ensure the same value is masked consistently throughout the data, as Immuta always pushes down the masked version of the literal when the querying user is exempt from the policy.
Authentication
Immuta can make requests to your External Masking service with one of two authentication methods:
- Username and password authentication: Immuta can send requests with a username and a password in the
Authorization
HTTP header. In this case, your service will need to be able to parse a Basic Authorization Header and validate the credentials sent with it. - PKI Certificate: Immuta can send requests using a CA certificate, a certificate, and a key.
Alternatively, Immuta can make unauthenticated requests to your REST masking service. This is recommended only if you have other security measures in place (e.g., if the service is in an isolated network that's reachable only by your Immuta environment.)
Endpoints
POST /
Description
Warning
The unmask
action allows Immuta to build predicates that can be used to query data that is consistently masked
at rest in the remote database; it does not dynamically mask data at query time.
To dynamically mask data, use Immuta’s standard masking policies.
This endpoint accepts a set of values and a directive to either mask or unmask them.
Request Body
Your service will need to parse and process the following body parameters:
Parameter | Type | Description |
---|---|---|
action |
Enum(MASK , UNMASK ) |
Either MASK or UNMASK the values |
values |
Map(string , String[] ) |
A map of columns containing an array of masked/unmasked values |
Below is an example request payload to mask values in the ssn
and ccn
columns:
{
"action": "MASK",
"values": {
"ssn": ["123-4567-890", "098-4567-321"],
"ccn": ["0000-1111-2222-3333", "5555-6666-7777-8888", "9999-0000-1111-2222"]
}
}
Below is an example request payload to unmask values in the ssn
and ccn
columns:
{
"action": "UNMASK",
"values": {
"ssn": ["AX6YYTURNHtyDkjm", "ue6AUyQYgWiTLAUT"],
"ccn": ["oDpEKiFutdWG46A2", "Tdz3BEVjjfVLrW6x", "nVANLwkgoLUAe4NQ"]
}
}
Response Body
Your service will need to return a map of values that corresponds to the columns and values that were specified in the request. It is important that your service returns the same column keys and that the position of each masked/unmasked value in your response corresponds to the masked/unmasked value from the request.
For example, the following request
curl --location --request POST 'https://your.external.mask.service/' \
--data-raw '{
"action": "MASK",
"values": {
"ssn": ["123-4567-890", "098-4567-321"],
"ccn": ["0000-1111-2222-3333", "5555-6666-7777-8888", "9999-0000-1111-2222"]
}
}'
could return the following body:
{
"ssn": ["AX6YYTURNHtyDkjm", "ue6AUyQYgWiTLAUT"],
"ccn": ["oDpEKiFutdWG46A2", "Tdz3BEVjjfVLrW6x", "nVANLwkgoLUAe4NQ"]
}
Notice that both ssn
and ccn
columns are present and that each of them contains the exact number of values specified
in the request. Immuta will fail to validate responses to its request under the following circumstances:
- The response contains column keys that were not present in the request.
- The response is missing column keys that were present in the request.
- The response doesn't contain the exact number of values for each of the corresponding column keys in the request.
Examples
Below are some very simplistic implementation examples of a service with mask()
and unmask()
functions:
const mask = (value) => {
// TODO: implement your custom masking logic and return a masked value
};
const unmask = (value) => {
// TODO: implement your custom unmasking logic and return an unmasked value
};
// Use as a POST handler:
const externalMaskingHandler = async (request, response) => {
const { action, values } = request.payload;
const result = {};
Object.keys(values).forEach(column => {
result[column] = values[column].map(action === 'MASK' ? mask : unmask);
});
return result;
};
from flask import Flask, request
app = Flask()
def mask(value):
# TODO: implement your custom masking logic and return a masked value
pass
def unmask(value):
# TODO: implement your custom unmasking logic and return an unmasked value
pass
@app.route("/path/to/my/services", methods=["POST"])
def external_masking_handler():
payload = request.get_json()
action = payload.get("action")
values = payload.get("values")
result = {}
for k, v in values:
result[k] = [mask(value) if action == "MASK" else unmask(value) for value in v]
return result