The RHTPA exploit intelligence operator automates the deployment and lifecycle management of the RHTPA exploit intelligence stack on Red Hat OpenShift clusters.
The operator manages the following components:
- Cache: Reverse proxy and cache for external APIs (NVD, GitHub, FIRST, NVIDIA NIM, OpenAI)
- Engine: Primary vulnerability analysis engine
- WebApp: Quarkus-based UI and API
Database: RHTPA exploit intelligence requires a MongoDB instance for data persistence. The operator does not provision MongoDB — you must create the database separately and configure the connection before deploying the stack.
- OpenShift: Version 4.16 or later
- Persistent storage (default storage class configured)
- Cluster-admin privileges
Obtain the following credentials before you begin. You will use these in Step 3: Configure secrets.
| Key | Description | Where to get it |
|---|---|---|
ghsa_api_key |
GitHub Personal Access Token for the GitHub Security Advisory API | github.com/settings/tokens — generate a new fine-grained token |
nvidia_api_key |
NVIDIA NGC API key | org.ngc.nvidia.com |
ngc_api_key |
NVIDIA NGC organization key | org.ngc.nvidia.com |
nvd_api_key |
National Vulnerability Database API key | nvd.nist.gov/developers/request-an-api-key |
serpapi_api_key |
SerpAPI key for web search | serpapi.com/manage-api-key |
For a MongoDB instance, see Connecting to MongoDB.
Navigate to Operators → OperatorHub or Ecosystem → Software Catalog and search for RHTPA exploit intelligence, then click Install.
Wait for the operator to become ready. The namespace depends on the install mode selected during installation (for example, openshift-operators for all-namespaces mode):
oc get csv -n <operator-namespace> | grep exploit-iq-operatoroc new-project exploit-iqGenerate the HMAC_SECRET_KEY:
export HMAC_SECRET_KEY=$(openssl rand -base64 32)oc create secret generic exploit-iq-secret \
--from-literal=ghsa_api_key="<YOUR_GITHUB_TOKEN>" \
--from-literal=nvidia_api_key="<YOUR_NVIDIA_KEY>" \
--from-literal=ngc_api_key="<YOUR_NGC_KEY>" \
--from-literal=nvd_api_key="<YOUR_NVD_KEY>" \
--from-literal=serpapi_api_key="<YOUR_SERPAPI_KEY>" \
--from-literal=hmac-secret-key="$HMAC_SECRET_KEY"Download the pull secret file from your registry service account, then run the following command.
The operator does not generate this Docker config Secret from exploit-iq-secret.
oc create secret generic image-registry-credentials \
--from-file=.dockerconfigjson=<path-to-pull-secret.json> \
--type=kubernetes.io/dockerconfigjsonMode A — host and port:
oc create secret generic mongodb-credentials \
--from-literal=host="<your-host>:27017" \
--from-literal=username="<app-username>" \
--from-literal=password="<app-password>"Mode B — connection string (Atlas or SRV):
oc create secret generic mongodb-credentials \
--from-literal=connection-string="mongodb+srv://<user>:<password>@<cluster>/"The operator generates a random OAuth client secret automatically. To provide your own:
export OAUTH_CLIENT_SECRET=$(openssl rand -base64 32)
export OPENSHIFT_DOMAIN=$(oc get dns cluster -o jsonpath='{.spec.baseDomain}')
oc create secret generic oauth-client-secret \
--from-literal=client-secret="$OAUTH_CLIENT_SECRET" \
--from-literal=openshift-domain="$OPENSHIFT_DOMAIN"Create an ExploitIQStack instance:
Important: The sample CR file is an example only. You must review and verify that the secret names in the file match the secrets you created in Step 3 before applying it.
For available sample CRs and guidance on which to use for your environment, see the samples README.
oc apply -f config/samples/exploitiq_v1alpha1_exploitiqstack.yamlExample CR:
apiVersion: exploitiq.openshift.io/v1alpha1
kind: ExploitIQStack
metadata:
name: exploit-iq
spec:
webApp:
registryCredentialsSecretRef:
name: image-registry-credentials
database:
mode: hostPort # or: connectionString (see Step 3 — MongoDB connection)
secretRef:
name: mongodb-credentialsBy default the stack sends text-embedding requests to the NVIDIA-hosted endpoint. To
self-host the embedder (or point it at a different endpoint), set spec.embedding:
| Parameter | Role | Default when unset |
|---|---|---|
spec.embedding.baseURL |
Upstream endpoint the cache proxies embedding requests to (drives NGINX_UPSTREAM_NIM_EMBED). Set this to a self-hosted embedding NIM to keep embeddings in-cluster. |
NVIDIA-hosted endpoint (https://integrate.api.nvidia.com) |
spec.embedding.modelName |
Identifier of the embedding model served at baseURL (drives the engine's EMBEDDER_MODEL_NAME). |
Engine built-in default (nvidia/nv-embedqa-e5-v5) |
spec.embedding.authSecretRef |
Secret key holding the API key for the embedding upstream. Supplies NVIDIA_API_KEY to the nginx-cache init container, which rewrites the Authorization header on /nim_embed requests. Required when llm.mode: openai, llm.authSecretRef is set, and the embedding upstream requires API key auth — without it the cache sends Authorization: Bearer EMPTY and receives 401. |
Unset — falls back to exploit-iq-secret.nvidia_api_key (nim mode) or EMPTY (openai mode with LLM key configured) |
spec:
embedding:
baseURL: http://nim-embed.<namespace>.svc:8000
modelName: nvidia/nv-embedqa-e5-v5
# authSecretRef: required when llm.mode is 'openai' with llm.authSecretRef set
# and the embedding upstream requires API key auth.
# authSecretRef:
# name: exploit-iq-secret
# key: nvidia_api_keyOmit the embedding block entirely to keep the NVIDIA-hosted default. See the
API reference for the full field list.
Monitor progress:
oc get exploitiqstack exploit-iq -wAfter the stack is running, grant users access by adding them to the exploit-iq-view group:
oc adm groups new exploit-iq-view
oc adm groups add-users exploit-iq-view <username>For additional post-deployment options, see the kustomize deployment guide.
On OpenShift, the operator creates one NetworkPolicy for each operand of an ExploitIQStack:
exploit-iq (engine), exploit-iq-client (web app) and nginx-cache (cache). Each policy selects only the
pods of that CR and operand, by app.kubernetes.io/instance=<CR name> and app.kubernetes.io/component
(engine, webapp or cache). The CR owns the policies, so deleting the CR deletes them. The operator
creates no NetworkPolicy on Kubernetes.
Each policy sets both Ingress and Egress. Every operand can always reach cluster DNS (openshift-dns,
UDP and TCP port 5353), the OpenShift router on TCP 443 and 80, and the internet on any port. The internet
rule allows 0.0.0.0/0 (when the cluster has IPv4 CIDRs) and ::/0 (when it has IPv6 CIDRs), each
except the pod and service CIDRs of the same IP family. IPv4-only, dual-stack and IPv6-only clusters are
supported. Pod and service IPs of other workloads are blocked unless a rule below allows them.
The operator watches the cluster network config (network.config.openshift.io/cluster). When the pod or
service CIDRs change, it reconciles every ExploitIQStack and updates the policies.
The router egress rule keeps Route hostnames (*.apps.<domain>) reachable. On some platforms (Azure,
GCP, MetalLB) the router address is a cluster virtual IP, so traffic to a Route hostname reaches router pod
IPs inside the pod network, which the internet rule excludes. Without the router rule, OpenShift OAuth
login, an in-cluster Keycloak exposed by a Route, and a KServe model exposed by a Route would fail.
Every ingress rule that allows the OpenShift router also allows the host-network namespaces
(policy-group.network.openshift.io/host-network). On bare metal, vSphere, OpenStack and Nutanix the
default router runs on the host network, and OVN-Kubernetes treats its traffic as host-network traffic,
not as traffic from the router namespace.
| Operand | Ingress allowed | Other egress allowed |
|---|---|---|
| engine | TCP 8080 from the cache and web app pods of the stack. TCP 6006 (tracing UI) from the OpenShift router and the host network, only when spec.engine.tracingRouteEnabled is true. |
TCP 8080 to the cache pods. TCP 8443 to the web app pods. Any port to the namespace of an in-cluster spec.auth.oidc.issuerURL. |
| webapp | TCP 8443 from the OpenShift router and the host network, from the engine pods of the stack, and from add-on pods (see below). TCP 9000 from the router and the host network, only when spec.webApp.swaggerUIRouteEnabled is true, and from openshift-user-workload-monitoring, only when the cluster has the monitoring API. |
TCP 8080 to the cache and engine pods. Any port to the namespace of an in-cluster MongoDB host (taken from the database Secret) or OIDC issuer. |
| cache | TCP 8080 from the engine and web app pods of the stack. | TCP 8080 to the engine pods. Any port to the namespace of an in-cluster spec.llm.baseURL or spec.embedding.baseURL. |
An "in-cluster" target is a Service host such as <service>.<namespace>, <service>.<namespace>.svc
or <service>.<namespace>.svc.cluster.local. The operator allows any port in that namespace, because a
policy matches the pod port and not the Service port. An external name with two labels, such as
example.com, also gets an unused rule for a namespace named com. Traffic to that host still works. A short host without dots (for example mongodb:27017) allows any pod in the
CR namespace. Other hosts, such as internet names and IPs outside the cluster CIDRs, use the
internet rule (Route hostnames use the router egress rule or the internet rule).
To stop the operator from managing the policies, set spec.networkPolicy.enabled to false. The default
is true. The operator then deletes the three policies.
spec:
networkPolicy:
enabled: falseThe web app accepts port 8443 from pods in the same namespace that carry the label
app.kubernetes.io/part-of: exploitiq. Add this label to the pod template of an add-on that calls the web
app, for example the ExploitIQ MCP server. Without it, calls from the add-on time out.
An MCP server that you installed before this label was required must be redeployed with the new label before, or together with, the operator upgrade. Otherwise its calls to the web app time out as soon as the operator creates the policies.
When spec.ingress.mode is headless, the operator creates no Route. The web app policy still allows
port 8443 only from the OpenShift router (and the host network), the engine pods and the add-on pods. A
custom ingress controller or gateway that is not the OpenShift router cannot reach the web app on 8443.
Create your own policy for it, for example:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-webapp
namespace: <stack namespace>
spec:
podSelector:
matchLabels:
app.kubernetes.io/instance: <CR name>
app.kubernetes.io/component: webapp
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: <ingress controller namespace>
ports:
- protocol: TCP
port: 8443The operator cannot know a destination that depends on the analysis, for example a git server or a container registry that runs in another namespace of the cluster (such as the OpenShift image registry on port 5000), or a serverless KServe model whose traffic goes through a gateway in another namespace. The policies block these. NetworkPolicies are additive, so create your own policy to allow them. This example lets the engine pods connect to a git server in another namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-engine-to-<destination>
namespace: <stack namespace>
spec:
podSelector:
matchLabels:
app.kubernetes.io/instance: <CR name>
app.kubernetes.io/component: engine
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: <destination namespace>
ports:
- protocol: TCP
port: <destination pod port>Use app.kubernetes.io/component: webapp for registry scans by the web app, and
app.kubernetes.io/component: cache for a model behind a gateway in another namespace.
To read the cluster pod and service CIDRs, the operator needs get, list and watch on networks in
the config.openshift.io API group. The role in the operator bundle already grants this. If the operator
cannot read the CIDRs, it does not apply the policies or roll out the Deployments, and it retries.
Delete the stack instance:
oc delete exploitiqstack --all -n <namespace>Uninstall the operator from Operators → Installed Operators in the web console.