On branch main
Initial commit Changes to be committed: new file: README.md new file: abx/apply_nsx_tags_for_tiers/README.md new file: abx/apply_nsx_tags_for_tiers/action.py new file: abx/list_vcenter_vms/README.md new file: abx/list_vcenter_vms/action.py new file: abx/send_email/README.md new file: abx/send_email/action.py new file: blueprints/forms/vdefend-form.json new file: blueprints/vdefend-form-driven.yaml
This commit is contained in:
52
README.md
Normal file
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# vDefend Form-Driven Blueprint (VCF Automation 9)
|
||||||
|
|
||||||
|
This package contains:
|
||||||
|
- `blueprints/vdefend-form-driven.yaml` — Cloud Template that calls Terraform to create vDefend groups/services/rules.
|
||||||
|
- `blueprints/forms/vdefend-form.json` — Custom Form draft that pulls vCenter VMs via ABX and parses CSV ports.
|
||||||
|
- ABX actions:
|
||||||
|
- `list_vcenter_vms` — exposes vCenter inventory for the form.
|
||||||
|
- `apply_nsx_tags_for_tiers` — tags selected VMs with `tier` and `env`.
|
||||||
|
- `send_email` — generic SMTP mailer.
|
||||||
|
|
||||||
|
## Wiring overview
|
||||||
|
|
||||||
|
1) **Create ABX actions** (Python 3):
|
||||||
|
- `list_vcenter_vms`: set constants `VCENTER_SERVER`, `VCENTER_USERNAME`, `VCENTER_PASSWORD`.
|
||||||
|
- `apply_nsx_tags_for_tiers`: none (reads blueprint inputs).
|
||||||
|
- `send_email`: set constants `SMTP_HOST` (and optionally user/pass).
|
||||||
|
|
||||||
|
2) **Import the Terraform module** (re-use the `vdefend_baseline_module` from the previous kit, or point the blueprint to your Git path).
|
||||||
|
|
||||||
|
3) **Create the Cloud Template** from `vdefend-form-driven.yaml`. Map `nsx_*` inputs to **Project Secrets**.
|
||||||
|
|
||||||
|
4) **Attach Custom Form**:
|
||||||
|
- Import `vdefend-form.json` into the Form Designer for this template.
|
||||||
|
- Change the data source `actionId` on `vm_web`, `vm_app`, `vm_db` to the actual ABX ID of `list_vcenter_vms`.
|
||||||
|
|
||||||
|
5) **Event Subscriptions**:
|
||||||
|
- Create a subscription: **Event = Deployment Completed**, **Filter by blueprint name = vdefend-form-driven**.
|
||||||
|
- Add two actions in order:
|
||||||
|
1. `apply_nsx_tags_for_tiers` — Map inputs from the deployment inputs (`vm_web`, `vm_app`, `vm_db`, `env_value`, `nsx_manager_url`, `nsx_username`, `nsx_password`).
|
||||||
|
2. `send_email` — Build `body` and `to_email` using deployment inputs/outputs:
|
||||||
|
- `to_email = requester_email`
|
||||||
|
- `subject = "vDefend policy created: " + app_name`
|
||||||
|
- `body` example:
|
||||||
|
```
|
||||||
|
Application: ${app_name}
|
||||||
|
Environment: ${env_value}
|
||||||
|
|
||||||
|
NSX Section: ${outputs.sectionPath}
|
||||||
|
Groups:
|
||||||
|
Web: ${outputs.groups.web}
|
||||||
|
App: ${outputs.groups.app}
|
||||||
|
DB : ${outputs.groups.db}
|
||||||
|
|
||||||
|
Ports:
|
||||||
|
Web->App: ${inputs.ports_web_to_app}
|
||||||
|
App->DB : ${inputs.ports_app_to_db}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- The policy is tag-driven. After deployment, the ABX action tags your selected VMs: `{tier:web|app|db}` and `{env:<value>}`. The groups in NSX will immediately include them.
|
||||||
|
- To extend rules, add more arrays (e.g., `ports_web_to_db`) and mirror them in the Terraform module.
|
||||||
|
- For strict change control, protect the section with a **lock** or maintain via GitOps-only.
|
||||||
14
abx/apply_nsx_tags_for_tiers/README.md
Normal file
14
abx/apply_nsx_tags_for_tiers/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# ABX: apply_nsx_tags_for_tiers (Python 3)
|
||||||
|
|
||||||
|
Applies NSX tags to selected VMs for tier and environment. Use as a **Deployment Completed** subscription so it runs after the Terraform policy is created.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
- nsx_manager_url, nsx_username, nsx_password
|
||||||
|
- env_value: prod|test|dev (or any string)
|
||||||
|
- vm_web: array of VM names
|
||||||
|
- vm_app: array of VM names
|
||||||
|
- vm_db: array of VM names
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
- Looks up each VM by display_name in NSX Fabric VMs.
|
||||||
|
- Adds tags: `{scope: 'tier', tag: 'web|app|db'}` and `{scope: 'env', tag: env_value}`.
|
||||||
50
abx/apply_nsx_tags_for_tiers/action.py
Normal file
50
abx/apply_nsx_tags_for_tiers/action.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import requests, json
|
||||||
|
|
||||||
|
def _session(nm, user, pwd):
|
||||||
|
s = requests.Session()
|
||||||
|
s.verify = False
|
||||||
|
s.auth = (user, pwd)
|
||||||
|
s.headers.update({"Content-Type": "application/json"})
|
||||||
|
return s
|
||||||
|
|
||||||
|
def _find_by_name(s, nm, name):
|
||||||
|
# A simple scan; for large estates consider filtering parameters & paging
|
||||||
|
r = s.get(f"{nm}/api/v1/fabric/virtual-machines")
|
||||||
|
r.raise_for_status()
|
||||||
|
for vm in r.json().get("results", []):
|
||||||
|
if vm.get("display_name") == name or vm.get("vm_name") == name:
|
||||||
|
return vm.get("external_id")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _apply_tags(s, nm, external_id, tags):
|
||||||
|
body = {"external_id": external_id, "tags": tags}
|
||||||
|
r = s.post(f"{nm}/api/v1/fabric/virtual-machines", params={"action": "add_tags"}, data=json.dumps(body))
|
||||||
|
if r.status_code not in (200,204):
|
||||||
|
raise Exception(f"Tagging failed: {r.status_code} {r.text}")
|
||||||
|
|
||||||
|
def handler(context, inputs):
|
||||||
|
nm = inputs["nsx_manager_url"]
|
||||||
|
user = inputs["nsx_username"]
|
||||||
|
pwd = inputs["nsx_password"]
|
||||||
|
env_value = inputs["env_value"]
|
||||||
|
|
||||||
|
tiers = {
|
||||||
|
"web": inputs.get("vm_web") or [],
|
||||||
|
"app": inputs.get("vm_app") or [],
|
||||||
|
"db": inputs.get("vm_db") or [],
|
||||||
|
}
|
||||||
|
|
||||||
|
s = _session(nm, user, pwd)
|
||||||
|
applied = []
|
||||||
|
|
||||||
|
for tier, names in tiers.items():
|
||||||
|
for name in names:
|
||||||
|
extid = _find_by_name(s, nm, name)
|
||||||
|
if not extid:
|
||||||
|
applied.append({"name": name, "tier": tier, "status": "not_found"})
|
||||||
|
continue
|
||||||
|
tags = [{"scope":"tier","tag":tier},{"scope":"env","tag":env_value}]
|
||||||
|
_apply_tags(s, nm, extid, tags)
|
||||||
|
applied.append({"name": name, "tier": tier, "status": "tagged"})
|
||||||
|
|
||||||
|
return {"result": applied}
|
||||||
11
abx/list_vcenter_vms/README.md
Normal file
11
abx/list_vcenter_vms/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# ABX: list_vcenter_vms (Python 3)
|
||||||
|
|
||||||
|
Returns an array of VM names from vCenter for use in the Custom Form.
|
||||||
|
|
||||||
|
## Constants (recommended)
|
||||||
|
- VCENTER_SERVER (e.g., https://vcsa.lab.legionitgroup.com)
|
||||||
|
- VCENTER_USERNAME
|
||||||
|
- VCENTER_PASSWORD
|
||||||
|
|
||||||
|
## Output format
|
||||||
|
Return a JSON array of strings (VM names) or objects `{ "text": "vm-name", "value": "vm-name" }`.
|
||||||
33
abx/list_vcenter_vms/action.py
Normal file
33
abx/list_vcenter_vms/action.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import os, requests
|
||||||
|
|
||||||
|
def _login(session, base):
|
||||||
|
# Try modern API session (vSphere 8/9)
|
||||||
|
r = session.post(f"{base}/api/session")
|
||||||
|
if r.status_code in (200, 204):
|
||||||
|
return
|
||||||
|
# Fallback to legacy vAPI
|
||||||
|
r = session.post(f"{base}/rest/com/vmware/cis/session")
|
||||||
|
if r.status_code not in (200, 201):
|
||||||
|
raise Exception(f"vCenter login failed: {r.status_code} {r.text}")
|
||||||
|
|
||||||
|
def handler(context, inputs):
|
||||||
|
base = os.getenv("VCENTER_SERVER") or inputs.get("vcenter_server")
|
||||||
|
user = os.getenv("VCENTER_USERNAME") or inputs.get("vcenter_username")
|
||||||
|
pwd = os.getenv("VCENTER_PASSWORD") or inputs.get("vcenter_password")
|
||||||
|
if not (base and user and pwd):
|
||||||
|
raise Exception("Missing vCenter credentials/server. Set VCENTER_* constants or pass in inputs.")
|
||||||
|
s = requests.Session()
|
||||||
|
s.verify = False
|
||||||
|
s.auth = (user, pwd)
|
||||||
|
_login(s, base)
|
||||||
|
# Try modern inventory endpoint
|
||||||
|
r = s.get(f"{base}/api/vcenter/vm")
|
||||||
|
if r.status_code == 200:
|
||||||
|
vms = [vm.get("name") for vm in r.json() if vm.get("name")]
|
||||||
|
else:
|
||||||
|
# Fallback legacy
|
||||||
|
r = s.get(f"{base}/rest/vcenter/vm")
|
||||||
|
r.raise_for_status()
|
||||||
|
vms = [vm.get("name") for vm in r.json().get("value", []) if vm.get("name")]
|
||||||
|
# Return array for multi-select
|
||||||
|
return vms
|
||||||
9
abx/send_email/README.md
Normal file
9
abx/send_email/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# ABX: send_email (Python 3)
|
||||||
|
|
||||||
|
Sends a summary email to the requester with the created section/groups and the chosen ports.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
- smtp_host, smtp_port (25 default), smtp_user/smtp_pass (optional), use_tls (bool)
|
||||||
|
- from_email, to_email, subject, body (text)
|
||||||
|
|
||||||
|
This action is generic; blueprint post-processing can construct a friendly body.
|
||||||
34
abx/send_email/action.py
Normal file
34
abx/send_email/action.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import smtplib, ssl, os
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
|
||||||
|
def handler(context, inputs):
|
||||||
|
smtp_host = inputs.get("smtp_host") or os.getenv("SMTP_HOST")
|
||||||
|
smtp_port = int(inputs.get("smtp_port", 25))
|
||||||
|
smtp_user = inputs.get("smtp_user") or os.getenv("SMTP_USER")
|
||||||
|
smtp_pass = inputs.get("smtp_pass") or os.getenv("SMTP_PASS")
|
||||||
|
use_tls = bool(inputs.get("use_tls", False))
|
||||||
|
|
||||||
|
from_email = inputs["from_email"]
|
||||||
|
to_email = inputs["to_email"]
|
||||||
|
subject = inputs.get("subject", "vDefend policy created")
|
||||||
|
body = inputs.get("body", "")
|
||||||
|
|
||||||
|
msg = MIMEText(body, "plain", "utf-8")
|
||||||
|
msg["Subject"] = subject
|
||||||
|
msg["From"] = from_email
|
||||||
|
msg["To"] = to_email
|
||||||
|
|
||||||
|
if use_tls:
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||||
|
server.starttls(context=context)
|
||||||
|
if smtp_user and smtp_pass:
|
||||||
|
server.login(smtp_user, smtp_pass)
|
||||||
|
server.send_message(msg)
|
||||||
|
else:
|
||||||
|
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||||
|
if smtp_user and smtp_pass:
|
||||||
|
server.login(smtp_user, smtp_pass)
|
||||||
|
server.send_message(msg)
|
||||||
|
|
||||||
|
return {"status": "sent", "to": to_email}
|
||||||
149
blueprints/forms/vdefend-form.json
Normal file
149
blueprints/forms/vdefend-form.json
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "1",
|
||||||
|
"layout": {
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"id": "page1",
|
||||||
|
"title": "vDefend Policy Builder",
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"id": "sec-app",
|
||||||
|
"label": "Application",
|
||||||
|
"fields": [
|
||||||
|
"app_name",
|
||||||
|
"env_value",
|
||||||
|
"requester_email"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sec-vms",
|
||||||
|
"label": "Select VMs",
|
||||||
|
"fields": [
|
||||||
|
"vm_web",
|
||||||
|
"vm_app",
|
||||||
|
"vm_db"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sec-ports",
|
||||||
|
"label": "Ports",
|
||||||
|
"fields": [
|
||||||
|
"ports_web_to_app_csv",
|
||||||
|
"ports_app_to_db_csv"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sec-endpoints",
|
||||||
|
"label": "Endpoints",
|
||||||
|
"fields": [
|
||||||
|
"nsx_manager_url",
|
||||||
|
"nsx_username",
|
||||||
|
"nsx_password"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"app_name": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "Application Name"
|
||||||
|
},
|
||||||
|
"env_value": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "Environment",
|
||||||
|
"enum": [
|
||||||
|
"prod",
|
||||||
|
"test",
|
||||||
|
"dev"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"requester_email": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "Requester Email"
|
||||||
|
},
|
||||||
|
"ports_web_to_app": {
|
||||||
|
"type": "array",
|
||||||
|
"label": "Ports (Web->App)",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
"ports_app_to_db": {
|
||||||
|
"type": "array",
|
||||||
|
"label": "Ports (App->DB)",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
"ports_web_to_app_csv": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "Ports (Web->App) CSV",
|
||||||
|
"default": "80,443",
|
||||||
|
"computeScript": "return form.getValue('ports_web_to_app_csv').split(',').map(s=>Number(s.trim())).filter(n=>!isNaN(n));",
|
||||||
|
"onChangeScript": "form.setValue('ports_web_to_app', eval(field.computeScript));"
|
||||||
|
},
|
||||||
|
"ports_app_to_db_csv": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "Ports (App->DB) CSV",
|
||||||
|
"default": "5432",
|
||||||
|
"computeScript": "return form.getValue('ports_app_to_db_csv').split(',').map(s=>Number(s.trim())).filter(n=>!isNaN(n));",
|
||||||
|
"onChangeScript": "form.setValue('ports_app_to_db', eval(field.computeScript));"
|
||||||
|
},
|
||||||
|
"vm_web": {
|
||||||
|
"type": "array",
|
||||||
|
"label": "Web Tier VMs",
|
||||||
|
"dataSource": {
|
||||||
|
"type": "action",
|
||||||
|
"actionId": "list_vcenter_vms",
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
"multiSelect": true
|
||||||
|
},
|
||||||
|
"vm_app": {
|
||||||
|
"type": "array",
|
||||||
|
"label": "App Tier VMs",
|
||||||
|
"dataSource": {
|
||||||
|
"type": "action",
|
||||||
|
"actionId": "list_vcenter_vms",
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
"multiSelect": true
|
||||||
|
},
|
||||||
|
"vm_db": {
|
||||||
|
"type": "array",
|
||||||
|
"label": "DB Tier VMs",
|
||||||
|
"dataSource": {
|
||||||
|
"type": "action",
|
||||||
|
"actionId": "list_vcenter_vms",
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
"multiSelect": true
|
||||||
|
},
|
||||||
|
"nsx_manager_url": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "NSX Manager URL"
|
||||||
|
},
|
||||||
|
"nsx_username": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "NSX Username"
|
||||||
|
},
|
||||||
|
"nsx_password": {
|
||||||
|
"type": "string",
|
||||||
|
"label": "NSX Password",
|
||||||
|
"encrypted": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"fieldOrder": [
|
||||||
|
"app_name",
|
||||||
|
"env_value",
|
||||||
|
"requester_email",
|
||||||
|
"vm_web",
|
||||||
|
"vm_app",
|
||||||
|
"vm_db",
|
||||||
|
"ports_web_to_app_csv",
|
||||||
|
"ports_app_to_db_csv",
|
||||||
|
"nsx_manager_url",
|
||||||
|
"nsx_username",
|
||||||
|
"nsx_password"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
94
blueprints/vdefend-form-driven.yaml
Normal file
94
blueprints/vdefend-form-driven.yaml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
formatVersion: 1
|
||||||
|
name: vdefend-form-driven
|
||||||
|
version: 1
|
||||||
|
inputs:
|
||||||
|
app_name:
|
||||||
|
type: string
|
||||||
|
title: Application Name
|
||||||
|
description: Logical name used to prefix NSX groups and section.
|
||||||
|
default: vdefend-app
|
||||||
|
env_value:
|
||||||
|
type: string
|
||||||
|
title: Environment
|
||||||
|
enum:
|
||||||
|
- prod
|
||||||
|
- test
|
||||||
|
- dev
|
||||||
|
default: prod
|
||||||
|
requester_email:
|
||||||
|
type: string
|
||||||
|
title: Requester Email
|
||||||
|
# vCenter inventory selection (populated via Custom Form using ABX data source)
|
||||||
|
vm_web:
|
||||||
|
type: array
|
||||||
|
title: Web Tier VMs
|
||||||
|
description: Select one or more VMs for the Web tier
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
vm_app:
|
||||||
|
type: array
|
||||||
|
title: App Tier VMs
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
vm_db:
|
||||||
|
type: array
|
||||||
|
title: DB Tier VMs
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
# Port lists (array of numbers; Custom Form will parse CSV input into arrays)
|
||||||
|
ports_web_to_app:
|
||||||
|
type: array
|
||||||
|
title: Ports (Web -> App)
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
default:
|
||||||
|
- 80
|
||||||
|
- 443
|
||||||
|
ports_app_to_db:
|
||||||
|
type: array
|
||||||
|
title: Ports (App -> DB)
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
default:
|
||||||
|
- 5432
|
||||||
|
# Endpoints / credentials (map these to Project Secrets in production)
|
||||||
|
nsx_manager_url:
|
||||||
|
type: string
|
||||||
|
title: NSX Manager URL
|
||||||
|
nsx_username:
|
||||||
|
type: string
|
||||||
|
encrypted: true
|
||||||
|
nsx_password:
|
||||||
|
type: string
|
||||||
|
encrypted: true
|
||||||
|
|
||||||
|
resources:
|
||||||
|
vdefendPolicy:
|
||||||
|
type: Cloud.Terraform
|
||||||
|
properties:
|
||||||
|
providers:
|
||||||
|
- name: nsxt
|
||||||
|
source: vmware/nsxt
|
||||||
|
version: ">= 3.9.0"
|
||||||
|
module:
|
||||||
|
# point to your Git content source that contains the module path below
|
||||||
|
source: git::https://your.git/VCFA_Avi_vDefend_kit.git//terraform/vdefend_baseline_module
|
||||||
|
variables:
|
||||||
|
nsx_manager_url: ${input.nsx_manager_url}
|
||||||
|
nsx_username: ${input.nsx_username}
|
||||||
|
nsx_password: ${input.nsx_password}
|
||||||
|
domain: "default"
|
||||||
|
app_name: ${input.app_name}
|
||||||
|
env_value: ${input.env_value}
|
||||||
|
services_web_to_app: ${input.ports_web_to_app}
|
||||||
|
services_app_to_db: ${input.ports_app_to_db}
|
||||||
|
create_drop_others_rule: false
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
sectionPath:
|
||||||
|
value: ${resource.vdefendPolicy.outputs.section}
|
||||||
|
groups:
|
||||||
|
value:
|
||||||
|
web: ${resource.vdefendPolicy.outputs.group_web}
|
||||||
|
app: ${resource.vdefendPolicy.outputs.group_app}
|
||||||
|
db: ${resource.vdefendPolicy.outputs.group_db}
|
||||||
Reference in New Issue
Block a user