From f63f47ae12392d281d62fc1fcb7f92632575cc2c Mon Sep 17 00:00:00 2001 From: Michael Elrom Date: Fri, 28 Aug 2026 15:58:06 -0400 Subject: [PATCH 1/4] refactor(secret-providers): restructure into secret-providers/ subfolder; add AWS Secrets Manager plugin Moves the Azure Key Vault and Delinea Secret Server secret-provider plugins from flat product-root files into a secret-providers/ subfolder (matching the device-drivers/ convention), generalizing the in-repo filename to plugin.py. Adds the AWS Secrets Manager plugin alongside them. Documents a new "Using the Alias in an Integration Model Instance" pattern in all three READMEs, and updates the top-level README's asset-type table, repository structure diagram, and Getting Started steps to include secret-providers as a recognized asset type. BREAKING: anyone with an existing secret-provider registered against /opt/gateway/azure-plugin.py or /opt/gateway/delinea-plugin.py should re-copy the plugin from its new path (Product/secret-providers/plugin.py) before updating; the deployed host-side filename is unchanged. --- AWS/Secrets Manager/README.md | 8 + .../secret-providers/README.md | 317 ++++++++++++++++++ .../secret-providers/plugin.py | 246 ++++++++++++++ Delinea/Secret Server/README.md | 181 +--------- .../Secret Server/secret-providers/README.md | 211 ++++++++++++ .../plugin.py} | 0 Microsoft/Azure Key Vault/README.md | 202 +---------- .../secret-providers/README.md | 232 +++++++++++++ .../plugin.py} | 0 README.md | 7 +- 10 files changed, 1038 insertions(+), 366 deletions(-) create mode 100644 AWS/Secrets Manager/secret-providers/README.md create mode 100644 AWS/Secrets Manager/secret-providers/plugin.py create mode 100644 Delinea/Secret Server/secret-providers/README.md rename Delinea/Secret Server/{delinea-plugin.py => secret-providers/plugin.py} (100%) create mode 100644 Microsoft/Azure Key Vault/secret-providers/README.md rename Microsoft/Azure Key Vault/{azure-plugin.py => secret-providers/plugin.py} (100%) diff --git a/AWS/Secrets Manager/README.md b/AWS/Secrets Manager/README.md index fc21fe0..148583a 100644 --- a/AWS/Secrets Manager/README.md +++ b/AWS/Secrets Manager/README.md @@ -10,12 +10,14 @@ This project provides an OpenAPI spec for automating against the AWS Secrets Man - [OpenAPIs](#openapis) - [`aws_secrets_manager-latest.json`](#aws_secrets_manager-latestjson) - [`aws_secrets_manager-2017-10-17.json`](#aws_secrets_manager-2017-10-17json) +- [Secret Providers](#secret-providers) ## Contents | Asset | Description | |---|---| | [OpenAPIs/](./OpenAPIs/) | AWS Secrets Manager API OpenAPI spec — curated `-latest` plus the full dated version | +| [secret-providers/](./secret-providers/) | IG5 custom secret-provider plugin — resolves AWS Secrets Manager secrets into Gateway secret aliases, usable in device inventory credentials and Gateway-executed Integration Model instances | ## Requirements @@ -63,3 +65,9 @@ Operations included, by category: ### `aws_secrets_manager-2017-10-17.json` Full spec, converted in-house from AWS's official service model, for AWS Secrets Manager API version 2017-10-17 (23 operations) — the entire upstream API surface as AWS defines it. See `aws_secrets_manager-latest.json` above for the curated automation spec. + +## Secret Providers + +Separately from automating *against* AWS Secrets Manager via the Integration Model above, this product also ships a custom secret-provider plugin so **Itential Gateway** can resolve credentials *from* AWS Secrets Manager at runtime — for device inventory passwords or Gateway-executed Integration Model instances — instead of storing them in Gateway's own encrypted store. + +See [secret-providers/README.md](./secret-providers/README.md) for full setup details: three ways to authenticate to AWS (EC2 instance role, IAM Roles Anywhere, or a static access key), registration steps, and how to reference the resulting alias. diff --git a/AWS/Secrets Manager/secret-providers/README.md b/AWS/Secrets Manager/secret-providers/README.md new file mode 100644 index 0000000..9909ec0 --- /dev/null +++ b/AWS/Secrets Manager/secret-providers/README.md @@ -0,0 +1,317 @@ +# AWS Secrets Manager — Custom Secret Provider for Itential Gateway + +Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). AWS Secrets Manager isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. + +This is a working example: a Python plugin for AWS Secrets Manager, **three different ways to authenticate it to AWS**, the registration steps, and how to reference the resulting alias from a device inventory. + +## Table of Contents + +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [The Plugin](#the-plugin) +- [Authenticating to AWS: Three Patterns](#authenticating-to-aws-three-patterns) + - [Pattern 1 — EC2 Instance Role](#pattern-1--ec2-instance-role-best-when-itential-gateway-runs-on-ec2) + - [Pattern 2 — IAM Roles Anywhere](#pattern-2--iam-roles-anywhere-recommended-for-on-prem--non-ec2) + - [Pattern 3 — Static IAM User Access Key](#pattern-3--static-iam-user-access-key-simplest-quick-start) + - [Cross-account note](#cross-account-note) +- [Registering the Provider and Alias](#registering-the-provider-and-alias) +- [Referencing the Alias](#referencing-the-alias) +- [Using the Alias in an Integration Model Instance](#using-the-alias-in-an-integration-model-instance) +- [Verifying It's Working](#verifying-its-working) +- [Adapting This Example](#adapting-this-example) +- [References](#references) + +## Architecture + +``` +Itential Platform (Inventory Manager / device sync) + │ device attribute: "itential_password": "$GATEWAYSECRET_(AWS-IOSXE-PASSWORD)" + ▼ +Itential Gateway ──(resolves alias)──▶ aws-plugin.py ──(SigV4-signed request)──▶ AWS Secrets Manager + │ + ▼ +Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password +``` + +Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. + +The interesting part of this integration isn't the Secrets Manager API call itself (a single `GetSecretValue`) — it's **how the plugin process authenticates to AWS**, since that varies a lot depending on where Itential Gateway is actually running. This example implements and tests three patterns and lets you pick per-provider. + +## Prerequisites + +- Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). +- Python 3 on the Itential Gateway host (already required by Itential Gateway itself). No third-party Python packages — the plugin hand-implements AWS Signature Version 4 using only the standard library, the same convention used by the Azure Key Vault and Delinea Secret Server plugins in this repo. +- An AWS Secrets Manager secret to read. A structured JSON secret (e.g. `{"username": "...", "password": "..."}`) works well with the `--key` option shown below; a plain string secret works too if you omit `--key`. + +## The Plugin + +Itential Gateway invokes the plugin as ` get`, writing a JSON request to stdin and reading a JSON response from stdout. Configuration (the non-sensitive values registered with the provider) arrives via the JSON on stdin, in `config.env` — the plugin process does not otherwise inherit Itential Gateway's environment. + +**Request (stdin):** +```json +{ + "path": "AWS-IOSXE-PASSWORD", + "key": "password", + "config": { + "env": { + "AWS_REGION": "us-east-1", + "...": "... one of the three auth patterns below ..." + } + } +} +``` + +**Response (stdout, exit 0):** +```json +{"value": "the-plaintext-secret"} +``` + +On failure: write a message to stderr and exit non-zero. + +See [`plugin.py`](./plugin.py) for the full implementation. Copy it to the Itential Gateway host — renaming it to something provider-specific like `aws-plugin.py` if you'll have more than one provider's plugin on the same host — and make it executable: + +```bash +cp plugin.py /opt/gateway/aws-plugin.py +chmod +x /opt/gateway/aws-plugin.py +``` + +`path` is the secret's **name or full ARN** — see the [cross-account note](#cross-account-note) for why the ARN matters. If the secret's `SecretString` is a JSON object and `key` is given, the plugin parses it and pulls out that one field; otherwise it returns the whole string. + +## Authenticating to AWS: Three Patterns + +Every pattern ends with the plugin process having, in one form or another, an AWS access key + secret key (+ optional session token) to sign the `GetSecretValue` request with. The plugin tries these in order, so the `config.env` you register with the provider determines which one actually runs: + +1. `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` / `AWS_SECRET_ACCESS_KEY_FILE` — static keys. +2. `AWS_CREDENTIAL_PROCESS` — a shell command that prints the [standard `credential_process` JSON](https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html) (`{"Version":1,"AccessKeyId":...,"SecretAccessKey":...,"SessionToken":...,"Expiration":...}`). This is how Pattern 2 (IAM Roles Anywhere) plugs in — AWS's own `aws_signing_helper credential-process` command produces exactly this output. +3. The EC2 instance's attached IAM role, fetched via IMDSv2. No configuration needed — this is the fallback when neither of the above is set. + +### Pattern 1 — EC2 Instance Role (best when Itential Gateway runs on EC2) + +If Itential Gateway itself runs on an EC2 instance, attach an IAM role to it and grant that role `secretsmanager:GetSecretValue` scoped to the secret's ARN. No credentials are configured on the provider at all — the plugin's IMDSv2 fallback picks them up automatically. + +```bash +iagctl create secret-provider aws-secretsmanager-instancerole-plugin \ + --type plugin \ + --command /opt/gateway/aws-plugin.py \ + --env AWS_REGION=us-east-1 \ + --description "AWS Secrets Manager via EC2 instance role (IMDSv2)" +``` + +IAM policy on the instance's role, scoped to one secret: +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": "secretsmanager:GetSecretValue", + "Resource": "arn:aws:secretsmanager:::secret:-" + }] +} +``` + +This is the closest analog to Azure's Managed Identity option — nothing to rotate, nothing on disk. It only applies if Itential Gateway is on EC2, which most customer deployments aren't (see Pattern 2). It's also the pattern most affected by the [cross-account note](#cross-account-note) below. + +### Pattern 2 — IAM Roles Anywhere (recommended for on-prem / non-EC2) + +**This is the pattern most customers will actually use** — Itential Gateway is typically deployed on-prem or in a customer's own VM, not as an EC2 instance. [IAM Roles Anywhere](https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html) lets a non-AWS server exchange an X.509 client certificate for short-lived AWS credentials, so there's still no long-lived AWS access key sitting on disk — only a certificate and private key, which are easier to scope, rotate, and revoke. + +**One-time AWS setup** (in the account that owns the secret): + +```bash +# 1. Trust anchor: register your CA (can be your own self-managed CA — no +# AWS Private CA required/billed — or an AWS Private CA if you have one) +aws rolesanywhere create-trust-anchor \ + --name my-iag5-trust-anchor \ + --source '{"sourceType":"CERTIFICATE_BUNDLE","sourceData":{"x509CertificateData":"'"$(cat ca-cert.pem)"'"}}' \ + --enabled + +# 2. IAM role that Roles Anywhere is allowed to hand out sessions for +cat > role-trust-policy.json <"}} + }] +} +EOF +aws iam create-role --role-name iag5-secretsmanager-reader --assume-role-policy-document file://role-trust-policy.json +aws iam put-role-policy --role-name iag5-secretsmanager-reader --policy-name read-secret \ + --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"secretsmanager:GetSecretValue","Resource":""}]}' + +# 3. Profile: associates the role with the trust anchor +aws rolesanywhere create-profile \ + --name iag5-secretsmanager-reader \ + --role-arns arn:aws:iam:::role/iag5-secretsmanager-reader \ + --enabled +``` + +Generate a CA + client certificate (a self-managed CA via `openssl` is fine for this — Roles Anywhere doesn't require AWS Private CA): + +```bash +openssl genrsa -out ca-key.pem 4096 +openssl req -x509 -new -nodes -key ca-key.pem -sha256 -days 3650 \ + -subj "/CN=my-iag5-ca" -addext "basicConstraints=critical,CA:TRUE" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" -out ca-cert.pem + +openssl genrsa -out client-key.pem 2048 +openssl req -new -key client-key.pem -subj "/CN=my-iag5-host" -out client.csr +openssl x509 -req -in client.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \ + -days 825 -sha256 -extfile <(printf "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth") \ + -out client-cert.pem +``` + +On the Itential Gateway host, copy `client-cert.pem`, `client-key.pem` (mode `400`), and `ca-cert.pem`, plus the [`aws_signing_helper`](https://docs.aws.amazon.com/rolesanywhere/latest/userguide/credential-helper.html) binary for your platform (verify the published SHA256 checksum after downloading). Then register the provider with `AWS_CREDENTIAL_PROCESS` pointing at it: + +```bash +iagctl create secret-provider aws-secretsmanager-rolesanywhere-plugin \ + --type plugin \ + --command /opt/gateway/aws-plugin.py \ + --env AWS_REGION=us-east-1 \ + --env "AWS_CREDENTIAL_PROCESS=/opt/gateway/aws_signing_helper credential-process --certificate /etc/gateway/aws-rolesanywhere-client-cert.pem --private-key /etc/gateway/aws-rolesanywhere-client-key.pem --intermediates /etc/gateway/aws-rolesanywhere-ca-cert.pem --trust-anchor-arn --profile-arn --role-arn " \ + --description "AWS Secrets Manager via IAM Roles Anywhere" +``` + +Each `get` call re-runs `credential-process`, which does a full `CreateSession` round trip (adds a little latency, but keeps every call using freshly-issued, short-lived credentials — never a cached long-lived key). + +### Pattern 3 — Static IAM User Access Key (simplest, quick-start) + +The same shape the Azure/Delinea plugins in this repo already use for their credentials — a scoped IAM user with one access key, the secret half stored in a locked-down file: + +```bash +aws iam create-user --user-name svc-iag5-secretsmanager-reader +aws iam put-user-policy --user-name svc-iag5-secretsmanager-reader --policy-name read-secret \ + --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"secretsmanager:GetSecretValue","Resource":""}]}' +aws iam create-access-key --user-name svc-iag5-secretsmanager-reader +``` + +Store the secret half in a file, never as a raw `--env` value (which would be stored in the gateway's provider configuration in plaintext): + +```bash +sudo tee /etc/gateway/aws_secret_access_key <<< 'the-secret-access-key' > /dev/null +sudo chown itential:itential /etc/gateway/aws_secret_access_key +sudo chmod 400 /etc/gateway/aws_secret_access_key +``` + +```bash +iagctl create secret-provider aws-secretsmanager-static-plugin \ + --type plugin \ + --command /opt/gateway/aws-plugin.py \ + --env AWS_REGION=us-east-1 \ + --env AWS_ACCESS_KEY_ID= \ + --env AWS_SECRET_ACCESS_KEY_FILE=/etc/gateway/aws_secret_access_key \ + --description "AWS Secrets Manager via static IAM user access key" +``` + +Simplest to set up, works regardless of where Itential Gateway runs — but it's a long-lived credential that needs manual rotation, so prefer Pattern 2 for anything on-prem and Pattern 1 when running on EC2. + +### Cross-account note + +If the IAM identity calling `GetSecretValue` (the EC2 instance role, the Roles Anywhere role, or the IAM user) lives in a **different AWS account** than the secret: + +- You must pass the secret's **full ARN** as `path`, not just its friendly name — cross-account calls can't resolve a bare name, only an ARN. +- The secret's owning account needs a [resource policy](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_resource-based-policies.html) naming the external principal, in addition to that principal's own identity policy. +- **The secret must be encrypted with a customer-managed KMS key**, not the default `aws/secretsmanager` key. AWS hard-blocks cross-account decryption of secrets under the default key — no IAM policy on either side can override this. The KMS key's key policy also needs a statement granting the external principal `kms:Decrypt` + `kms:DescribeKey`. + +None of this applies when the calling identity and the secret share an account, which is the common case and why Patterns 2 and 3 above are shown same-account. + +## Registering the Provider and Alias + +Pick one pattern above and register its provider, then create the alias mapping a friendly name to the specific secret: + +```bash +iagctl create secret AWS-IOSXE-PASSWORD \ + --provider aws-secretsmanager-rolesanywhere-plugin \ + --secret AWS-IOSXE-PASSWORD \ + --key password +``` + +Verify: +```bash +iagctl get secret-providers +iagctl describe secret AWS-IOSXE-PASSWORD +``` + +`describe secret` only shows the alias's metadata (provider/secret/key) — it never displays the resolved value. + +## Referencing the Alias + +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a Cisco IOS device synced into Itential Platform: + +```json +{ + "name": "device-name", + "attributes": { + "itential_host": "10.0.25.20", + "itential_port": 22, + "itential_driver": "netmiko", + "itential_platform": "cisco_ios", + "itential_user": "itential", + "itential_password": "$GATEWAYSECRET_(AWS-IOSXE-PASSWORD)" + } +} +``` + +Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Secrets Manager on every run rather than stored anywhere on the platform. + +## Using the Alias in an Integration Model Instance + +`$GATEWAYSECRET_(alias-name)` isn't limited to device inventory — it also resolves in an **Integration Model instance's** credential fields, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster. + +In the instance's connectivity config: +- Set `proxyOverride.executionMode` to `cluster_no_proxy` or `proxy` — **not** `direct`. `direct` means Platform makes the call itself, Gateway is never involved, and the alias won't resolve. +- Optionally set `clusterOverride` to target a specific Gateway cluster instead of the Admin Essentials default. + +With that in place, reference the alias in the instance's security/credential value the same way you would in an inventory attribute: + +```json +{ + "security": { + "apiKey": { + "value": "$GATEWAYSECRET_(AWS-API-TOKEN)" + } + }, + "proxyOverride": { + "overrideProxyBehavior": true, + "executionMode": "cluster_no_proxy", + "proxy": { + "auth": { + "authMode": "none" + } + } + } +} +``` + +Gateway resolves the alias just before the outbound call executes; the real token is never sent back to Platform. It's the same secret-provider plugin doing the work — only the caller changed, from a device driver connection to an API integration. + +## Verifying It's Working + +- `iagctl get secret-providers` shows your provider(s). +- `iagctl describe secret ` shows the correct provider/secret/key. +- The gateway log (`journalctl -u iagctl`) shows a line like `secret_resolution alias="..." provider="..." path="..." outcome=success` for each call that uses the alias. +- The device/API call using the resolved secret succeeds end to end. +- To test a provider directly without going through a real device call, pipe a request straight into the plugin using the exact `config.env` you registered — this is exactly what Itential Gateway does internally: + ```bash + echo '{"path":"AWS-IOSXE-PASSWORD","key":"password","config":{"env":{"AWS_REGION":"us-east-1"}}}' \ + | /opt/gateway/aws-plugin.py get + ``` + +## Adapting This Example + +- **Multiple secrets**: register one provider, then create as many `secret` aliases as you need against it — each just needs its own `--secret `. +- **Plain-string secrets**: omit `--key` when creating the alias; the plugin returns the whole `SecretString` value as-is. +- **Session tokens with static keys**: if you're using temporary credentials rather than a permanent IAM user (e.g. from `aws sts assume-role`), also set `AWS_SESSION_TOKEN` — the plugin picks it up automatically alongside `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`. +- **Any other `credential_process`-compatible tool**: `AWS_CREDENTIAL_PROCESS` isn't specific to Roles Anywhere — any command that prints the standard JSON (SSO, a custom broker, etc.) works the same way. + +## References + +- [Itential Gateway — Configure a Custom Plugin Secret Provider](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) +- [Itential Gateway — Manage Secret Aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases) +- [AWS Secrets Manager — GetSecretValue API reference](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html) +- [AWS Signature Version 4 signing process](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html) +- [IAM Roles Anywhere — credential helper](https://docs.aws.amazon.com/rolesanywhere/latest/userguide/credential-helper.html) +- [AWS SDKs — sourcing credentials from an external process](https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html) +- [Secrets Manager cross-account access with a customer-managed key](https://docs.aws.amazon.com/secretsmanager/latest/userguide/security-encryption.html#security-encryption-cross-account) diff --git a/AWS/Secrets Manager/secret-providers/plugin.py b/AWS/Secrets Manager/secret-providers/plugin.py new file mode 100644 index 0000000..1caf11e --- /dev/null +++ b/AWS/Secrets Manager/secret-providers/plugin.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +"""IAG custom secret-provider plugin for AWS Secrets Manager. + +IAG invokes this as `aws-plugin.py get`, writing a JSON request to +stdin (`{"path": "", "key": "", "config": +{"env": {...}}}`) and reading a JSON response from stdout +(`{"value": "..."}`) on success. On failure, write a message to stderr +and exit non-zero. +""" +import sys +import json +import os +import hmac +import hashlib +import datetime +import shlex +import subprocess +import urllib.request +import urllib.error + +IMDS_BASE = "http://169.254.169.254" + + +def fail(msg): + print(msg, file=sys.stderr) + sys.exit(1) + + +def imds_get(path, headers=None, method="GET"): + req = urllib.request.Request(f"{IMDS_BASE}{path}", headers=headers or {}, method=method) + return urllib.request.urlopen(req, timeout=2).read().decode() + + +def instance_role_credentials(): + """Temporary credentials for the EC2 instance profile, via IMDSv2. + + Returns None (rather than raising) on any failure so callers can treat + "not running on EC2" / "no role attached" as just another reason to fall + through to a clearer, actionable error message. + """ + try: + token = imds_get( + "/latest/api/token", + headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + method="PUT", + ) + role = imds_get( + "/latest/meta-data/iam/security-credentials/", + headers={"X-aws-ec2-metadata-token": token}, + ).strip() + if not role: + return None + creds = json.loads( + imds_get( + f"/latest/meta-data/iam/security-credentials/{role}", + headers={"X-aws-ec2-metadata-token": token}, + ) + ) + return creds["AccessKeyId"], creds["SecretAccessKey"], creds.get("Token") + except Exception: + return None + + +def read_secret_key(cfg_env): + key_file = cfg_env.get("AWS_SECRET_ACCESS_KEY_FILE") or os.environ.get("AWS_SECRET_ACCESS_KEY_FILE") + if key_file: + try: + with open(key_file, "r") as f: + return f.read().strip() + except OSError as e: + fail(f"failed to read AWS_SECRET_ACCESS_KEY_FILE: {e}") + return cfg_env.get("AWS_SECRET_ACCESS_KEY") or os.environ.get("AWS_SECRET_ACCESS_KEY") + + +def credential_process_credentials(cfg_env): + """Run an external credential process (e.g. `aws_signing_helper + credential-process ...` for IAM Roles Anywhere) and parse its standard + JSON output — the same `credential_process` contract the AWS CLI/SDKs + support. See https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html + """ + command = cfg_env.get("AWS_CREDENTIAL_PROCESS") or os.environ.get("AWS_CREDENTIAL_PROCESS") + if not command: + return None + try: + result = subprocess.run( + shlex.split(command), capture_output=True, text=True, timeout=30, check=True + ) + except (subprocess.SubprocessError, OSError) as e: + fail(f"AWS_CREDENTIAL_PROCESS failed: {e}") + try: + creds = json.loads(result.stdout) + return creds["AccessKeyId"], creds["SecretAccessKey"], creds.get("SessionToken") + except (json.JSONDecodeError, KeyError) as e: + fail(f"AWS_CREDENTIAL_PROCESS returned unparseable output: {e}") + + +def resolve_credentials(cfg_env): + """Three ways to authenticate, tried in order — mirrors the AWS SDK's own + default provider chain: + 1. Static keys (env/file) — simplest, works anywhere, needs manual rotation. + 2. AWS_CREDENTIAL_PROCESS — an external command (e.g. `aws_signing_helper + credential-process` for IAM Roles Anywhere) that returns short-lived + credentials. Recommended for on-prem/non-EC2 hosts: no long-lived + AWS keys stored anywhere, only an X.509 client certificate. + 3. EC2 instance role via IMDSv2 — recommended only when Itential Gateway + itself runs on an EC2 instance; same idea as Azure Managed Identity + for the Key Vault plugin. + """ + access_key = cfg_env.get("AWS_ACCESS_KEY_ID") or os.environ.get("AWS_ACCESS_KEY_ID") + secret_key = read_secret_key(cfg_env) + session_token = cfg_env.get("AWS_SESSION_TOKEN") or os.environ.get("AWS_SESSION_TOKEN") + if access_key and secret_key: + return access_key, secret_key, session_token + + process_creds = credential_process_credentials(cfg_env) + if process_creds: + return process_creds + + role_creds = instance_role_credentials() + if role_creds: + return role_creds + fail( + "no AWS credentials: set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY(_FILE), or " + "AWS_CREDENTIAL_PROCESS, in the provider's env, or run Itential Gateway on an " + "EC2 instance with an attached role" + ) + + +def sigv4_headers(method, host, region, service, payload, access_key, secret_key, session_token, amz_target): + """Build the signed request headers for a single AWS Signature Version 4 + request. See https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html + """ + now = datetime.datetime.now(datetime.timezone.utc) + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + payload_hash = hashlib.sha256(payload).hexdigest() + + sign_headers = { + "content-type": "application/x-amz-json-1.1", + "host": host, + "x-amz-date": amz_date, + "x-amz-target": amz_target, + } + if session_token: + sign_headers["x-amz-security-token"] = session_token + + signed_header_names = ";".join(sorted(sign_headers)) + canonical_headers = "".join(f"{k}:{sign_headers[k]}\n" for k in sorted(sign_headers)) + canonical_request = "\n".join( + ["POST", "/", "", canonical_headers, signed_header_names, payload_hash] + ) + + credential_scope = f"{date_stamp}/{region}/{service}/aws4_request" + string_to_sign = "\n".join( + [ + "AWS4-HMAC-SHA256", + amz_date, + credential_scope, + hashlib.sha256(canonical_request.encode()).hexdigest(), + ] + ) + + def hmac_sha256(key, msg): + return hmac.new(key, msg.encode(), hashlib.sha256).digest() + + k_date = hmac_sha256(("AWS4" + secret_key).encode(), date_stamp) + k_region = hmac_sha256(k_date, region) + k_service = hmac_sha256(k_region, service) + k_signing = hmac_sha256(k_service, "aws4_request") + signature = hmac.new(k_signing, string_to_sign.encode(), hashlib.sha256).hexdigest() + + authorization = ( + f"AWS4-HMAC-SHA256 Credential={access_key}/{credential_scope}, " + f"SignedHeaders={signed_header_names}, Signature={signature}" + ) + + # `host` is left out here — urllib sets it automatically from the URL, + # and sending it twice would create a duplicate Host header on the wire. + request_headers = {k: v for k, v in sign_headers.items() if k != "host"} + request_headers["authorization"] = authorization + return request_headers + + +def main(): + if len(sys.argv) < 2 or sys.argv[1] != "get": + fail("usage: aws-plugin.py get") + + try: + req_in = json.load(sys.stdin) + except json.JSONDecodeError as e: + fail(f"failed to parse stdin: {e}") + + path = req_in.get("path") + key = req_in.get("key", "") + cfg_env = req_in.get("config", {}).get("env", {}) + + region = cfg_env.get("AWS_REGION") or os.environ.get("AWS_REGION") + if not region: + fail("AWS_REGION must be set in the plugin's environment") + if not path: + fail("no secret name or ARN provided") + + access_key, secret_key, session_token = resolve_credentials(cfg_env) + + host = f"secretsmanager.{region}.amazonaws.com" + payload = json.dumps({"SecretId": path}).encode() + headers = sigv4_headers( + "POST", host, region, "secretsmanager", payload, + access_key, secret_key, session_token, "secretsmanager.GetSecretValue", + ) + + req = urllib.request.Request(f"https://{host}/", data=payload, headers=headers, method="POST") + try: + resp = urllib.request.urlopen(req, timeout=15) + body = resp.read() + except urllib.error.HTTPError as e: + detail = e.read().decode(errors="replace") + fail(f"secret fetch failed: status {e.code}: {detail}") + except Exception as e: + fail(f"secret fetch failed: {e}") + + try: + result = json.loads(body) + except json.JSONDecodeError: + fail("failed to parse secret response") + + value = result.get("SecretString") + if value is None: + fail("secret has no SecretString (binary secrets are not supported)") + + # Optional: if the secret's value is itself a JSON object (e.g. a + # username+password pair stored as one secret) and `key` was given, + # pull that one field out. Otherwise return the value as-is. + if key: + try: + parsed = json.loads(value) + if isinstance(parsed, dict) and key in parsed: + value = parsed[key] + except (json.JSONDecodeError, TypeError): + pass + + print(json.dumps({"value": value})) + + +if __name__ == "__main__": + main() diff --git a/Delinea/Secret Server/README.md b/Delinea/Secret Server/README.md index d2dabc6..10a82fa 100644 --- a/Delinea/Secret Server/README.md +++ b/Delinea/Secret Server/README.md @@ -1,178 +1,15 @@ -# Delinea Secret Server — Custom Secret Provider for Itential Gateway +# Delinea Secret Server -Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Delinea Secret Server isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. +Delinea Secret Server is a privileged access management (PAM) platform for storing and controlling access to credentials, keys, and secrets. -This is a working example: a Python plugin for Delinea Secret Server Cloud, the registration steps, and how to reference the resulting alias from a device inventory (for example, a NetBox-synced inventory in Itential Platform). +This project provides a custom secret-provider plugin so Itential Gateway can resolve credentials from Secret Server Cloud at runtime, instead of storing them in Gateway's own encrypted store. -## Table of Contents +**Requirements:** Itential Gateway >= 5.5 -- [Architecture](#architecture) -- [Prerequisites](#prerequisites) -- [How Delinea Secret Server API Access Works](#how-delinea-secret-server-api-access-works) -- [The Plugin](#the-plugin) -- [Storing the Password](#storing-the-password) -- [Registering the Provider and Alias](#registering-the-provider-and-alias) -- [Referencing the Alias](#referencing-the-alias) -- [Verifying It's Working](#verifying-its-working) -- [Adapting This Example](#adapting-this-example) -- [References](#references) +## Contents -## Architecture +| Asset | Description | +|---|---| +| [secret-providers/](./secret-providers/) | IG5 custom secret-provider plugin — resolves Delinea Secret Server secrets into Gateway secret aliases, usable in device inventory credentials and Gateway-executed Integration Model instances | -``` -Itential Platform (Inventory Manager / device sync) - │ device attribute: "itential_password": "$GATEWAYSECRET_(DELINEA-IOSXE-PASSWORD)" - ▼ -Itential Gateway ──(resolves alias)──▶ delinea-plugin.py ──(OAuth2 password grant)──▶ Delinea Secret Server Cloud - │ - ▼ -Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password -``` - -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. - -## Prerequisites - -- Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). See the [custom plugin provider docs](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) for the full reference. -- A Delinea Secret Server Cloud tenant (`https://.secretservercloud.com`) and a service account with access to the target secret(s). -- Python 3 on the Itential Gateway host (already required by Itential Gateway itself). - -## How Delinea Secret Server API Access Works - -Secret Server Cloud uses OAuth2 password grant, not a static API key: - -1. `POST {base_url}/oauth2/token` with form body `grant_type=password&username=&password=` (add `domain=` only if not using the default `Local` domain). Returns `access_token` + `refresh_token`. -2. `GET {base_url}/api/v1/secrets/{id}/fields/{fieldSlug}` with `Authorization: Bearer ` to fetch a specific field's value (e.g. `password`, `username`). This endpoint returns the value as a JSON-encoded string literal (e.g. `"my-password"`, quotes included) — decode it with a JSON parser rather than using the raw response bytes directly. - -The secret `{id}` is the numeric ID Secret Server assigns (visible in the secret's URL, e.g. `.../app/#/secrets/382/general`) — the API addresses secrets by ID, not by name. - -## The Plugin - -Itential Gateway invokes the plugin as ` get`, writing a JSON request to stdin and reading a JSON response from stdout. Configuration (the non-sensitive values registered with the provider) arrives via the JSON on stdin, in `config.env` — the plugin process does not otherwise inherit Itential Gateway's environment. - -**Request (stdin):** -```json -{ - "path": "382", - "key": "password", - "config": { - "env": { - "DELINEA_BASE_URL": "https://.secretservercloud.com", - "DELINEA_USERNAME": "svc-account", - "DELINEA_DOMAIN": "Local", - "DELINEA_PASSWORD_FILE": "/etc/gateway/delinea_password" - } - } -} -``` - -**Response (stdout, exit 0):** -```json -{"value": "the-plaintext-secret"} -``` - -On failure: write a message to stderr and exit non-zero. - -See [`delinea-plugin.py`](./delinea-plugin.py) for the full implementation. Copy it to the Itential Gateway host and make it executable: - -```bash -chmod +x /opt/gateway/delinea-plugin.py -``` - -## Storing the Password - -Never pass the Secret Server account's password as a raw `--env` value — it would be stored in the gateway's provider configuration. Instead, write it to a file the plugin reads at runtime, and only pass the file path via `--env`: - -```bash -sudo tee /etc/gateway/delinea_password <<< 'the-service-account-password' > /dev/null -sudo chown itential:itential /etc/gateway/delinea_password -sudo chmod 400 /etc/gateway/delinea_password -``` - -The file should contain just the raw password as a single line of plain text — no quotes, no `KEY=value` formatting: - -``` -$ cat /etc/gateway/delinea_password -the-service-account-password -``` - -The plugin reads the file's contents directly and strips any surrounding whitespace/newline, so a trailing newline (which `tee`/most editors add automatically) is fine. - -## Registering the Provider and Alias - -Full CLI reference: [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). - -```bash -# Provider: registers the plugin and its non-sensitive config -iagctl create secret-provider delinea-plugin \ - --type plugin \ - --command /opt/gateway/delinea-plugin.py \ - --env DELINEA_BASE_URL=https://.secretservercloud.com \ - --env DELINEA_USERNAME=svc-account \ - --env DELINEA_DOMAIN=Local \ - --env DELINEA_PASSWORD_FILE=/etc/gateway/delinea_password \ - --description "Delinea Secret Server Cloud via OAuth2 password grant" - -# Alias: maps a friendly name to a specific secret + field on that provider -iagctl create secret DELINEA-IOSXE-PASSWORD \ - --provider delinea-plugin \ - --secret 382 \ - --key password -``` - -Verify: -```bash -iagctl get secret-providers -iagctl describe secret DELINEA-IOSXE-PASSWORD -``` - -`describe secret` only shows the alias's metadata (provider/secret/key) — it never displays the resolved value. - -## Referencing the Alias - -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a device synced from NetBox into Itential Platform: - -```json -{ - "name": "device-name", - "attributes": { - "itential_host": "10.0.25.20", - "itential_port": 22, - "itential_driver": "netmiko", - "itential_platform": "cisco_xe", - "itential_user": "itential", - "itential_password": "$GATEWAYSECRET_(DELINEA-IOSXE-PASSWORD)", - "itential_driver_options": { - "netmiko": { - "banner_timeout": 60, - "conn_timeout": 60, - "enable_fast_mode": true, - "global_delay_factor": 3, - "read_timeout_override": 600, - "session_timeout": 300 - } - } - } -} -``` - -Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Secret Server on every run rather than stored anywhere on the platform. - -## Verifying It's Working - -- `iagctl get secret-providers` shows your provider. -- `iagctl describe secret ` shows the correct provider/secret/key. -- The gateway log (`journalctl -u iagctl`) shows a line like `secret_resolution alias="..." provider="..." path="..." outcome=success` for each call that uses the alias. -- The device/API call using the resolved secret succeeds end to end. - -## Adapting This Example - -- **Different secret templates**: change `--key` to the field slug you need (e.g. `username`, `notes`), or omit `--key` when creating the alias to return the full secret as a JSON object. -- **Multiple secrets**: register one provider, then create as many `secret` aliases as you need against it — each just needs its own `--secret ` (and optionally `--key`). -- **Other Delinea deployments**: this targets Secret Server Cloud's OAuth2 password grant. An on-premises Secret Server instance with a different auth model (e.g. Windows-integrated auth) would need the OAuth section of the plugin adjusted accordingly. - -## References - -- [Itential Gateway — Configure a Custom Plugin Secret Provider](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) -- [Itential Gateway — Manage Secret Aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases) -- [Delinea Secret Server REST API documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/rest-api/index.htm) +See [secret-providers/README.md](./secret-providers/README.md) for full setup details: how Secret Server API access works, registration steps, and how to reference the resulting alias. diff --git a/Delinea/Secret Server/secret-providers/README.md b/Delinea/Secret Server/secret-providers/README.md new file mode 100644 index 0000000..441981a --- /dev/null +++ b/Delinea/Secret Server/secret-providers/README.md @@ -0,0 +1,211 @@ +# Delinea Secret Server — Custom Secret Provider for Itential Gateway + +Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Delinea Secret Server isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. + +This is a working example: a Python plugin for Delinea Secret Server Cloud, the registration steps, and how to reference the resulting alias from a device inventory (for example, a NetBox-synced inventory in Itential Platform). + +## Table of Contents + +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [How Delinea Secret Server API Access Works](#how-delinea-secret-server-api-access-works) +- [The Plugin](#the-plugin) +- [Storing the Password](#storing-the-password) +- [Registering the Provider and Alias](#registering-the-provider-and-alias) +- [Referencing the Alias](#referencing-the-alias) +- [Using the Alias in an Integration Model Instance](#using-the-alias-in-an-integration-model-instance) +- [Verifying It's Working](#verifying-its-working) +- [Adapting This Example](#adapting-this-example) +- [References](#references) + +## Architecture + +``` +Itential Platform (Inventory Manager / device sync) + │ device attribute: "itential_password": "$GATEWAYSECRET_(DELINEA-IOSXE-PASSWORD)" + ▼ +Itential Gateway ──(resolves alias)──▶ delinea-plugin.py ──(OAuth2 password grant)──▶ Delinea Secret Server Cloud + │ + ▼ +Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password +``` + +Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. + +## Prerequisites + +- Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). See the [custom plugin provider docs](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) for the full reference. +- A Delinea Secret Server Cloud tenant (`https://.secretservercloud.com`) and a service account with access to the target secret(s). +- Python 3 on the Itential Gateway host (already required by Itential Gateway itself). + +## How Delinea Secret Server API Access Works + +Secret Server Cloud uses OAuth2 password grant, not a static API key: + +1. `POST {base_url}/oauth2/token` with form body `grant_type=password&username=&password=` (add `domain=` only if not using the default `Local` domain). Returns `access_token` + `refresh_token`. +2. `GET {base_url}/api/v1/secrets/{id}/fields/{fieldSlug}` with `Authorization: Bearer ` to fetch a specific field's value (e.g. `password`, `username`). This endpoint returns the value as a JSON-encoded string literal (e.g. `"my-password"`, quotes included) — decode it with a JSON parser rather than using the raw response bytes directly. + +The secret `{id}` is the numeric ID Secret Server assigns (visible in the secret's URL, e.g. `.../app/#/secrets/382/general`) — the API addresses secrets by ID, not by name. + +## The Plugin + +Itential Gateway invokes the plugin as ` get`, writing a JSON request to stdin and reading a JSON response from stdout. Configuration (the non-sensitive values registered with the provider) arrives via the JSON on stdin, in `config.env` — the plugin process does not otherwise inherit Itential Gateway's environment. + +**Request (stdin):** +```json +{ + "path": "382", + "key": "password", + "config": { + "env": { + "DELINEA_BASE_URL": "https://.secretservercloud.com", + "DELINEA_USERNAME": "svc-account", + "DELINEA_DOMAIN": "Local", + "DELINEA_PASSWORD_FILE": "/etc/gateway/delinea_password" + } + } +} +``` + +**Response (stdout, exit 0):** +```json +{"value": "the-plaintext-secret"} +``` + +On failure: write a message to stderr and exit non-zero. + +See [`plugin.py`](./plugin.py) for the full implementation. Copy it to the Itential Gateway host — renaming it to something provider-specific like `delinea-plugin.py` if you'll have more than one provider's plugin on the same host — and make it executable: + +```bash +cp plugin.py /opt/gateway/delinea-plugin.py +chmod +x /opt/gateway/delinea-plugin.py +``` + +## Storing the Password + +Never pass the Secret Server account's password as a raw `--env` value — it would be stored in the gateway's provider configuration. Instead, write it to a file the plugin reads at runtime, and only pass the file path via `--env`: + +```bash +sudo tee /etc/gateway/delinea_password <<< 'the-service-account-password' > /dev/null +sudo chown itential:itential /etc/gateway/delinea_password +sudo chmod 400 /etc/gateway/delinea_password +``` + +The file should contain just the raw password as a single line of plain text — no quotes, no `KEY=value` formatting: + +``` +$ cat /etc/gateway/delinea_password +the-service-account-password +``` + +The plugin reads the file's contents directly and strips any surrounding whitespace/newline, so a trailing newline (which `tee`/most editors add automatically) is fine. + +## Registering the Provider and Alias + +Full CLI reference: [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). + +```bash +# Provider: registers the plugin and its non-sensitive config +iagctl create secret-provider delinea-plugin \ + --type plugin \ + --command /opt/gateway/delinea-plugin.py \ + --env DELINEA_BASE_URL=https://.secretservercloud.com \ + --env DELINEA_USERNAME=svc-account \ + --env DELINEA_DOMAIN=Local \ + --env DELINEA_PASSWORD_FILE=/etc/gateway/delinea_password \ + --description "Delinea Secret Server Cloud via OAuth2 password grant" + +# Alias: maps a friendly name to a specific secret + field on that provider +iagctl create secret DELINEA-IOSXE-PASSWORD \ + --provider delinea-plugin \ + --secret 382 \ + --key password +``` + +Verify: +```bash +iagctl get secret-providers +iagctl describe secret DELINEA-IOSXE-PASSWORD +``` + +`describe secret` only shows the alias's metadata (provider/secret/key) — it never displays the resolved value. + +## Referencing the Alias + +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a device synced from NetBox into Itential Platform: + +```json +{ + "name": "device-name", + "attributes": { + "itential_host": "10.0.25.20", + "itential_port": 22, + "itential_driver": "netmiko", + "itential_platform": "cisco_xe", + "itential_user": "itential", + "itential_password": "$GATEWAYSECRET_(DELINEA-IOSXE-PASSWORD)", + "itential_driver_options": { + "netmiko": { + "banner_timeout": 60, + "conn_timeout": 60, + "enable_fast_mode": true, + "global_delay_factor": 3, + "read_timeout_override": 600, + "session_timeout": 300 + } + } + } +} +``` + +Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Secret Server on every run rather than stored anywhere on the platform. + +## Using the Alias in an Integration Model Instance + +`$GATEWAYSECRET_(alias-name)` isn't limited to device inventory — it also resolves in an **Integration Model instance's** credential fields, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster. + +In the instance's connectivity config: +- Set `proxyOverride.executionMode` to `cluster_no_proxy` or `proxy` — **not** `direct`. `direct` means Platform makes the call itself, Gateway is never involved, and the alias won't resolve. +- Optionally set `clusterOverride` to target a specific Gateway cluster instead of the Admin Essentials default. + +With that in place, reference the alias in the instance's security/credential value the same way you would in an inventory attribute: + +```json +{ + "security": { + "apiKey": { + "value": "$GATEWAYSECRET_(DELINEA-API-TOKEN)" + } + }, + "proxyOverride": { + "overrideProxyBehavior": true, + "executionMode": "cluster_no_proxy", + "proxy": { + "auth": { + "authMode": "none" + } + } + } +} +``` + +Gateway resolves the alias just before the outbound call executes; the real token is never sent back to Platform. It's the same secret-provider plugin doing the work — only the caller changed, from a device driver connection to an API integration. + +## Verifying It's Working + +- `iagctl get secret-providers` shows your provider. +- `iagctl describe secret ` shows the correct provider/secret/key. +- The gateway log (`journalctl -u iagctl`) shows a line like `secret_resolution alias="..." provider="..." path="..." outcome=success` for each call that uses the alias. +- The device/API call using the resolved secret succeeds end to end. + +## Adapting This Example + +- **Different secret templates**: change `--key` to the field slug you need (e.g. `username`, `notes`), or omit `--key` when creating the alias to return the full secret as a JSON object. +- **Multiple secrets**: register one provider, then create as many `secret` aliases as you need against it — each just needs its own `--secret ` (and optionally `--key`). +- **Other Delinea deployments**: this targets Secret Server Cloud's OAuth2 password grant. An on-premises Secret Server instance with a different auth model (e.g. Windows-integrated auth) would need the OAuth section of the plugin adjusted accordingly. + +## References + +- [Itential Gateway — Configure a Custom Plugin Secret Provider](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) +- [Itential Gateway — Manage Secret Aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases) +- [Delinea Secret Server REST API documentation](https://docs.delinea.com/online-help/secret-server/api-scripting/rest-api/index.htm) diff --git a/Delinea/Secret Server/delinea-plugin.py b/Delinea/Secret Server/secret-providers/plugin.py similarity index 100% rename from Delinea/Secret Server/delinea-plugin.py rename to Delinea/Secret Server/secret-providers/plugin.py diff --git a/Microsoft/Azure Key Vault/README.md b/Microsoft/Azure Key Vault/README.md index 4249920..ca90ece 100644 --- a/Microsoft/Azure Key Vault/README.md +++ b/Microsoft/Azure Key Vault/README.md @@ -1,199 +1,15 @@ -# Azure Key Vault — Custom Secret Provider for Itential Gateway +# Azure Key Vault -Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Azure Key Vault isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. +Azure Key Vault is Microsoft's cloud secrets management service for storing and retrieving credentials, keys, and certificates. -This is a working example: a Python plugin for Azure Key Vault, the service principal and role assignment it needs, the registration steps, and how to reference the resulting alias from a device inventory (for example, a NetBox-synced inventory in Itential Platform). +This project provides a custom secret-provider plugin so Itential Gateway can resolve credentials from Azure Key Vault at runtime, instead of storing them in Gateway's own encrypted store. -## Table of Contents +**Requirements:** Itential Gateway >= 5.5 -- [Architecture](#architecture) -- [Prerequisites](#prerequisites) -- [Setting Up a Service Principal](#setting-up-a-service-principal) -- [The Plugin](#the-plugin) -- [Storing the Client Secret](#storing-the-client-secret) -- [Registering the Provider and Alias](#registering-the-provider-and-alias) -- [Referencing the Alias](#referencing-the-alias) -- [Verifying It's Working](#verifying-its-working) -- [Adapting This Example](#adapting-this-example) -- [References](#references) +## Contents -## Architecture +| Asset | Description | +|---|---| +| [secret-providers/](./secret-providers/) | IG5 custom secret-provider plugin — resolves Azure Key Vault secrets into Gateway secret aliases, usable in device inventory credentials and Gateway-executed Integration Model instances | -``` -Itential Platform (Inventory Manager / device sync) - │ device attribute: "itential_password": "$GATEWAYSECRET_(AZURE-IOSXE-PASSWORD)" - ▼ -Itential Gateway ──(resolves alias)──▶ azure-plugin.py ──(OAuth2 client credentials)──▶ Azure Key Vault - │ - ▼ -Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password -``` - -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. - -## Prerequisites - -- Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). See the [custom plugin provider docs](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) for the full reference. -- An Azure Key Vault with the **Azure role-based access control (RBAC)** permission model enabled (this is the default/recommended option when creating a vault). This example uses an RBAC role assignment, not a legacy vault access policy. -- Python 3 on the Itential Gateway host (already required by Itential Gateway itself). - -## Setting Up a Service Principal - -Itential Gateway typically doesn't run on an Azure VM, so there's no Azure Managed Identity available to it — the plugin needs its own identity to authenticate as. If Itential Gateway *is* running on an Azure VM in your environment, use a **Managed Identity** instead of everything in this section — it removes the credential entirely. - -Otherwise, create a dedicated App Registration for this integration (don't reuse a shared/general-purpose one): - -```bash -# Create the App Registration -az ad app create --display-name "svc-iag-keyvault-reader" -# Note the appId from the output - -# Create its service principal -az ad sp create --id - -# Generate a client secret (Azure secrets expire — pick a lifetime and -# plan to rotate before it does; 6-12 months is a reasonable default) -az ad app credential reset --id --display-name "iag-plugin" --end-date 2027-07-10 -# Note the password from the output — Azure will not show it again - -# Grant the narrowest useful role, scoped to just this vault (not the -# resource group or subscription) -az role assignment create \ - --assignee \ - --role "Key Vault Secrets User" \ - --scope $(az keyvault show --name --resource-group --query id -o tsv) -``` - -This gives you three values the plugin needs: the **Tenant ID**, the **Client ID** (`appId`), and the **client secret**. A client certificate is a stronger alternative to a client secret if you want to harden this further — no shared secret in transit. - -## The Plugin - -Itential Gateway invokes the plugin as ` get`, writing a JSON request to stdin and reading a JSON response from stdout. Configuration (the non-sensitive values registered with the provider) arrives via the JSON on stdin, in `config.env` — the plugin process does not otherwise inherit Itential Gateway's environment. - -**Request (stdin):** -```json -{ - "path": "IOSXE-PASSWORD", - "key": "", - "config": { - "env": { - "AZURE_VAULT_URL": "https://.vault.azure.net/", - "AZURE_TENANT_ID": "", - "AZURE_CLIENT_ID": "", - "AZURE_CLIENT_SECRET_FILE": "/etc/gateway/azure_client_secret" - } - } -} -``` - -**Response (stdout, exit 0):** -```json -{"value": "the-plaintext-secret"} -``` - -On failure: write a message to stderr and exit non-zero. - -See [`azure-plugin.py`](./azure-plugin.py) for the full implementation. Copy it to the Itential Gateway host and make it executable: - -```bash -chmod +x /opt/gateway/azure-plugin.py -``` - -Unlike Secret Server-style providers, Key Vault secrets are addressed by **name**, not a numeric ID, and each secret is a single value rather than a multi-field record — so `path` is the secret's name and `key` is normally left empty. - -## Storing the Client Secret - -Never pass the client secret as a raw `--env` value — it would be stored in the gateway's provider configuration. Instead, write it to a file the plugin reads at runtime, and only pass the file path via `--env`: - -```bash -sudo tee /etc/gateway/azure_client_secret <<< 'the-client-secret-value' > /dev/null -sudo chown itential:itential /etc/gateway/azure_client_secret -sudo chmod 400 /etc/gateway/azure_client_secret -``` - -The file should contain just the raw client secret as a single line of plain text — no quotes, no `KEY=value` formatting: - -``` -$ cat /etc/gateway/azure_client_secret -the-client-secret-value -``` - -## Registering the Provider and Alias - -Full CLI reference: [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). - -```bash -# Provider: registers the plugin and its non-sensitive config -iagctl create secret-provider azure-keyvault-plugin \ - --type plugin \ - --command /opt/gateway/azure-plugin.py \ - --env AZURE_VAULT_URL=https://.vault.azure.net/ \ - --env AZURE_TENANT_ID= \ - --env AZURE_CLIENT_ID= \ - --env AZURE_CLIENT_SECRET_FILE=/etc/gateway/azure_client_secret \ - --description "Azure Key Vault via Entra ID client credentials" - -# Alias: maps a friendly name to a specific secret on that provider -iagctl create secret AZURE-IOSXE-PASSWORD \ - --provider azure-keyvault-plugin \ - --secret IOSXE-PASSWORD -``` - -Verify: -```bash -iagctl get secret-providers -iagctl describe secret AZURE-IOSXE-PASSWORD -``` - -`describe secret` only shows the alias's metadata (provider/secret/key) — it never displays the resolved value. - -## Referencing the Alias - -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a device synced from NetBox into Itential Platform: - -```json -{ - "name": "device-name", - "attributes": { - "itential_host": "10.0.25.20", - "itential_port": 22, - "itential_driver": "netmiko", - "itential_platform": "cisco_xe", - "itential_user": "itential", - "itential_password": "$GATEWAYSECRET_(AZURE-IOSXE-PASSWORD)", - "itential_driver_options": { - "netmiko": { - "banner_timeout": 60, - "conn_timeout": 60, - "enable_fast_mode": true, - "global_delay_factor": 3, - "read_timeout_override": 600, - "session_timeout": 300 - } - } - } -} -``` - -Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Key Vault on every run rather than stored anywhere on the platform. - -## Verifying It's Working - -- `iagctl get secret-providers` shows your provider. -- `iagctl describe secret ` shows the correct provider/secret. -- The gateway log (`journalctl -u iagctl`) shows a line like `secret_resolution alias="..." provider="..." path="..." outcome=success` for each call that uses the alias. -- The device/API call using the resolved secret succeeds end to end. - -## Adapting This Example - -- **Multiple secrets**: register one provider, then create as many `secret` aliases as you need against it — each just needs its own `--secret `. -- **Structured secrets**: if you store a JSON object as a secret's value (e.g. `{"username": "...", "password": "..."}`), pass `--key ` when creating the alias — the plugin will parse the value as JSON and pull out that field. -- **Itential Gateway running on an Azure VM**: use a Managed Identity instead of a service principal, and adjust the plugin's authentication step accordingly — no client secret to create, store, or rotate. -- **Client secret rotation**: unlike some other providers, Azure AD client secrets expire. Track the expiration date you set and rotate the secret (`az ad app credential reset`) and the file on the Itential Gateway host before it does. - -## References - -- [Itential Gateway — Configure a Custom Plugin Secret Provider](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) -- [Itential Gateway — Manage Secret Aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases) -- [Azure Key Vault REST API reference](https://learn.microsoft.com/en-us/rest/api/keyvault/) -- [Microsoft Entra ID — OAuth2 client credentials flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow) +See [secret-providers/README.md](./secret-providers/README.md) for full setup details: the service principal and role assignment it needs, registration steps, and how to reference the resulting alias. diff --git a/Microsoft/Azure Key Vault/secret-providers/README.md b/Microsoft/Azure Key Vault/secret-providers/README.md new file mode 100644 index 0000000..5ba52aa --- /dev/null +++ b/Microsoft/Azure Key Vault/secret-providers/README.md @@ -0,0 +1,232 @@ +# Azure Key Vault — Custom Secret Provider for Itential Gateway + +Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Azure Key Vault isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. + +This is a working example: a Python plugin for Azure Key Vault, the service principal and role assignment it needs, the registration steps, and how to reference the resulting alias from a device inventory (for example, a NetBox-synced inventory in Itential Platform). + +## Table of Contents + +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [Setting Up a Service Principal](#setting-up-a-service-principal) +- [The Plugin](#the-plugin) +- [Storing the Client Secret](#storing-the-client-secret) +- [Registering the Provider and Alias](#registering-the-provider-and-alias) +- [Referencing the Alias](#referencing-the-alias) +- [Using the Alias in an Integration Model Instance](#using-the-alias-in-an-integration-model-instance) +- [Verifying It's Working](#verifying-its-working) +- [Adapting This Example](#adapting-this-example) +- [References](#references) + +## Architecture + +``` +Itential Platform (Inventory Manager / device sync) + │ device attribute: "itential_password": "$GATEWAYSECRET_(AZURE-IOSXE-PASSWORD)" + ▼ +Itential Gateway ──(resolves alias)──▶ azure-plugin.py ──(OAuth2 client credentials)──▶ Azure Key Vault + │ + ▼ +Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password +``` + +Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. + +## Prerequisites + +- Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). See the [custom plugin provider docs](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) for the full reference. +- An Azure Key Vault with the **Azure role-based access control (RBAC)** permission model enabled (this is the default/recommended option when creating a vault). This example uses an RBAC role assignment, not a legacy vault access policy. +- Python 3 on the Itential Gateway host (already required by Itential Gateway itself). + +## Setting Up a Service Principal + +Itential Gateway typically doesn't run on an Azure VM, so there's no Azure Managed Identity available to it — the plugin needs its own identity to authenticate as. If Itential Gateway *is* running on an Azure VM in your environment, use a **Managed Identity** instead of everything in this section — it removes the credential entirely. + +Otherwise, create a dedicated App Registration for this integration (don't reuse a shared/general-purpose one): + +```bash +# Create the App Registration +az ad app create --display-name "svc-iag-keyvault-reader" +# Note the appId from the output + +# Create its service principal +az ad sp create --id + +# Generate a client secret (Azure secrets expire — pick a lifetime and +# plan to rotate before it does; 6-12 months is a reasonable default) +az ad app credential reset --id --display-name "iag-plugin" --end-date 2027-07-10 +# Note the password from the output — Azure will not show it again + +# Grant the narrowest useful role, scoped to just this vault (not the +# resource group or subscription) +az role assignment create \ + --assignee \ + --role "Key Vault Secrets User" \ + --scope $(az keyvault show --name --resource-group --query id -o tsv) +``` + +This gives you three values the plugin needs: the **Tenant ID**, the **Client ID** (`appId`), and the **client secret**. A client certificate is a stronger alternative to a client secret if you want to harden this further — no shared secret in transit. + +## The Plugin + +Itential Gateway invokes the plugin as ` get`, writing a JSON request to stdin and reading a JSON response from stdout. Configuration (the non-sensitive values registered with the provider) arrives via the JSON on stdin, in `config.env` — the plugin process does not otherwise inherit Itential Gateway's environment. + +**Request (stdin):** +```json +{ + "path": "IOSXE-PASSWORD", + "key": "", + "config": { + "env": { + "AZURE_VAULT_URL": "https://.vault.azure.net/", + "AZURE_TENANT_ID": "", + "AZURE_CLIENT_ID": "", + "AZURE_CLIENT_SECRET_FILE": "/etc/gateway/azure_client_secret" + } + } +} +``` + +**Response (stdout, exit 0):** +```json +{"value": "the-plaintext-secret"} +``` + +On failure: write a message to stderr and exit non-zero. + +See [`plugin.py`](./plugin.py) for the full implementation. Copy it to the Itential Gateway host — renaming it to something provider-specific like `azure-plugin.py` if you'll have more than one provider's plugin on the same host — and make it executable: + +```bash +cp plugin.py /opt/gateway/azure-plugin.py +chmod +x /opt/gateway/azure-plugin.py +``` + +Unlike Secret Server-style providers, Key Vault secrets are addressed by **name**, not a numeric ID, and each secret is a single value rather than a multi-field record — so `path` is the secret's name and `key` is normally left empty. + +## Storing the Client Secret + +Never pass the client secret as a raw `--env` value — it would be stored in the gateway's provider configuration. Instead, write it to a file the plugin reads at runtime, and only pass the file path via `--env`: + +```bash +sudo tee /etc/gateway/azure_client_secret <<< 'the-client-secret-value' > /dev/null +sudo chown itential:itential /etc/gateway/azure_client_secret +sudo chmod 400 /etc/gateway/azure_client_secret +``` + +The file should contain just the raw client secret as a single line of plain text — no quotes, no `KEY=value` formatting: + +``` +$ cat /etc/gateway/azure_client_secret +the-client-secret-value +``` + +## Registering the Provider and Alias + +Full CLI reference: [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). + +```bash +# Provider: registers the plugin and its non-sensitive config +iagctl create secret-provider azure-keyvault-plugin \ + --type plugin \ + --command /opt/gateway/azure-plugin.py \ + --env AZURE_VAULT_URL=https://.vault.azure.net/ \ + --env AZURE_TENANT_ID= \ + --env AZURE_CLIENT_ID= \ + --env AZURE_CLIENT_SECRET_FILE=/etc/gateway/azure_client_secret \ + --description "Azure Key Vault via Entra ID client credentials" + +# Alias: maps a friendly name to a specific secret on that provider +iagctl create secret AZURE-IOSXE-PASSWORD \ + --provider azure-keyvault-plugin \ + --secret IOSXE-PASSWORD +``` + +Verify: +```bash +iagctl get secret-providers +iagctl describe secret AZURE-IOSXE-PASSWORD +``` + +`describe secret` only shows the alias's metadata (provider/secret/key) — it never displays the resolved value. + +## Referencing the Alias + +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a device synced from NetBox into Itential Platform: + +```json +{ + "name": "device-name", + "attributes": { + "itential_host": "10.0.25.20", + "itential_port": 22, + "itential_driver": "netmiko", + "itential_platform": "cisco_xe", + "itential_user": "itential", + "itential_password": "$GATEWAYSECRET_(AZURE-IOSXE-PASSWORD)", + "itential_driver_options": { + "netmiko": { + "banner_timeout": 60, + "conn_timeout": 60, + "enable_fast_mode": true, + "global_delay_factor": 3, + "read_timeout_override": 600, + "session_timeout": 300 + } + } + } +} +``` + +Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Key Vault on every run rather than stored anywhere on the platform. + +## Using the Alias in an Integration Model Instance + +`$GATEWAYSECRET_(alias-name)` isn't limited to device inventory — it also resolves in an **Integration Model instance's** credential fields, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster. + +In the instance's connectivity config: +- Set `proxyOverride.executionMode` to `cluster_no_proxy` or `proxy` — **not** `direct`. `direct` means Platform makes the call itself, Gateway is never involved, and the alias won't resolve. +- Optionally set `clusterOverride` to target a specific Gateway cluster instead of the Admin Essentials default. + +With that in place, reference the alias in the instance's security/credential value the same way you would in an inventory attribute: + +```json +{ + "security": { + "apiKey": { + "value": "$GATEWAYSECRET_(AZURE-API-TOKEN)" + } + }, + "proxyOverride": { + "overrideProxyBehavior": true, + "executionMode": "cluster_no_proxy", + "proxy": { + "auth": { + "authMode": "none" + } + } + } +} +``` + +Gateway resolves the alias just before the outbound call executes; the real token is never sent back to Platform. It's the same secret-provider plugin doing the work — only the caller changed, from a device driver connection to an API integration. + +## Verifying It's Working + +- `iagctl get secret-providers` shows your provider. +- `iagctl describe secret ` shows the correct provider/secret. +- The gateway log (`journalctl -u iagctl`) shows a line like `secret_resolution alias="..." provider="..." path="..." outcome=success` for each call that uses the alias. +- The device/API call using the resolved secret succeeds end to end. + +## Adapting This Example + +- **Multiple secrets**: register one provider, then create as many `secret` aliases as you need against it — each just needs its own `--secret `. +- **Structured secrets**: if you store a JSON object as a secret's value (e.g. `{"username": "...", "password": "..."}`), pass `--key ` when creating the alias — the plugin will parse the value as JSON and pull out that field. +- **Itential Gateway running on an Azure VM**: use a Managed Identity instead of a service principal, and adjust the plugin's authentication step accordingly — no client secret to create, store, or rotate. +- **Client secret rotation**: unlike some other providers, Azure AD client secrets expire. Track the expiration date you set and rotate the secret (`az ad app credential reset`) and the file on the Itential Gateway host before it does. + +## References + +- [Itential Gateway — Configure a Custom Plugin Secret Provider](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) +- [Itential Gateway — Manage Secret Aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases) +- [Azure Key Vault REST API reference](https://learn.microsoft.com/en-us/rest/api/keyvault/) +- [Microsoft Entra ID — OAuth2 client credentials flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow) diff --git a/Microsoft/Azure Key Vault/azure-plugin.py b/Microsoft/Azure Key Vault/secret-providers/plugin.py similarity index 100% rename from Microsoft/Azure Key Vault/azure-plugin.py rename to Microsoft/Azure Key Vault/secret-providers/plugin.py diff --git a/README.md b/README.md index 3cc2ede..4987233 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Assets are organized by vendor and product. Each folder may contain one or more | **Golden Configurations** | Config Manager compliance trees for auditing device configuration drift | | **device-drivers** | Netmiko-based drivers for connecting Itential Gateway to physical and virtual devices | | **Configuration Parsers** | Scripts for parsing structured output from device CLI commands | +| **secret-providers** | Custom secret-provider plugin so Itential Gateway resolves credentials from an external secrets manager at runtime, instead of storing them in Gateway's own encrypted store — usable for both device inventory credentials and Gateway-executed Integration Model instances | --- @@ -82,6 +83,7 @@ Vendor/ ├── device-drivers/ ├── Golden Configurations/ ├── OpenAPIs/ + ├── secret-providers/ ├── Studio Projects/ └── README.md ``` @@ -117,6 +119,9 @@ See [Golden Configuration overview](https://docs.itential.com/itential-platform/ ### Install a Device Driver (Gateway) Follow the instructions in the driver's `README.md`. Drivers typically require copying files to your Itential Gateway host and restarting the Itential Gateway service. +### Install a Secret Provider (Gateway) +Follow the instructions in the `secret-providers/README.md`. Copy the plugin file to your Itential Gateway host, then register it as a secret provider and create a secret alias via `iagctl` — see [Itential Gateway — Configure a Custom Plugin Secret Provider](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider). + --- ## Requirements @@ -124,7 +129,7 @@ Follow the instructions in the driver's `README.md`. Drivers typically require c Minimum versions vary by asset - check each product's `README.md` for specifics. In general: - **Itential Platform** ≥ 6.4 -- **Itential Gateway** ≥ 5.0 (for device-driver assets) +- **Itential Gateway** ≥ 5.0 (for device-driver assets), ≥ 5.5 (for secret-providers assets) --- From 226baab4ed60c545db4bde9c2cb0be6f08f8d4c5 Mon Sep 17 00:00:00 2001 From: Michael Elrom Date: Fri, 28 Aug 2026 16:08:02 -0400 Subject: [PATCH 2/4] docs(secret-providers): reframe architecture as dual-caller, not device-first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Architecture section previously showed only the device-inventory flow, then bolted the Integration Model instance case on afterward as a "but you can also" footnote. Reframed so Gateway resolving the secret is the one constant, with device inventory and Integration Model instances shown as two equal callers into the same alias → provider → plugin path. Merged the two usage examples under one "Referencing the Alias" section with matching subsections instead of a separate standalone section. --- .../secret-providers/README.md | 36 ++++++++++--------- .../Secret Server/secret-providers/README.md | 36 ++++++++++--------- .../secret-providers/README.md | 36 ++++++++++--------- 3 files changed, 57 insertions(+), 51 deletions(-) diff --git a/AWS/Secrets Manager/secret-providers/README.md b/AWS/Secrets Manager/secret-providers/README.md index 9909ec0..7fc5cb2 100644 --- a/AWS/Secrets Manager/secret-providers/README.md +++ b/AWS/Secrets Manager/secret-providers/README.md @@ -2,7 +2,7 @@ Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). AWS Secrets Manager isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. -This is a working example: a Python plugin for AWS Secrets Manager, **three different ways to authenticate it to AWS**, the registration steps, and how to reference the resulting alias from a device inventory. +This is a working example: a Python plugin for AWS Secrets Manager, **three different ways to authenticate it to AWS**, the registration steps, and how to reference the resulting alias — from device inventory or from an Integration Model instance. ## Table of Contents @@ -16,24 +16,25 @@ This is a working example: a Python plugin for AWS Secrets Manager, **three diff - [Cross-account note](#cross-account-note) - [Registering the Provider and Alias](#registering-the-provider-and-alias) - [Referencing the Alias](#referencing-the-alias) -- [Using the Alias in an Integration Model Instance](#using-the-alias-in-an-integration-model-instance) + - [In Device Inventory](#in-device-inventory) + - [In an Integration Model Instance](#in-an-integration-model-instance) - [Verifying It's Working](#verifying-its-working) - [Adapting This Example](#adapting-this-example) - [References](#references) ## Architecture +Itential Gateway is the only thing that ever talks to AWS Secrets Manager. Two different callers can trigger that resolution — Inventory Manager driving a device connection, or an Integration Model instance making an API call — but both go through the exact same alias → provider → plugin path, and the plaintext secret never travels back to Platform: + ``` -Itential Platform (Inventory Manager / device sync) - │ device attribute: "itential_password": "$GATEWAYSECRET_(AWS-IOSXE-PASSWORD)" - ▼ -Itential Gateway ──(resolves alias)──▶ aws-plugin.py ──(SigV4-signed request)──▶ AWS Secrets Manager - │ - ▼ -Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password +Itential Platform Itential Gateway +────────────────── ──────────────── +device sync (Inventory Manager) ─┐ +Integration Model instance ├─▶ resolves $GATEWAYSECRET_(alias) ──▶ aws-plugin.py ──(SigV4-signed request)──▶ AWS Secrets Manager +(Gateway-executed) ─┘ ``` -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. +Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, in an Integration Model instance's config, or in Itential Gateway's own database — only the alias name is. The interesting part of this integration isn't the Secrets Manager API call itself (a single `GetSecretValue`) — it's **how the plugin process authenticates to AWS**, since that varies a lot depending on where Itential Gateway is actually running. This example implements and tests three patterns and lets you pick per-provider. @@ -238,7 +239,11 @@ iagctl describe secret AWS-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a Cisco IOS device synced into Itential Platform: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. Two common places: + +### In Device Inventory + +For example, a Cisco IOS device synced into Itential Platform: ```json { @@ -256,16 +261,13 @@ Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Secrets Manager on every run rather than stored anywhere on the platform. -## Using the Alias in an Integration Model Instance +### In an Integration Model Instance -`$GATEWAYSECRET_(alias-name)` isn't limited to device inventory — it also resolves in an **Integration Model instance's** credential fields, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster. +The same alias resolves in an **Integration Model instance's** credential fields too, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster: -In the instance's connectivity config: - Set `proxyOverride.executionMode` to `cluster_no_proxy` or `proxy` — **not** `direct`. `direct` means Platform makes the call itself, Gateway is never involved, and the alias won't resolve. - Optionally set `clusterOverride` to target a specific Gateway cluster instead of the Admin Essentials default. -With that in place, reference the alias in the instance's security/credential value the same way you would in an inventory attribute: - ```json { "security": { @@ -285,7 +287,7 @@ With that in place, reference the alias in the instance's security/credential va } ``` -Gateway resolves the alias just before the outbound call executes; the real token is never sent back to Platform. It's the same secret-provider plugin doing the work — only the caller changed, from a device driver connection to an API integration. +Gateway resolves the alias just before the outbound call executes, same as the device inventory case — the real token is never sent back to Platform. ## Verifying It's Working diff --git a/Delinea/Secret Server/secret-providers/README.md b/Delinea/Secret Server/secret-providers/README.md index 441981a..3f4032f 100644 --- a/Delinea/Secret Server/secret-providers/README.md +++ b/Delinea/Secret Server/secret-providers/README.md @@ -2,7 +2,7 @@ Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Delinea Secret Server isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. -This is a working example: a Python plugin for Delinea Secret Server Cloud, the registration steps, and how to reference the resulting alias from a device inventory (for example, a NetBox-synced inventory in Itential Platform). +This is a working example: a Python plugin for Delinea Secret Server Cloud, the registration steps, and how to reference the resulting alias — from device inventory or from an Integration Model instance. ## Table of Contents @@ -13,24 +13,25 @@ This is a working example: a Python plugin for Delinea Secret Server Cloud, the - [Storing the Password](#storing-the-password) - [Registering the Provider and Alias](#registering-the-provider-and-alias) - [Referencing the Alias](#referencing-the-alias) -- [Using the Alias in an Integration Model Instance](#using-the-alias-in-an-integration-model-instance) + - [In Device Inventory](#in-device-inventory) + - [In an Integration Model Instance](#in-an-integration-model-instance) - [Verifying It's Working](#verifying-its-working) - [Adapting This Example](#adapting-this-example) - [References](#references) ## Architecture +Itential Gateway is the only thing that ever talks to Secret Server. Two different callers can trigger that resolution — Inventory Manager driving a device connection, or an Integration Model instance making an API call — but both go through the exact same alias → provider → plugin path, and the plaintext secret never travels back to Platform: + ``` -Itential Platform (Inventory Manager / device sync) - │ device attribute: "itential_password": "$GATEWAYSECRET_(DELINEA-IOSXE-PASSWORD)" - ▼ -Itential Gateway ──(resolves alias)──▶ delinea-plugin.py ──(OAuth2 password grant)──▶ Delinea Secret Server Cloud - │ - ▼ -Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password +Itential Platform Itential Gateway +────────────────── ──────────────── +device sync (Inventory Manager) ─┐ +Integration Model instance ├─▶ resolves $GATEWAYSECRET_(alias) ──▶ delinea-plugin.py ──(OAuth2 password grant)──▶ Delinea Secret Server Cloud +(Gateway-executed) ─┘ ``` -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. +Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, in an Integration Model instance's config, or in Itential Gateway's own database — only the alias name is. ## Prerequisites @@ -132,7 +133,11 @@ iagctl describe secret DELINEA-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a device synced from NetBox into Itential Platform: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. Two common places: + +### In Device Inventory + +For example, a device synced from NetBox into Itential Platform: ```json { @@ -160,16 +165,13 @@ Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Secret Server on every run rather than stored anywhere on the platform. -## Using the Alias in an Integration Model Instance +### In an Integration Model Instance -`$GATEWAYSECRET_(alias-name)` isn't limited to device inventory — it also resolves in an **Integration Model instance's** credential fields, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster. +The same alias resolves in an **Integration Model instance's** credential fields too, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster: -In the instance's connectivity config: - Set `proxyOverride.executionMode` to `cluster_no_proxy` or `proxy` — **not** `direct`. `direct` means Platform makes the call itself, Gateway is never involved, and the alias won't resolve. - Optionally set `clusterOverride` to target a specific Gateway cluster instead of the Admin Essentials default. -With that in place, reference the alias in the instance's security/credential value the same way you would in an inventory attribute: - ```json { "security": { @@ -189,7 +191,7 @@ With that in place, reference the alias in the instance's security/credential va } ``` -Gateway resolves the alias just before the outbound call executes; the real token is never sent back to Platform. It's the same secret-provider plugin doing the work — only the caller changed, from a device driver connection to an API integration. +Gateway resolves the alias just before the outbound call executes, same as the device inventory case — the real token is never sent back to Platform. ## Verifying It's Working diff --git a/Microsoft/Azure Key Vault/secret-providers/README.md b/Microsoft/Azure Key Vault/secret-providers/README.md index 5ba52aa..f28c3fd 100644 --- a/Microsoft/Azure Key Vault/secret-providers/README.md +++ b/Microsoft/Azure Key Vault/secret-providers/README.md @@ -2,7 +2,7 @@ Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Azure Key Vault isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. -This is a working example: a Python plugin for Azure Key Vault, the service principal and role assignment it needs, the registration steps, and how to reference the resulting alias from a device inventory (for example, a NetBox-synced inventory in Itential Platform). +This is a working example: a Python plugin for Azure Key Vault, the service principal and role assignment it needs, the registration steps, and how to reference the resulting alias — from device inventory or from an Integration Model instance. ## Table of Contents @@ -13,24 +13,25 @@ This is a working example: a Python plugin for Azure Key Vault, the service prin - [Storing the Client Secret](#storing-the-client-secret) - [Registering the Provider and Alias](#registering-the-provider-and-alias) - [Referencing the Alias](#referencing-the-alias) -- [Using the Alias in an Integration Model Instance](#using-the-alias-in-an-integration-model-instance) + - [In Device Inventory](#in-device-inventory) + - [In an Integration Model Instance](#in-an-integration-model-instance) - [Verifying It's Working](#verifying-its-working) - [Adapting This Example](#adapting-this-example) - [References](#references) ## Architecture +Itential Gateway is the only thing that ever talks to Azure Key Vault. Two different callers can trigger that resolution — Inventory Manager driving a device connection, or an Integration Model instance making an API call — but both go through the exact same alias → provider → plugin path, and the plaintext secret never travels back to Platform: + ``` -Itential Platform (Inventory Manager / device sync) - │ device attribute: "itential_password": "$GATEWAYSECRET_(AZURE-IOSXE-PASSWORD)" - ▼ -Itential Gateway ──(resolves alias)──▶ azure-plugin.py ──(OAuth2 client credentials)──▶ Azure Key Vault - │ - ▼ -Device driver (e.g. netmiko) ──▶ target device, using the resolved plaintext password +Itential Platform Itential Gateway +────────────────── ──────────────── +device sync (Inventory Manager) ─┐ +Integration Model instance ├─▶ resolves $GATEWAYSECRET_(alias) ──▶ azure-plugin.py ──(OAuth2 client credentials)──▶ Azure Key Vault +(Gateway-executed) ─┘ ``` -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, or in Itential Gateway's own database — only the alias name is. +Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, in an Integration Model instance's config, or in Itential Gateway's own database — only the alias name is. ## Prerequisites @@ -151,7 +152,11 @@ iagctl describe secret AZURE-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time — including inside device inventory attributes, for example a device synced from NetBox into Itential Platform: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. Two common places: + +### In Device Inventory + +For example, a device synced from NetBox into Itential Platform: ```json { @@ -179,16 +184,13 @@ Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at Itential Gateway resolves the alias just before the device driver call, so the real password is fetched fresh from Key Vault on every run rather than stored anywhere on the platform. -## Using the Alias in an Integration Model Instance +### In an Integration Model Instance -`$GATEWAYSECRET_(alias-name)` isn't limited to device inventory — it also resolves in an **Integration Model instance's** credential fields, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster. +The same alias resolves in an **Integration Model instance's** credential fields too, as long as that instance's calls actually execute through Itential Gateway rather than directly from the Platform cluster: -In the instance's connectivity config: - Set `proxyOverride.executionMode` to `cluster_no_proxy` or `proxy` — **not** `direct`. `direct` means Platform makes the call itself, Gateway is never involved, and the alias won't resolve. - Optionally set `clusterOverride` to target a specific Gateway cluster instead of the Admin Essentials default. -With that in place, reference the alias in the instance's security/credential value the same way you would in an inventory attribute: - ```json { "security": { @@ -208,7 +210,7 @@ With that in place, reference the alias in the instance's security/credential va } ``` -Gateway resolves the alias just before the outbound call executes; the real token is never sent back to Platform. It's the same secret-provider plugin doing the work — only the caller changed, from a device driver connection to an API integration. +Gateway resolves the alias just before the outbound call executes, same as the device inventory case — the real token is never sent back to Platform. ## Verifying It's Working From b61bfd4f8691cc3a4952509a352bfec0b22cf588 Mon Sep 17 00:00:00 2001 From: Michael Elrom Date: Fri, 28 Aug 2026 16:24:03 -0400 Subject: [PATCH 3/4] docs(secret-providers): lead with What This Is, move architecture into Referencing the Alias Readers configuring an alternative secrets-manager plugin want "what this is, then how to set it up" - not an intro, a bulleted rationale, and a diagram before Prerequisites. Replaced the verbose opening with a short What This Is (folds in the value prop: one less credential store, automatic rotation propagation, no VPN between SaaS Platform and an on-prem secrets manager) and dropped the standalone Architecture section. The diagram now lives inline in Referencing the Alias, where it's actually needed to introduce the two worked examples, and the caller list is broadened (not just device inventory / Integration Model instances) to include Config Manager command templates and GatewayManager tasks (runService, runCode, sendCommand, sendConfig), linking to Itential's own external-secrets overview doc for the complete list rather than inventing unverified specifics. --- .../secret-providers/README.md | 45 ++++++++++--------- .../Secret Server/secret-providers/README.md | 41 +++++++++-------- .../secret-providers/README.md | 41 +++++++++-------- 3 files changed, 71 insertions(+), 56 deletions(-) diff --git a/AWS/Secrets Manager/secret-providers/README.md b/AWS/Secrets Manager/secret-providers/README.md index 7fc5cb2..2d7f0ae 100644 --- a/AWS/Secrets Manager/secret-providers/README.md +++ b/AWS/Secrets Manager/secret-providers/README.md @@ -1,12 +1,16 @@ # AWS Secrets Manager — Custom Secret Provider for Itential Gateway -Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). AWS Secrets Manager isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. +## What This Is -This is a working example: a Python plugin for AWS Secrets Manager, **three different ways to authenticate it to AWS**, the registration steps, and how to reference the resulting alias — from device inventory or from an Integration Model instance. +A custom secret-provider plugin so Itential Gateway resolves AWS Secrets Manager credentials at runtime instead of storing them in Gateway's own encrypted store — for device inventory passwords, Integration Model API credentials, and other Gateway-executed actions. One less place your team manages credentials, and rotation in AWS Secrets Manager propagates automatically on the next run — no VPN needed between a SaaS Platform and an on-prem secrets manager, since only Gateway ever resolves the value. + +The interesting part of this integration isn't the Secrets Manager API call itself (a single `GetSecretValue`) — it's **how the plugin process authenticates to AWS**, since that varies a lot depending on where Itential Gateway is actually running. This example implements and tests three patterns and lets you pick per-provider. + +Itential Gateway 5.5+ supports **external secret providers** out of the box for **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). AWS Secrets Manager isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. ## Table of Contents -- [Architecture](#architecture) +- [What This Is](#what-this-is) - [Prerequisites](#prerequisites) - [The Plugin](#the-plugin) - [Authenticating to AWS: Three Patterns](#authenticating-to-aws-three-patterns) @@ -22,22 +26,6 @@ This is a working example: a Python plugin for AWS Secrets Manager, **three diff - [Adapting This Example](#adapting-this-example) - [References](#references) -## Architecture - -Itential Gateway is the only thing that ever talks to AWS Secrets Manager. Two different callers can trigger that resolution — Inventory Manager driving a device connection, or an Integration Model instance making an API call — but both go through the exact same alias → provider → plugin path, and the plaintext secret never travels back to Platform: - -``` -Itential Platform Itential Gateway -────────────────── ──────────────── -device sync (Inventory Manager) ─┐ -Integration Model instance ├─▶ resolves $GATEWAYSECRET_(alias) ──▶ aws-plugin.py ──(SigV4-signed request)──▶ AWS Secrets Manager -(Gateway-executed) ─┘ -``` - -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, in an Integration Model instance's config, or in Itential Gateway's own database — only the alias name is. - -The interesting part of this integration isn't the Secrets Manager API call itself (a single `GetSecretValue`) — it's **how the plugin process authenticates to AWS**, since that varies a lot depending on where Itential Gateway is actually running. This example implements and tests three patterns and lets you pick per-provider. - ## Prerequisites - Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). @@ -239,7 +227,24 @@ iagctl describe secret AWS-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. Two common places: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager device sync and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: + +``` +Itential Platform + • Inventory Manager device sync + • Integration Model instances (Gateway-executed) + • Config Manager command templates + • GatewayManager tasks (runService, runCode, sendCommand, sendConfig, ...) + │ + ▼ +Itential Gateway + │ resolves $GATEWAYSECRET_(alias) + ▼ +aws-plugin.py + │ SigV4-signed request + ▼ +AWS Secrets Manager +``` ### In Device Inventory diff --git a/Delinea/Secret Server/secret-providers/README.md b/Delinea/Secret Server/secret-providers/README.md index 3f4032f..94ca758 100644 --- a/Delinea/Secret Server/secret-providers/README.md +++ b/Delinea/Secret Server/secret-providers/README.md @@ -1,12 +1,14 @@ # Delinea Secret Server — Custom Secret Provider for Itential Gateway -Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Delinea Secret Server isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. +## What This Is -This is a working example: a Python plugin for Delinea Secret Server Cloud, the registration steps, and how to reference the resulting alias — from device inventory or from an Integration Model instance. +A custom secret-provider plugin so Itential Gateway resolves Delinea Secret Server credentials at runtime instead of storing them in Gateway's own encrypted store — for device inventory passwords, Integration Model API credentials, and other Gateway-executed actions. One less place your team manages credentials, and rotation in Secret Server propagates automatically on the next run — no VPN needed between a SaaS Platform and an on-prem secrets manager, since only Gateway ever resolves the value. + +Itential Gateway 5.5+ supports **external secret providers** out of the box for **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Delinea Secret Server isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. ## Table of Contents -- [Architecture](#architecture) +- [What This Is](#what-this-is) - [Prerequisites](#prerequisites) - [How Delinea Secret Server API Access Works](#how-delinea-secret-server-api-access-works) - [The Plugin](#the-plugin) @@ -19,20 +21,6 @@ This is a working example: a Python plugin for Delinea Secret Server Cloud, the - [Adapting This Example](#adapting-this-example) - [References](#references) -## Architecture - -Itential Gateway is the only thing that ever talks to Secret Server. Two different callers can trigger that resolution — Inventory Manager driving a device connection, or an Integration Model instance making an API call — but both go through the exact same alias → provider → plugin path, and the plaintext secret never travels back to Platform: - -``` -Itential Platform Itential Gateway -────────────────── ──────────────── -device sync (Inventory Manager) ─┐ -Integration Model instance ├─▶ resolves $GATEWAYSECRET_(alias) ──▶ delinea-plugin.py ──(OAuth2 password grant)──▶ Delinea Secret Server Cloud -(Gateway-executed) ─┘ -``` - -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, in an Integration Model instance's config, or in Itential Gateway's own database — only the alias name is. - ## Prerequisites - Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). See the [custom plugin provider docs](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) for the full reference. @@ -133,7 +121,24 @@ iagctl describe secret DELINEA-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. Two common places: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager device sync and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: + +``` +Itential Platform + • Inventory Manager device sync + • Integration Model instances (Gateway-executed) + • Config Manager command templates + • GatewayManager tasks (runService, runCode, sendCommand, sendConfig, ...) + │ + ▼ +Itential Gateway + │ resolves $GATEWAYSECRET_(alias) + ▼ +delinea-plugin.py + │ OAuth2 password grant + ▼ +Delinea Secret Server Cloud +``` ### In Device Inventory diff --git a/Microsoft/Azure Key Vault/secret-providers/README.md b/Microsoft/Azure Key Vault/secret-providers/README.md index f28c3fd..db0236a 100644 --- a/Microsoft/Azure Key Vault/secret-providers/README.md +++ b/Microsoft/Azure Key Vault/secret-providers/README.md @@ -1,12 +1,14 @@ # Azure Key Vault — Custom Secret Provider for Itential Gateway -Itential Gateway 5.5+ supports **external secret providers**: instead of storing credentials in Itential Gateway's own encrypted store, Itential Gateway resolves them at execution time from an external secrets system. Out of the box it supports **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Azure Key Vault isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. +## What This Is -This is a working example: a Python plugin for Azure Key Vault, the service principal and role assignment it needs, the registration steps, and how to reference the resulting alias — from device inventory or from an Integration Model instance. +A custom secret-provider plugin so Itential Gateway resolves Azure Key Vault credentials at runtime instead of storing them in Gateway's own encrypted store — for device inventory passwords, Integration Model API credentials, and other Gateway-executed actions. One less place your team manages credentials, and rotation in Azure Key Vault propagates automatically on the next run — no VPN needed between a SaaS Platform and an on-prem secrets manager, since only Gateway ever resolves the value. + +Itential Gateway 5.5+ supports **external secret providers** out of the box for **HashiCorp Vault (KV v2)** and **CyberArk CCP** — see Itential's docs on [configuring a custom secret provider plugin](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) and [managing secret aliases](https://docs.itential.com/itential-gateway/secrets/external-secrets/manage-secret-aliases). Azure Key Vault isn't a built-in type, so this uses the third option — **`plugin`** — a small executable you provide that Itential Gateway calls to fetch a secret on demand. ## Table of Contents -- [Architecture](#architecture) +- [What This Is](#what-this-is) - [Prerequisites](#prerequisites) - [Setting Up a Service Principal](#setting-up-a-service-principal) - [The Plugin](#the-plugin) @@ -19,20 +21,6 @@ This is a working example: a Python plugin for Azure Key Vault, the service prin - [Adapting This Example](#adapting-this-example) - [References](#references) -## Architecture - -Itential Gateway is the only thing that ever talks to Azure Key Vault. Two different callers can trigger that resolution — Inventory Manager driving a device connection, or an Integration Model instance making an API call — but both go through the exact same alias → provider → plugin path, and the plaintext secret never travels back to Platform: - -``` -Itential Platform Itential Gateway -────────────────── ──────────────── -device sync (Inventory Manager) ─┐ -Integration Model instance ├─▶ resolves $GATEWAYSECRET_(alias) ──▶ azure-plugin.py ──(OAuth2 client credentials)──▶ Azure Key Vault -(Gateway-executed) ─┘ -``` - -Itential Gateway resolves the `$GATEWAYSECRET_(...)` reference just before the value is used, so the plaintext password is never stored in Inventory Manager, in a sync template, in an Integration Model instance's config, or in Itential Gateway's own database — only the alias name is. - ## Prerequisites - Itential Gateway 5.5 or later, with the `secret-provider` feature available (`iagctl create secret-provider --help` should show the `plugin`, `vault`, and `cyberark` provider types). See the [custom plugin provider docs](https://docs.itential.com/itential-gateway/secrets/external-secrets/configure-custom-plugin-provider) for the full reference. @@ -152,7 +140,24 @@ iagctl describe secret AZURE-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. Two common places: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager device sync and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: + +``` +Itential Platform + • Inventory Manager device sync + • Integration Model instances (Gateway-executed) + • Config Manager command templates + • GatewayManager tasks (runService, runCode, sendCommand, sendConfig, ...) + │ + ▼ +Itential Gateway + │ resolves $GATEWAYSECRET_(alias) + ▼ +azure-plugin.py + │ OAuth2 client credentials + ▼ +Azure Key Vault +``` ### In Device Inventory From 153d7fa470d44de33846c5f02268dfb64e94bfb0 Mon Sep 17 00:00:00 2001 From: Michael Elrom Date: Fri, 28 Aug 2026 17:18:39 -0400 Subject: [PATCH 4/4] docs(secret-providers): rename Inventory Manager device sync to Inventory Manager Nodes --- AWS/Secrets Manager/secret-providers/README.md | 4 ++-- Delinea/Secret Server/secret-providers/README.md | 4 ++-- Microsoft/Azure Key Vault/secret-providers/README.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AWS/Secrets Manager/secret-providers/README.md b/AWS/Secrets Manager/secret-providers/README.md index 2d7f0ae..7ba4700 100644 --- a/AWS/Secrets Manager/secret-providers/README.md +++ b/AWS/Secrets Manager/secret-providers/README.md @@ -227,11 +227,11 @@ iagctl describe secret AWS-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager device sync and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager Nodes and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: ``` Itential Platform - • Inventory Manager device sync + • Inventory Manager Nodes • Integration Model instances (Gateway-executed) • Config Manager command templates • GatewayManager tasks (runService, runCode, sendCommand, sendConfig, ...) diff --git a/Delinea/Secret Server/secret-providers/README.md b/Delinea/Secret Server/secret-providers/README.md index 94ca758..d34d6a9 100644 --- a/Delinea/Secret Server/secret-providers/README.md +++ b/Delinea/Secret Server/secret-providers/README.md @@ -121,11 +121,11 @@ iagctl describe secret DELINEA-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager device sync and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager Nodes and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: ``` Itential Platform - • Inventory Manager device sync + • Inventory Manager Nodes • Integration Model instances (Gateway-executed) • Config Manager command templates • GatewayManager tasks (runService, runCode, sendCommand, sendConfig, ...) diff --git a/Microsoft/Azure Key Vault/secret-providers/README.md b/Microsoft/Azure Key Vault/secret-providers/README.md index db0236a..6147753 100644 --- a/Microsoft/Azure Key Vault/secret-providers/README.md +++ b/Microsoft/Azure Key Vault/secret-providers/README.md @@ -140,11 +140,11 @@ iagctl describe secret AZURE-IOSXE-PASSWORD ## Referencing the Alias -Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager device sync and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: +Use `$GATEWAYSECRET_(alias-name)` anywhere Itential Gateway resolves secrets at execution time. This doc covers two callers in detail below — Inventory Manager Nodes and Integration Model instances — but Gateway resolves the same alias for other Gateway-executed actions too, including Config Manager command templates and GatewayManager tasks like `runService`/`runCode`/`sendCommand`/`sendConfig`. See [Itential Gateway — External Secrets Overview](https://docs.itential.com/itential-gateway/5/secrets/external-secrets/overview) for the complete list. Whichever one triggers it, the path is identical, and the plaintext secret never travels back to Platform: ``` Itential Platform - • Inventory Manager device sync + • Inventory Manager Nodes • Integration Model instances (Gateway-executed) • Config Manager command templates • GatewayManager tasks (runService, runCode, sendCommand, sendConfig, ...)