Configure a custom plugin provider

Gateway 5.5+

The custom plugin provider lets you connect Itential Gateway to any secrets manager using a customer-supplied executable. Gateway calls the executable as a subprocess using a stable, documented protocol. The executable retrieves the secret using whatever logic is appropriate for your environment.

Use a custom plugin when:

  • You use HashiCorp Vault KV v1 (only KV v2 is supported by the bundled Vault connector)
  • Your organization uses IAM-based authentication to Vault
  • You need to integrate with a secrets manager not covered by the bundled providers

Plugin protocol

Gateway invokes your plugin with the get subcommand:

$/path/to/your-plugin get

Input

Gateway writes a single JSON object to the plugin’s stdin:

1{
2 "path": "secret/my-app/db-password",
3 "key": "password",
4 "config": {
5 "env": {
6 "VAULT_ADDR": "https://vault.example.com",
7 "VAULT_TOKEN_FILE": "/etc/gateway/vault_token"
8 }
9 }
10}
FieldDescription
pathThe secret path in the external system
keyOptional. When set, Gateway treats the retrieved value as JSON and extracts this field from it.
config.envNon-sensitive configuration from provider registration: URLs, file path references. Never raw credentials.

Output — success

On success, write a JSON object to stdout and exit with code 0:

1{"value": "the-plaintext-secret-value"}

Output — failure

On failure, write a descriptive error message to stderr, write nothing to stdout, and exit with a non-zero code. Gateway includes your stderr output in the operator-facing error.

Security model

Credentials must never pass through stdin or stdout. Store tokens, certificates, and API keys as files on the gateway server. Reference them using environment variables set at provider registration:

$iagctl create secret-provider my-plugin \
> --type plugin \
> --command /usr/local/bin/my-plugin \
> --env VAULT_TOKEN_FILE=/etc/gateway/vault_token \
> --env VAULT_ADDR=https://vault.example.com

Gateway passes the config.env values to the plugin at invocation time. The plugin reads credentials directly from disk—they never travel over the stdin/stdout pipe or appear in Gateway logs.

Reference implementation — Python (HashiCorp Vault KV v1, token auth)

The following example retrieves a secret from HashiCorp Vault KV v1 using a token file. It uses only Python standard library modules.

1#!/usr/bin/env python3
2"""
3Itential Gateway custom plugin — HashiCorp Vault KV v1 with token file.
4Invocation: /path/to/plugin get
5Input: JSON via stdin {"path": "...", "key": "...", "config": {"env": {...}}}
6Output: JSON via stdout {"value": "..."} on success; non-zero exit + stderr on failure
7"""
8import json
9import os
10import sys
11import urllib.request
12import urllib.error
13
14
15def main():
16 try:
17 request_data = json.load(sys.stdin)
18 except json.JSONDecodeError as e:
19 print(f"Failed to parse input: {e}", file=sys.stderr)
20 sys.exit(1)
21
22 path = request_data.get("path")
23 key = request_data.get("key")
24 env_config = request_data.get("config", {}).get("env", {})
25
26 vault_addr = env_config.get("VAULT_ADDR") or os.environ.get("VAULT_ADDR")
27 token_file = env_config.get("VAULT_TOKEN_FILE")
28
29 if not vault_addr:
30 print("VAULT_ADDR is required", file=sys.stderr)
31 sys.exit(1)
32 if not token_file:
33 print("VAULT_TOKEN_FILE is required", file=sys.stderr)
34 sys.exit(1)
35
36 try:
37 with open(token_file) as f:
38 token = f.read().strip()
39 except OSError as e:
40 print(f"Failed to read token file: {e}", file=sys.stderr)
41 sys.exit(1)
42
43 url = f"{vault_addr.rstrip('/')}/v1/{path.lstrip('/')}"
44 req = urllib.request.Request(url, headers={"X-Vault-Token": token})
45
46 try:
47 with urllib.request.urlopen(req) as resp:
48 data = json.loads(resp.read())
49 except urllib.error.HTTPError as e:
50 print(f"Vault request failed ({e.code}): {e.reason}", file=sys.stderr)
51 sys.exit(1)
52 except Exception as e:
53 print(f"Failed to retrieve secret: {e}", file=sys.stderr)
54 sys.exit(1)
55
56 secret_data = data.get("data", {})
57
58 if key:
59 if key not in secret_data:
60 print(f"Key '{key}' not found at path '{path}'", file=sys.stderr)
61 sys.exit(1)
62 value = secret_data[key]
63 else:
64 value = json.dumps(secret_data)
65
66 print(json.dumps({"value": value}))
67
68
69if __name__ == "__main__":
70 main()

