Skip to content

Commit ec0cdd1

Browse files
Refactor policy configuration (#4026)
1 parent 5f56294 commit ec0cdd1

123 files changed

Lines changed: 4217 additions & 709 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,103 @@
1-
# vSphere cloud connection
1+
# Set up the vSphere cloud connection
22

3-
Create or choose a vCenter user that can read the inventory you want Workbench to query. A read-only role at the vCenter, datacenter, cluster, or folder scope is usually enough for inventory tables.
3+
Connect Cloud Query to a vCenter inventory. This integration is read-only: it lists VMs, hosts, datastores, and networks. It does not power VMs on or off, change config, or install agents.
44

5-
Use these values in the connection form:
5+
The form needs a **vCenter SDK endpoint**, a **vCenter SSO user**, and that user's **password**.
66

7-
- **vCenter SDK endpoint:** `https://<vcenter-host>/sdk`
8-
- **User:** a vCenter SSO user such as `administrator@vsphere.local`, or another account with read permissions
9-
- **Password:** the password for that user
10-
- **Allow unverified TLS certificates:** enable this only when vCenter uses an internal or self-signed certificate
7+
## Find the SDK endpoint
8+
9+
The SOAP management API shares the vCenter hostname and HTTPS port with the vSphere Client. The difference is the **path**, not the port.
10+
11+
| What | URL |
12+
|---|---|
13+
| vSphere Client (browser UI) | `https://<vcenter-host>/ui` |
14+
| SOAP SDK (this form) | `https://<vcenter-host>/sdk` |
15+
| Automation REST API | `https://<vcenter-host>/api` |
16+
17+
1. Log in to the vSphere Client.
18+
2. Copy the hostname from the browser address bar. Ignore `/ui`, `/vsphere-client`, and any later path.
19+
3. Enter `https://<vcenter-host>/sdk`.
20+
21+
Example: if the client is `https://vcenter.example.com/ui`, the SDK endpoint is `https://vcenter.example.com/sdk`. Default HTTPS port is **443**. Use `https://<vcenter-host>:<port>/sdk` only when vCenter listens on a non-default port.
1122

12-
You can verify that the SDK endpoint is exposed with:
23+
Do not use:
24+
25+
- `/ui` or `/vsphere-client` (HTML, not SOAP)
26+
- `/api` or `/rest` (vSphere Automation REST, not the VIM SDK this connection uses)
27+
- An ESXi host URL, unless you only have a standalone host. Full inventory (datacenters, clusters, all VMs) requires **vCenter**.
28+
29+
A hostname without a scheme or path (`vcenter.example.com`) also works; the client defaults to `https` and `/sdk`. Prefer the full `https://…/sdk` URL so it is obvious which API you are targeting.
30+
31+
Verify the SDK is reachable:
1332

1433
```bash
1534
curl -k https://<vcenter-host>/sdk/vimServiceVersions.xml
1635
```
1736

18-
The response should be XML listing the supported `urn:vim25` versions.
37+
The response should be XML listing `urn:vim25` versions, not an HTML login page.
38+
39+
Enable **Allow unverified TLS certificates** only when vCenter uses an internal or self-signed certificate.
40+
41+
## Create credentials
42+
43+
Create a dedicated vCenter identity. Do not use the ESXi `root` user, and do not paste a vSphere Client session cookie.
44+
45+
### Local SSO user (typical)
46+
47+
1. In the vSphere Client, open **Menu → Administration → Single Sign On → Users and Groups**.
48+
2. Select the SSO domain (default `vsphere.local`).
49+
3. **Add User**. Choose a name such as `plural-cloud-query` and set a password.
50+
51+
The form **User** value must include the domain: `plural-cloud-query@vsphere.local`. A bare username without `@vsphere.local` will fail SSO login.
52+
53+
### Active Directory or other identity source
54+
55+
If vCenter is joined to an identity source, create or pick a service account there, then use `user@domain.example` or `DOMAIN\user` as vCenter expects for that source. Still assign vCenter permissions below; directory membership alone does not grant inventory access.
56+
57+
## Grant permissions for the APIs Cloud Query calls
58+
59+
Cloud Query uses the Steampipe vSphere plugin. It authenticates with username/password, then:
60+
61+
1. SOAP `POST https://<vcenter-host>/sdk` (VIM / vim25)
62+
- `SessionManager.Login`
63+
- `ViewManager.CreateContainerView` on the vCenter root folder
64+
- `ContainerView.Retrieve` for `VirtualMachine`, `HostSystem`, `Datastore`, and `Network`
65+
2. Automation REST (VM tags only, table `vsphere_vm`)
66+
- Session login on the vAPI endpoint
67+
- `cis/tagging` `GetAttachedTags` and `GetCategory`
68+
69+
It does not call guest operations, power APIs, or write APIs.
70+
71+
### Inventory (required)
72+
73+
Use the built-in **Read-only** role. That role is exactly the system privileges needed to log in and read object properties:
74+
75+
- `System.Anonymous`
76+
- `System.View`
77+
- `System.Read`
78+
79+
Assign it so the user can see the objects you want queried:
80+
81+
1. **Menu → Administration → Access Control → Global Permissions → Add**, or right-click the vCenter object (or a datacenter, cluster, or folder) → **Add Permission**.
82+
2. Select the SSO user.
83+
3. Role: **Read-only**.
84+
4. Enable **Propagate to children**.
85+
86+
Without propagate, ContainerView returns nothing below the object you assigned. Scope the permission to a folder or cluster if the connection should not see the whole inventory.
87+
88+
Do not grant Administrator, Virtual machine **Power user**, or other write roles.
89+
90+
### Tags (needed for `vsphere_vm.tags`)
91+
92+
vSphere tags are global objects, not children of a VM. Inventory Read-only on a VM does not include tag reads.
93+
94+
Assign **Read-only** as a **Global Permission** (with propagate) so the user can call tagging REST. If tagging REST fails, VM inventory queries can fail entirely because tag lookup runs as part of listing VMs.
95+
96+
You do not need **vSphere Tagging → Create / Edit / Delete** or **Assign or Unassign vSphere Tag**.
97+
98+
## Complete the configuration
99+
100+
- **vCenter SDK endpoint:** `https://<vcenter-host>/sdk`
101+
- **User:** SSO principal, for example `plural-cloud-query@vsphere.local`
102+
- **Password:** that user's password
103+
- **Allow unverified TLS certificates:** only for internal or self-signed vCenter certs
Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,94 @@
1-
# Splunk tool setup
1+
# Set up the Splunk integration
22

3-
Use this guide to fill `URL` and either `Bearer token` or `Username` + `Password`.
3+
Connect a workbench to logs already stored in Splunk Enterprise or Splunk Cloud. This integration is read-only: it streams search results and does not ingest data or install a Splunk forwarder.
44

5-
## 1) Create read-only access for searches
5+
The workbench form needs a **management/REST API base URL** and either an **authentication token** or a **username and password**.
66

7-
Create a dedicated Splunk role/user with read/search capabilities only.
7+
## Find the API base URL
88

9-
Common capabilities for API search access:
10-
- `search`
11-
- `rest_properties_get`
9+
The `URL` field is the Splunk **management port** host, not the Splunk Web URL. The integration appends `/services/search/v2/jobs/export` itself, so do not include that path (or any `/en-US/app/...` path).
1210

13-
Also grant access only to the indexes this tool should query.
11+
Use HTTPS. Default management port is **8089**. Do not use Splunk Web port **8000** or HTTP Event Collector (HEC) port **8088**.
1412

15-
## 2) Choose auth method
13+
### Splunk Cloud Platform
1614

17-
- Token auth (recommended): create an auth token for the integration user.
18-
- Basic auth: use dedicated service username/password.
15+
1. Log in to Splunk Web.
16+
2. Copy the hostname from the browser address bar (for example `acme.splunkcloud.com`). Ignore everything after the hostname.
17+
3. Enter `https://<hostname>:8089` in the form.
1918

20-
## 3) Fill the Workbench tool form
19+
Example: if Splunk Web is `https://acme.splunkcloud.com/en-US/app/search/search`, the API base URL is `https://acme.splunkcloud.com:8089`.
2120

22-
- `URL`: Splunk management/query endpoint base URL
23-
- Option A: `Bearer token`
24-
- Option B: `Username` + `Password`
21+
Port 8089 is often closed until you allowlist the caller:
22+
23+
- Add the workbench egress IPs with the Admin Config Service `search-api` IP allow list, or
24+
- Open a Splunk support case to enable REST API access on 8089.
25+
26+
Free trial Splunk Cloud stacks cannot use the REST API.
27+
28+
### Splunk Enterprise
29+
30+
1. Identify the search head hostname or IP you use to run searches (not a heavy forwarder).
31+
2. Confirm the management port: **Settings → Server settings → General settings → Management port** (default `8089`).
32+
3. Enter `https://<search-head-host>:8089`.
33+
34+
For local or self-signed TLS certificates, append `?insecure_skip_verify=true` to the URL (for example `https://splunk.internal:8089?insecure_skip_verify=true`).
35+
36+
## Create credentials
37+
38+
Create a dedicated integration identity. Do **not** use an HTTP Event Collector (HEC) token from **Settings → Data Inputs → HTTP Event Collector**. HEC tokens ingest events; they cannot call the search REST API.
39+
40+
### Option A: authentication token (recommended)
41+
42+
These are JWT authentication tokens from **Settings → Tokens**, not HEC tokens.
43+
44+
1. Enable token authentication if it is off: **Settings → Tokens → Token Settings → Enable token authentication**. This requires the `edit_tokens_settings` capability (typically `admin` or `sc_admin`).
45+
2. Create the dedicated user and role described below.
46+
3. Go to **Settings → Tokens → New Token**.
47+
4. Set **User** to that dedicated user and **Audience** to a short purpose string such as `plural-workbench`.
48+
5. Create the token and copy it immediately. Splunk will not show the full value again.
49+
6. Paste it into **Bearer token**. Leave **Username** and **Password** empty.
50+
51+
### Option B: username and password
52+
53+
1. Create the dedicated user described below and set a password.
54+
2. Fill **Username** and **Password**. Leave **Bearer token** empty.
55+
56+
## Grant permissions for the search export API
57+
58+
This integration calls only:
59+
60+
`POST {URL}/services/search/v2/jobs/export`
61+
62+
with form fields `search`, `earliest_time`, `latest_time`, and `output_mode=json`. It does not create saved searches, write indexes, or call HEC.
63+
64+
Create a dedicated role: **Settings → Users and authentication → Roles → New Role** (on older Splunk Web: **Settings → Access controls → Roles**).
65+
66+
### Capabilities
67+
68+
Grant only:
69+
70+
- `search` — required to run SPL on `/services/search/v2/jobs/export`
71+
- `rest_properties_get` — commonly required for REST search clients (covers `/services/properties`; included on Splunk's built-in `user` role)
72+
73+
Do not grant write or admin capabilities such as `indexes_edit`, `admin_all_objects`, `edit_user`, or `edit_roles`.
74+
75+
### Index access (required for data)
76+
77+
Capabilities alone do not return events. On the same role, open **Indexes** and allow only the indexes this tool should query:
78+
79+
- **Indexes** / **Indexes allowed** (`srchIndexesAllowed`) — indexes the user may search
80+
- **Indexes searched by default** (`srchIndexesDefault`) — optional defaults when a query does not name an index
81+
82+
If these are empty or omit the target index, the export call can succeed with no logs.
83+
84+
### Create the user
85+
86+
**Settings → Users and authentication → Users → New User** (or **Settings → Access controls → Users**). Assign only the role above. Create the authentication token for this user, not for a personal admin account.
87+
88+
## Complete the configuration
89+
90+
- **URL:** management/REST API base URL (`https://<host>:8089`, no search path)
91+
- **Bearer token:** authentication token from **Settings → Tokens**, or
92+
- **Username** + **Password:** dedicated service user
93+
94+
After saving, attach the tool to a workbench and run a log query against an index the role can search.

assets/src/components/awaiting-review/AwaitingReviewItem.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,23 @@ export function AwaitingReviewItem({
6565
/>
6666
</Flex>
6767

68-
{configuration?.aiApproval?.enabled && approvalResult?.result && (
68+
{approvalResult?.result && (
6969
<Flex
7070
direction="column"
7171
gap="xsmall"
7272
padding="small"
7373
css={{
74-
backgroundImage: `linear-gradient(${theme.colors['fill-one']}, ${theme.colors['fill-one']}), linear-gradient(316deg, #E3A966 0%, #8961F4 32%, #747AF6 71%, #6D94F9 100%)`,
75-
backgroundClip: 'padding-box, border-box',
76-
backgroundOrigin: 'border-box',
77-
border: '1px solid transparent',
74+
...(configuration?.aiApproval?.enabled
75+
? {
76+
backgroundImage: `linear-gradient(${theme.colors['fill-one']}, ${theme.colors['fill-one']}), linear-gradient(316deg, #E3A966 0%, #8961F4 32%, #747AF6 71%, #6D94F9 100%)`,
77+
backgroundClip: 'padding-box, border-box',
78+
backgroundOrigin: 'border-box',
79+
border: '1px solid transparent',
80+
}
81+
: {
82+
backgroundColor: theme.colors['fill-one'],
83+
border: theme.borders.default,
84+
}),
7885
borderRadius: theme.borderRadiuses.large,
7986
}}
8087
>
@@ -85,10 +92,12 @@ export function AwaitingReviewItem({
8592
}}
8693
>
8794
<CaptionP $color="text-xlight">Approval decision</CaptionP>
88-
<AiSparkleFilledIcon
89-
color="icon-info"
90-
size={13}
91-
/>
95+
{configuration?.aiApproval?.enabled && (
96+
<AiSparkleFilledIcon
97+
color="icon-info"
98+
size={13}
99+
/>
100+
)}
92101
</StretchedFlex>
93102
<StackAIApprovalChip
94103
approvalResult={approvalResult?.result}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Converts `terraform show -json` output into the reduced plan Spacelift
3+
* exposes to plan policies, instead of passing the full Terraform document.
4+
*
5+
* See: https://docs.spacelift.io/concepts/policy/terraform-plan-policy
6+
*/
7+
8+
export type TerraformPlanAction =
9+
'create' | 'update' | 'delete' | 'no-op' | 'read'
10+
11+
export type PolicyResourceChange = {
12+
address?: string
13+
type?: string
14+
name?: string
15+
provider_name?: string | null
16+
change: {
17+
actions: TerraformPlanAction[]
18+
before?: unknown
19+
after?: unknown
20+
}
21+
}
22+
23+
export type PolicyPlan = {
24+
terraform_version?: string | null
25+
resource_changes: PolicyResourceChange[]
26+
}
27+
28+
type TerraformChange = {
29+
actions?: TerraformPlanAction[]
30+
before?: unknown
31+
after?: unknown
32+
after_unknown?: unknown
33+
before_sensitive?: unknown
34+
after_sensitive?: unknown
35+
}
36+
37+
type TerraformResourceChange = {
38+
address?: string
39+
mode?: string
40+
type?: string
41+
name?: string
42+
provider_name?: string
43+
change?: TerraformChange
44+
}
45+
46+
type TerraformPlan = {
47+
format_version?: string
48+
terraform_version?: string
49+
resource_changes?: TerraformResourceChange[]
50+
}
51+
52+
export function toPolicyPlan(
53+
plan: TerraformPlan | null | undefined
54+
): PolicyPlan {
55+
if (!plan) return { terraform_version: null, resource_changes: [] }
56+
57+
return {
58+
terraform_version: plan.terraform_version ?? null,
59+
resource_changes: (plan.resource_changes ?? []).map(convertChange),
60+
}
61+
}
62+
63+
function convertChange(change: TerraformResourceChange): PolicyResourceChange {
64+
return {
65+
address: change.address,
66+
type: change.type,
67+
name: change.name,
68+
provider_name: providerName(change.provider_name),
69+
change: {
70+
actions: change.change?.actions ?? [],
71+
before: change.change?.before,
72+
after: change.change?.after,
73+
},
74+
}
75+
}
76+
77+
function providerName(name?: string): string | undefined {
78+
if (!name) return name
79+
const parts = name.split('/')
80+
return parts[parts.length - 1]
81+
}

assets/src/components/stacks/run/Header.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ function StackRunHeaderInfo({ stackRun }): ReactNode {
170170
>
171171
<GitCommitIcon />
172172
{stackRun.git?.ref}
173+
{stackRun.committer && <span>{stackRun.committer}</span>}
173174
</span>
174175
</div>
175176
)

0 commit comments

Comments
 (0)