id: hashicorp-vault-seal-status-and-ha-standby-sentinel
namespace: company.security
description: |
Audits HashiCorp Vault cluster nodes for sealed state and high-availability (HA)
leader failover status via Vault sys/health API, opens a critical GitHub SecOps
outage ticket, and alerts on-call infrastructure security engineers on Slack.
inputs:
- id: vault_address
type: STRING
displayName: HashiCorp Vault Cluster Address
defaults: "https://vault.internal.company.com:8200"
- id: target_repository
type: STRING
displayName: SecOps Infrastructure Tracking Repository
defaults: my-org/security-infrastructure
triggers:
- id: check_vault_health_frequent
type: io.kestra.plugin.core.trigger.Schedule
cron: "*/5 * * * *"
disabled: true
tasks:
- id: probe_vault_sys_health
type: io.kestra.plugin.scripts.python.Script
description: Interrogates Vault sys/health endpoint to evaluate seal status,
initialization, and HA active leadership
containerImage: python:3.11-slim
beforeCommands:
- pip install requests kestra
script: |
import requests
from kestra import Kestra
addr = "{{ inputs.vault_address }}"
# sys/health returns HTTP 200 (active), 429 (unsealed standby), 501 (not initialized), 503 (sealed)
url = f"{addr}/v1/sys/health"
try:
response = requests.get(url, timeout=15)
status_code = response.status_code
data = response.json() if response.content else {}
except Exception as err:
print(f"Failed to connect to Vault cluster: {err}")
data = {"sealed": True, "initialized": False, "error": str(err)}
status_code = 503
is_sealed = data.get("sealed", True)
is_initialized = data.get("initialized", True)
cluster_name = data.get("cluster_name", "unknown")
version = data.get("version", "unknown")
is_standby = data.get("standby", False)
is_critical = is_sealed or not is_initialized
print(f"Vault {addr} (Cluster: {cluster_name}): sealed={is_sealed}, initialized={is_initialized}, status={status_code}")
Kestra.outputs({
"status_code": status_code,
"is_sealed": is_sealed,
"is_initialized": is_initialized,
"is_critical": is_critical,
"cluster_name": cluster_name,
"version": version
})
- id: branch_on_vault_status
type: io.kestra.plugin.core.flow.If
description: Branches based on whether Vault is sealed, uninitialized, or degraded
condition: "{{ outputs.probe_vault_sys_health.vars.is_critical == true }}"
then:
- id: create_vault_outage_issue
type: io.kestra.plugin.github.issues.Create
description: Logs a critical P1 GitHub issue documenting Vault cluster sealed status
oauthToken: "{{ secret('GITHUB_TOKEN') }}"
repository: "{{ inputs.target_repository }}"
title: "[P1 Security Alert] HashiCorp Vault Cluster is SEALED / Uninitialized:
{{ outputs.probe_vault_sys_health.vars.cluster_name }}"
body: |
## 🚨 CRITICAL: HashiCorp Vault Cluster Sealed Outage
Automated cluster health sentinel detected that HashiCorp Vault is in a sealed or uninitialized state:
- **Vault Address:** `{{ inputs.vault_address }}`
- **Cluster Name:** `{{ outputs.probe_vault_sys_health.vars.cluster_name }}`
- **Sealed Status:** **{{ outputs.probe_vault_sys_health.vars.is_sealed }}**
- **Initialized:** `{{ outputs.probe_vault_sys_health.vars.is_initialized }}``
- **Vault Version:** `{{ outputs.probe_vault_sys_health.vars.version }}`
Immediate unseal ceremony (auto-unseal KMS verification or Shamir key shards) is required to restore secrets access.
*Generated automatically by Kestra Vault Health Sentinel.*
- id: alert_secops_slack
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Alerts on-call security response channels on Slack with immediate
unseal escalation
url: "{{ secret('SLACK_WEBHOOK') }}"
payload: |
{{ {
"text": "🚨 *CRITICAL: HashiCorp Vault Cluster SEALED*\n*Address:* `" ~ inputs.vault_address ~ "` (Cluster: `" ~ outputs.probe_vault_sys_health.vars.cluster_name ~ "`)\n*Status:* Sealed: *" ~ outputs.probe_vault_sys_health.vars.is_sealed ~ "*\n*Outage Ticket:* <" ~ (outputs.create_vault_outage_issue.issueUrl ?? "#") ~ "|View GitHub P1 Incident>"
} | toJson }}
else:
- id: log_vault_healthy
type: io.kestra.plugin.core.log.Log
message: "Vault cluster {{ outputs.probe_vault_sys_health.vars.cluster_name }}
is unsealed and operational (Version: {{
outputs.probe_vault_sys_health.vars.version }})."
- id: log_audit_complete
type: io.kestra.plugin.core.log.Log
description: Audit trail summarizing the Vault seal check execution
message: "Vault health probe completed. sealed={{
outputs.probe_vault_sys_health.vars.is_sealed }}, initialized={{
outputs.probe_vault_sys_health.vars.is_initialized }}."
errors:
- id: alert_probe_failure
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Alerts Slack when the health check execution encounters an unhandled error
url: "{{ secret('SLACK_WEBHOOK') }}"
payload: |
{{ {
"text": "⚠️ *Vault Health Sentinel Failed* in execution `" ~ execution.id ~ "`. Unable to query `" ~ inputs.vault_address ~ "`."
} | toJson }}