Reference implementation — Go (Azure Key Vault, DefaultAzureCredential)

The following example retrieves a secret from Azure Key Vault using the Azure SDK’s DefaultAzureCredential. It demonstrates that the plugin protocol is language-agnostic and works with any external secrets source, including providers not covered by the bundled connectors.

1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "os"
8
9 "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
10 "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
11)
12
13type input struct {
14 Path string `json:"path"`
15 Key string `json:"key"`
16 Config struct {
17 Env map[string]string `json:"env"`
18 } `json:"config"`
19}
20
21func main() {
22 var in input
23 if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
24 fmt.Fprintf(os.Stderr, "failed to parse input: %v\n", err)
25 os.Exit(1)
26 }
27
28 vaultURL := in.Config.Env["AZURE_VAULT_URL"]
29 if vaultURL == "" {
30 fmt.Fprintln(os.Stderr, "AZURE_VAULT_URL is required")
31 os.Exit(1)
32 }
33
34 cred, err := azidentity.NewDefaultAzureCredential(nil)
35 if err != nil {
36 fmt.Fprintf(os.Stderr, "failed to create credential: %v\n", err)
37 os.Exit(1)
38 }
39
40 client, err := azsecrets.NewClient(vaultURL, cred, nil)
41 if err != nil {
42 fmt.Fprintf(os.Stderr, "failed to create Key Vault client: %v\n", err)
43 os.Exit(1)
44 }
45
46 resp, err := client.GetSecret(context.Background(), in.Path, "", nil)
47 if err != nil {
48 fmt.Fprintf(os.Stderr, "failed to retrieve secret: %v\n", err)
49 os.Exit(1)
50 }
51
52 if resp.Value == nil {
53 fmt.Fprintf(os.Stderr, "secret value is nil for path: %s\n", in.Path)
54 os.Exit(1)
55 }
56
57 if err := json.NewEncoder(os.Stdout).Encode(map[string]string{"value": *resp.Value}); err != nil {
58 fmt.Fprintf(os.Stderr, "failed to encode output: %v\n", err)
59 os.Exit(1)
60 }
61}

Register this provider with the vault URL passed as a non-sensitive --env value. DefaultAzureCredential picks up the Azure identity from the gateway server’s environment (managed identity, environment variables, or CLI login):

$iagctl create secret-provider my-azure-kv \
> --type plugin \
> --command /usr/local/bin/azure-kv-plugin \
> --env AZURE_VAULT_URL=https://my-vault.vault.azure.net

Register a custom plugin provider

1

Place the plugin on the gateway server

Copy your plugin executable to the gateway server and make it executable:

$chmod +x /usr/local/bin/my-plugin

If you run Itential Gateway in a cluster, every gateway server needs a copy of the plugin at the same path.

2

Register the provider

Register the provider by specifying the plugin type, the path to the executable, and any non-sensitive configuration as --env flags. Each --env value is passed to the plugin as config.env at invocation time.

$iagctl create secret-provider my-vault-plugin \
> --type plugin \
> --command /usr/local/bin/my-plugin \
> --env VAULT_TOKEN_FILE=/etc/gateway/vault_token \
> --env VAULT_ADDR=https://vault.example.com
3

Verify registration

Confirm the provider appears in the list:

$iagctl get secret-providers

Next steps