|
| 1 | +<!-- |
| 2 | + Licensed to the Apache Software Foundation (ASF) under one |
| 3 | + or more contributor license agreements. See the NOTICE file |
| 4 | + distributed with this work for additional information |
| 5 | + regarding copyright ownership. The ASF licenses this file |
| 6 | + to you under the Apache License, Version 2.0 (the |
| 7 | + "License"); you may not use this file except in compliance |
| 8 | + with the License. You may obtain a copy of the License at |
| 9 | +
|
| 10 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +
|
| 12 | + Unless required by applicable law or agreed to in writing, |
| 13 | + software distributed under the License is distributed on an |
| 14 | + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | + KIND, either express or implied. See the License for the |
| 16 | + specific language governing permissions and limitations |
| 17 | + under the License. |
| 18 | +--> |
| 19 | + |
| 20 | +# Apache CloudStack Terraform Provider Security Threat Model — delta (draft) |
| 21 | + |
| 22 | +> **Delta document.** Inherits §3, §4 B1, and §7 from |
| 23 | +> `cloudstack-threat-model-draft.md`. Read the main model first. |
| 24 | +
|
| 25 | +## §1 Header |
| 26 | + |
| 27 | +- **Project:** Apache CloudStack Terraform Provider |
| 28 | + (`apache/cloudstack-terraform-provider`) — Terraform provider that |
| 29 | + drives the CloudStack JSON API as a Hashicorp Terraform plugin. |
| 30 | +- **Commit:** `f19bffc` (HEAD of `main` at draft time). |
| 31 | +- **Date:** 2026-05-29. |
| 32 | +- **Authors:** ASF Security team draft. |
| 33 | +- **Status:** Draft delta over `cloudstack-threat-model-draft.md`. |
| 34 | +- **Version binding:** as of the commit above. Provider released at |
| 35 | + `v0.5.x` series *(documented: `README.md` example)*. |
| 36 | +- **Reporting:** as in the main model. |
| 37 | +- **Provenance legend:** as in the main model. |
| 38 | +- **Draft confidence:** 8 documented / 0 maintainer / 10 inferred. |
| 39 | + |
| 40 | +**About the project.** A Terraform provider plugin written in Go, |
| 41 | +built on top of `apache/cloudstack-go`. Each Terraform resource |
| 42 | +(`cloudstack_instance`, `cloudstack_network`, `cloudstack_template`, |
| 43 | +…) and data source maps to one or more CloudStack API calls. Runs |
| 44 | +inside the Terraform plugin host as a separate process invoked by |
| 45 | +the `terraform` binary over the Terraform plugin RPC protocol |
| 46 | +*(documented: `README.md`, Terraform plugin architecture)*. |
| 47 | + |
| 48 | +## §2 Scope and intended use |
| 49 | + |
| 50 | +**Primary intended use.** *(documented — README)* Operators describe |
| 51 | +their CloudStack-managed infrastructure as Terraform resources, then |
| 52 | +`terraform apply` drives the API to converge. Used for IaC-style |
| 53 | +infrastructure provisioning. |
| 54 | + |
| 55 | +**Deployment shape.** A short-lived Go process launched by the |
| 56 | +Terraform binary on the operator's workstation or a CI runner. No |
| 57 | +long-running daemon, no listener. |
| 58 | + |
| 59 | +**Caller expectations.** The caller (the operator / CI) is trusted to: |
| 60 | + |
| 61 | +- supply provider config (`api_url`, `api_key`, `secret_key`, |
| 62 | + `http_get_only`, `timeout`) via either the provider block or env |
| 63 | + vars, |
| 64 | +- not source provider config from end-user input, |
| 65 | +- store the Terraform state file (which may contain resource IDs and |
| 66 | + some sensitive metadata) per Terraform's own best practices. |
| 67 | + |
| 68 | +**Component-family table.** |
| 69 | + |
| 70 | +| Family | Representative entry | Touches outside the process? | In this delta? | |
| 71 | +| --- | --- | --- | --- | |
| 72 | +| Provider config / client (`cloudstack/config.go`) | `Config.NewClient()` *(documented)* | **yes — network + creds via cloudstack-go** | yes | |
| 73 | +| Resource implementations (`cloudstack/resource_cloudstack_*.go`, ~80 resources) | CRUD lifecycles | inherited from client | yes | |
| 74 | +| Data sources (`cloudstack/data_source_cloudstack_*.go`, ~30 data sources) | read-only lifecycles | inherited from client | yes | |
| 75 | +| `main.go` (provider entry) | Terraform plugin handshake | Terraform RPC | yes | |
| 76 | +| `website/`, `scripts/`, `CHANGELOG.md` | docs + release tooling | n/a | **out of model** *(§3)* | |
| 77 | +| Tests under `cloudstack/*_test.go` | acceptance tests | network | **out of model** *(§3)* | |
| 78 | +| `vendor/` (if vendored) | upstream Go deps | n/a | **out of model** *(§3)* | |
| 79 | + |
| 80 | +## §3 Out of scope (explicit non-goals) |
| 81 | + |
| 82 | +The main model's §3 applies. **Additional** out-of-scope items |
| 83 | +specific to the Terraform provider: |
| 84 | + |
| 85 | +1. **Terraform state file confidentiality and storage.** Where the |
| 86 | + operator stores Terraform state (local, S3, Terraform Cloud, …) |
| 87 | + and what protections are applied are operator decisions out of |
| 88 | + model. *(inferred — Q1)* |
| 89 | +2. **Server-side correctness of management-server responses.** Same |
| 90 | + as the Go SDK delta. |
| 91 | +3. **Terraform plugin RPC trust.** The provider trusts the `terraform` |
| 92 | + binary that invoked it. |
| 93 | +4. **`vendor/`, `website/`, `scripts/`, tests.** |
| 94 | +5. **The four sibling repos.** |
| 95 | +6. **Terraform itself.** Bugs in HashiCorp Terraform are upstream. |
| 96 | +7. **HCL configuration trust.** A malicious `*.tf` file is `OUT-OF-MODEL: |
| 97 | + trusted-input` — operator's responsibility. |
| 98 | + |
| 99 | +## §4 Trust boundaries and data flow |
| 100 | + |
| 101 | +The boundary is **the Terraform plugin handshake** plus the |
| 102 | +provider-block config. Provider config (`api_url`, `api_key`, |
| 103 | +`secret_key`, `http_get_only`) flows from the operator's environment |
| 104 | +into `Config.NewClient()` *(documented: `cloudstack/config.go`)*. The |
| 105 | +client then makes B1-shape API calls; see the Go SDK delta for the |
| 106 | +signing flow. |
| 107 | + |
| 108 | +Crucially, *(documented: `cloudstack/config.go` line ~36)*: |
| 109 | + |
| 110 | +```go |
| 111 | +cs := cloudstack.NewAsyncClient(c.APIURL, c.APIKey, c.SecretKey, false) |
| 112 | +cs.HTTPGETOnly = c.HTTPGETOnly |
| 113 | +``` |
| 114 | + |
| 115 | +The fourth `NewAsyncClient` argument is **hardcoded to `false`** — |
| 116 | +meaning **`InsecureSkipVerify: true` is unconditionally set** in the |
| 117 | +TLS config of every Terraform-provider client *(documented: see the Go |
| 118 | +SDK `cloudstack.go` line ~216)*. That is the single most security- |
| 119 | +relevant fact in this delta and is the centerpiece of §14 Q2 below. |
| 120 | +The provider does **not** appear to expose a `verify_ssl` provider |
| 121 | +attribute to the operator *(inferred — needs maintainer confirmation |
| 122 | +via Q2)*. |
| 123 | + |
| 124 | +## §5 Assumptions about the environment |
| 125 | + |
| 126 | +- **Host**: Linux/macOS/Windows operator workstation or CI runner; |
| 127 | + Terraform 1.0+ *(documented: `README.md`)*. |
| 128 | +- **Go**: 1.20+ *(documented: `README.md`)*. |
| 129 | +- **Filesystem**: Terraform state file under operator-chosen backend. |
| 130 | +- **Network**: outbound HTTPS to `api_url`. |
| 131 | +- **What the provider does not do**: no listener, no daemon, no |
| 132 | + global state mutation outside Terraform plugin handshake |
| 133 | + *(inferred — Q3)*. |
| 134 | + |
| 135 | +## §5a Build-time and configuration variants |
| 136 | + |
| 137 | +| Knob | Default | Stance | Effect | |
| 138 | +| --- | --- | --- | --- | |
| 139 | +| `api_url` | none | operator config | endpoint | |
| 140 | +| `api_key`, `secret_key` | none | operator config | credentials | |
| 141 | +| `http_get_only` | per provider block | when `true`, signatures land in URL — see Go SDK delta Q3 | transport choice | |
| 142 | +| `timeout` | provider default *(inferred — Q4)* | bounds async polling | | |
| 143 | +| TLS verification | **hardcoded off (`verifyssl=false`)** *(documented: `cloudstack/config.go` line ~36)* | **maintainer ruling required** *(inferred — Q2)* | **all HTTPS calls skip cert verification** | |
| 144 | + |
| 145 | +## §6 Assumptions about inputs |
| 146 | + |
| 147 | +| Entry point | Parameter | Attacker-controllable in the model? | Caller must enforce | |
| 148 | +| --- | --- | --- | --- | |
| 149 | +| Provider block | `api_url`, `api_key`, `secret_key`, `http_get_only`, `timeout` | **no** — operator / CI config | not from end-user input | |
| 150 | +| HCL resource definitions | resource attributes | **no** — IaC author | not from end-user input | |
| 151 | +| HTTP response | JSON body | trusted (B1) | typed-decoded | |
| 152 | + |
| 153 | +## §7 Adversary model |
| 154 | + |
| 155 | +Main-model §7 applies. **Adjustments specific to this provider**: |
| 156 | + |
| 157 | +- "Unauthenticated network peer reaching `:8080`" is upstream. |
| 158 | +- **A network adversary between the operator / CI runner and the |
| 159 | + CloudStack management server can man-in-the-middle the API |
| 160 | + conversation undetected**, because TLS verification is disabled at |
| 161 | + the provider layer. Whether this is a `VALID` report (provider |
| 162 | + should change) or `BY-DESIGN: property-disclaimed` (provider |
| 163 | + intentionally delegates TLS to the operator's network) is Q2. |
| 164 | + |
| 165 | +## §8 Security properties the provider provides |
| 166 | + |
| 167 | +### T1 — HMAC-SHA1 signature via `cloudstack-go` |
| 168 | + |
| 169 | +- **Property.** Each API call carries a valid HMAC-SHA1 signature per |
| 170 | + main-model §8 P1, via the Go SDK. |
| 171 | +- **Conditions / violation / severity.** As main model §8 P1. |
| 172 | + |
| 173 | +### T2 — Terraform plugin handshake |
| 174 | + |
| 175 | +- **Property.** The provider speaks only the Terraform plugin RPC |
| 176 | + protocol on stdin/stdout to its parent `terraform` process. |
| 177 | +- **Conditions.** Invoked by Terraform. |
| 178 | +- **Violation symptom.** Provider accepts commands from a non-Terraform |
| 179 | + caller. |
| 180 | +- **Severity.** Security-critical, `VALID`. |
| 181 | + |
| 182 | +## §9 Security properties the provider does *not* provide |
| 183 | + |
| 184 | +- **No TLS verification of the management-server cert** — TLS-verify |
| 185 | + is hardcoded off in `cloudstack/config.go` *(documented)*. This is |
| 186 | + the headline non-property and the single biggest §14 question. |
| 187 | +- **No protection of `secret_key` in Terraform state.** Provider-level |
| 188 | + secrets may end up in plan output and state. |
| 189 | +- **No re-validation of management-server response correctness.** |
| 190 | +- **No defence against malicious Terraform HCL.** A `*.tf` file with |
| 191 | + destructive resource definitions executes as written. |
| 192 | + |
| 193 | +### False-friend properties |
| 194 | + |
| 195 | +- **The provider running over HTTPS is not "TLS-protected" in the |
| 196 | + authentication sense** because cert verification is off. Any |
| 197 | + on-path attacker can MitM, present any cert, see all signed |
| 198 | + requests, and replay them within their `expires` window. |
| 199 | + |
| 200 | +## §10 Downstream responsibilities |
| 201 | + |
| 202 | +The operator / CI MUST: |
| 203 | + |
| 204 | +1. Run the provider only over a network where the |
| 205 | + operator-to-management-server path is already trusted (private |
| 206 | + network, VPN, internal CI runner inside the CloudStack control |
| 207 | + plane), because the provider does not verify the management-server |
| 208 | + cert. *(pending Q2 — if the PMC adds a `verify_ssl` knob, this |
| 209 | + responsibility narrows.)* |
| 210 | +2. Treat the Terraform state file as sensitive — `secret_key` and |
| 211 | + resource secrets may live there. |
| 212 | +3. Source `api_key` / `secret_key` from a secret manager, not from |
| 213 | + the HCL file. |
| 214 | +4. Match provider version to the management-server version. |
| 215 | +5. Apply `http_get_only` only when URL logs cannot leak the signature. |
| 216 | + |
| 217 | +## §11 Known misuse patterns |
| 218 | + |
| 219 | +- Running the provider over an untrusted network (public Internet) |
| 220 | + expecting TLS verification. |
| 221 | +- Hardcoding `secret_key` in HCL or env vars committed to a repo. |
| 222 | +- Storing Terraform state in a backend without per-team ACLs. |
| 223 | +- Sharing one set of `api_key` / `secret_key` across multiple CI |
| 224 | + jobs without isolation. |
| 225 | + |
| 226 | +## §11a Known non-findings (recurring false positives) |
| 227 | + |
| 228 | +- **"HMAC-SHA1 — SHA1 is deprecated."** → `KNOWN-NON-FINDING` per main |
| 229 | + model §11a. |
| 230 | +- **"`InsecureSkipVerify: true` is hardcoded in `Config.NewClient`."** |
| 231 | + This is the *actual provider behaviour* *(documented: |
| 232 | + `cloudstack/config.go` line ~36)*. Whether it is a `VALID` finding |
| 233 | + or `BY-DESIGN: property-disclaimed` is Q2. **Do not silently |
| 234 | + suppress.** |
| 235 | +- **"Secrets appear in Terraform state."** Out of model — Terraform |
| 236 | + state security is operator's job. → `OUT-OF-MODEL: trusted-input`. |
| 237 | +- **"Tests in `cloudstack/*_test.go` have weak input handling."** |
| 238 | + Out of model. → `OUT-OF-MODEL: unsupported-component`. |
| 239 | + |
| 240 | +## §12 Conditions that would change this delta |
| 241 | + |
| 242 | +- A `verify_ssl` (or `insecure_skip_verify`) provider attribute is |
| 243 | + added — would change Q2. |
| 244 | +- A switch in default to verifying TLS — would change §9 first |
| 245 | + bullet. |
| 246 | +- A new resource that introduces a new credential-bearing state shape |
| 247 | + (e.g. a `cloudstack_user_credentials` resource that persists secrets |
| 248 | + outside Terraform state). |
| 249 | +- Change in signing algorithm at the main-model layer. |
| 250 | + |
| 251 | +## §13 Triage dispositions |
| 252 | + |
| 253 | +Use the same table as the main model. |
| 254 | + |
| 255 | +## §14 Open questions for the maintainers |
| 256 | + |
| 257 | +**Q1.** Out of scope: Terraform state file storage and encryption. |
| 258 | +Confirm. |
| 259 | + |
| 260 | +**Q2.** **Highest-leverage question in this delta.** The provider |
| 261 | +calls `cloudstack.NewAsyncClient(c.APIURL, c.APIKey, c.SecretKey, |
| 262 | +false)` in `cloudstack/config.go` line ~36 — hardcoding |
| 263 | +`verifyssl=false` and thus `InsecureSkipVerify: true` on every HTTP |
| 264 | +call. |
| 265 | + |
| 266 | +- (a) Is this intentional (the provider assumes operators run only |
| 267 | + on a trusted control-plane network and explicitly opts out of TLS |
| 268 | + verification), and should the §9 first bullet stay? |
| 269 | +- (b) Or should the provider expose a `verify_ssl` attribute and |
| 270 | + default it to `true`, in which case the current hardcoding is a |
| 271 | + `VALID` finding to fix? |
| 272 | + |
| 273 | +The text of §9 and §10 assumes (a) — please confirm or correct. |
| 274 | +*(maps to §5a, §9, §10, §11a)* |
| 275 | + |
| 276 | +**Q3.** Confirm §5 negative side-effect inventory: no env-var |
| 277 | +consumption beyond Terraform plugin standard, no signal handlers, no |
| 278 | +global mutation. |
| 279 | + |
| 280 | +**Q4.** Confirm `timeout` default for the cloudstack-go async client |
| 281 | +as used by the provider. |
| 282 | + |
| 283 | +**Q5.** Where in the provider's docs is the credential-handling |
| 284 | +responsibility documented for HCL authors? |
| 285 | + |
| 286 | +**Q6.** Does the provider redact `secret_key` from Terraform plan |
| 287 | +output? From `terraform show`? |
| 288 | + |
| 289 | +**Q7.** Does the provider integrate with a Vault or other |
| 290 | +secret-manager pattern, or is the operator expected to wire that in |
| 291 | +externally? |
| 292 | + |
| 293 | +**Q8.** Meta — should this delta live at `docs/threat-model.md` in |
| 294 | +`apache/cloudstack-terraform-provider`, or in the website tree? |
| 295 | + |
| 296 | +**Q9.** When the main model's signing algorithm changes (e.g. v3 → |
| 297 | +v4, SHA1 → SHA256), what is the release-cadence commitment to update |
| 298 | +the provider's vendored `cloudstack-go`? |
| 299 | + |
| 300 | +**Q10.** Confirm the unsupported-component list (tests, website, |
| 301 | +scripts, vendor). |
0 commit comments