|
| 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 Go SDK Security Threat Model — delta (draft) |
| 21 | + |
| 22 | +> **Delta document.** This document is a delta over the canonical |
| 23 | +> `apache/cloudstack` threat model. It inherits §3 (out-of-scope), |
| 24 | +> §4 B1 (API trust boundary), and §7 (adversary model) from the |
| 25 | +> main model. It restates only what `apache/cloudstack-go` uniquely |
| 26 | +> introduces. Read the main model first. |
| 27 | +
|
| 28 | +## §1 Header |
| 29 | + |
| 30 | +- **Project:** Apache CloudStack Go SDK (`apache/cloudstack-go`) — Go |
| 31 | + client library that calls the `apache/cloudstack` JSON API. |
| 32 | +- **Commit:** `358fe85` (HEAD of `main` at draft time). |
| 33 | +- **Date:** 2026-05-29. |
| 34 | +- **Authors:** ASF Security team draft, awaiting CloudStack PMC review. |
| 35 | +- **Status:** Draft delta over `cloudstack-threat-model-draft.md`. |
| 36 | +- **Version binding:** as of the commit above. The SDK's release line is |
| 37 | + versioned independently of the management-server release line |
| 38 | + *(documented: `README.md` — "SDK releases tagged based on the ACS |
| 39 | + version")*. |
| 40 | +- **Reporting:** same as the main model — `security@apache.org`. |
| 41 | +- **Provenance legend:** as in the main model. |
| 42 | +- **Draft confidence:** 9 documented / 0 maintainer / 11 inferred. |
| 43 | + |
| 44 | +**About the project.** `cloudstack-go` is a generated-then-curated Go |
| 45 | +library that wraps every CloudStack JSON API command in a typed |
| 46 | +parameter struct and a typed response *(documented: `README.md`, |
| 47 | +`generate/generate.go`)*. Its main moving parts: an HMAC-SHA1 signer |
| 48 | ++ HTTP client (`cloudstack/cloudstack.go`) and one `<Service>.go` file |
| 49 | +per API service group (Account, Address, VirtualMachine, …) generated |
| 50 | +from `generate/listApis.json` against the latest stable CloudStack |
| 51 | +release (currently v4.18.x baseline) *(documented: `README.md`)*. |
| 52 | + |
| 53 | +## §2 Scope and intended use |
| 54 | + |
| 55 | +**Primary intended use.** *(documented — README)* In-process import by |
| 56 | +a Go program that wants to drive a CloudStack management server. The |
| 57 | +embedding application instantiates a client with `NewClient(endpoint, |
| 58 | +apiKey, secretKey, verifySSL)` or `NewAsyncClient(…)`, then issues |
| 59 | +typed API calls. |
| 60 | + |
| 61 | +**Deployment shape.** Always in-process; the SDK is a library, never |
| 62 | +a daemon. |
| 63 | + |
| 64 | +**Caller expectations.** The caller is trusted to: |
| 65 | + |
| 66 | +- choose the management-server endpoint URL, |
| 67 | +- supply a valid `apiKey` + `secretKey` pair, |
| 68 | +- pass `verifySSL = true` *unless* connecting to a known dev cluster |
| 69 | + using a self-signed CloudStack Root CA cert, |
| 70 | +- not source any of the above from end-user input. |
| 71 | + |
| 72 | +**Component-family table.** |
| 73 | + |
| 74 | +| Family | Representative entry | Touches outside the process? | In this delta? | |
| 75 | +| --- | --- | --- | --- | |
| 76 | +| Client + signer (`cloudstack/cloudstack.go`) | `NewAsyncClient`, signer in `cs.sign(params)` | **yes — network + creds** | yes | |
| 77 | +| Per-service generated wrappers (`cloudstack/<Service>.go`, ~600 services) | `cs.VirtualMachine.DeployVirtualMachine(…)` | inherited from client | yes | |
| 78 | +| Code generator (`generate/`) | reads `generate/listApis.json` and emits `cloudstack/*.go` | host filesystem at generation time | **out of model** *(§3)* — generator output is in-model, generator itself is build-time tooling | |
| 79 | +| `examples/` | `examples/AddHost.go`, `examples/CreateDeleteDomain.go`, `mock_test.go` | network | **out of model** *(§3)* | |
| 80 | +| `test/`, `ci/` | tests + CI scaffolding | varies | **out of model** *(§3)* | |
| 81 | +| `cloudstack/*_mock.go` | generated mocks | none | **out of model** *(§3)* | |
| 82 | + |
| 83 | +## §3 Out of scope (explicit non-goals) |
| 84 | + |
| 85 | +The main model's §3 applies in full. **Additional** out-of-scope items |
| 86 | +specific to the SDK: |
| 87 | + |
| 88 | +1. **Storage of `apiKey` / `secretKey` on disk.** Where the caller |
| 89 | + chooses to persist credentials is out of model. *(inferred — Q1)* |
| 90 | +2. **Server-side correctness of the management server's response.** If |
| 91 | + the management server returns wrong data, the SDK forwards it; the |
| 92 | + SDK is not a re-verification layer. *(inferred — Q2)* |
| 93 | +3. **TLS transport configuration.** When the caller passes `verifySSL = |
| 94 | + false` (the fourth `NewAsyncClient` argument), the SDK explicitly |
| 95 | + sets `InsecureSkipVerify: true` *(documented: |
| 96 | + `cloudstack/cloudstack.go` line ~216 |
| 97 | + `TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifyssl}`)*. |
| 98 | + That is a documented caller-supplied weakening; reports of "the SDK |
| 99 | + permits TLS verification to be disabled" are `OUT-OF-MODEL: |
| 100 | + trusted-input` because the caller chose it. |
| 101 | +4. **`examples/`, `cloudstack/*_mock.go`, `test/`, `ci/`.** |
| 102 | +5. **The four sibling repos** — `apache/cloudstack`, |
| 103 | + `apache/cloudstack-cloudmonkey`, `apache/cloudstack-terraform-provider`, |
| 104 | + `apache/cloudstack-kubernetes-provider`. Covered by their own models. |
| 105 | + |
| 106 | +## §4 Trust boundaries and data flow |
| 107 | + |
| 108 | +The only boundary the SDK contributes is **the embedded application's |
| 109 | +call into `NewAsyncClient` (or `NewClient`) and subsequent typed API |
| 110 | +calls**. Bytes the caller passes — endpoint, `apiKey`, `secretKey`, |
| 111 | +verifySSL, per-method parameters — are caller-controlled config and not |
| 112 | +attacker-controlled per the main-model §7 adversary set. |
| 113 | + |
| 114 | +The bytes that come *back* from the management server (JSON responses) |
| 115 | +are trusted control-plane content per the main model's B1 transition. |
| 116 | + |
| 117 | +**Per-call request shape** *(documented: `cloudstack/cloudstack.go` |
| 118 | +lines ~547–575)*: |
| 119 | + |
| 120 | +- the SDK forces `signatureversion=3`, which mandates an `expires` |
| 121 | + parameter (good — see main-model §5a "api.signature.version" row); |
| 122 | +- the SDK computes HMAC-SHA1 of the lowercase-sorted parameter string |
| 123 | + under `secretKey` (`crypto/hmac` + `crypto/sha1`); |
| 124 | +- the signature is base64-encoded and placed in either the POST body |
| 125 | + or the URL (when `HTTPGETOnly` is set); |
| 126 | +- when `HTTPGETOnly = true`, the SDK constructs the URL as |
| 127 | + `baseURL + "?" + paramString + "&signature=" + url.QueryEscape(sig)` |
| 128 | + — note the signature lands in the *URL* (and thus the access log / |
| 129 | + reverse proxy log) for GET requests *(inferred — Q3)*. |
| 130 | + |
| 131 | +## §5 Assumptions about the environment |
| 132 | + |
| 133 | +- **Go version**: per `go.mod` (`go 1.x`) — confirm with maintainer |
| 134 | + *(inferred — Q4)*. |
| 135 | +- **TLS**: provided by `crypto/tls` from the Go stdlib; verification |
| 136 | + default is on, weakened only by the explicit `verifyssl=false` caller |
| 137 | + choice. |
| 138 | +- **Transport**: `net/http` with stdlib defaults; no connection pool |
| 139 | + sharing across `cloudstack.Client` instances *(inferred — Q5)*. |
| 140 | +- **Concurrency**: `cloudstack.Client` is intended to be safe for |
| 141 | + concurrent use *(inferred — Q5)*. |
| 142 | +- **What the SDK does not do to its host**: no global state mutation, |
| 143 | + no signal handlers, no env-var consumption (credentials are passed |
| 144 | + in via the constructor, not read from env). *(inferred — Q6)* |
| 145 | + |
| 146 | +## §5a Build-time and configuration variants |
| 147 | + |
| 148 | +| Knob | Default | Stance | Effect | |
| 149 | +| --- | --- | --- | --- | |
| 150 | +| `verifyssl` arg to `NewClient` / `NewAsyncClient` | passed-in *(no default)* | calling with `false` is documented for dev / self-signed-CA setups | when `false`, `InsecureSkipVerify: true` is set in the TLS config | |
| 151 | +| `cs.HTTPGETOnly` | `false` *(inferred — Q3)* | the Kubernetes / Terraform satellites set this `true` to force GET, which inlines signatures in the URL | when `true`, requests are GET with signature in URL | |
| 152 | +| async timeout via `cs.AsyncTimeout(…)` | `300s` default *(inferred — Q7)* | bounds how long async jobs are polled | timeout exhaustion returns an error, not a hang | |
| 153 | + |
| 154 | +## §6 Assumptions about inputs |
| 155 | + |
| 156 | +| Entry point | Parameter | Attacker-controllable in the model? | Caller must enforce | |
| 157 | +| --- | --- | --- | --- | |
| 158 | +| `NewClient` / `NewAsyncClient` | `endpoint`, `apiKey`, `secretKey`, `verifyssl` | **no** — operator config per §3 item 1 | sanity-check the endpoint URL; do not source from end-user input | |
| 159 | +| Per-service typed call (e.g. `VirtualMachine.NewDeployVirtualMachineParams`) | typed params | **no** — typed params come from the embedding app's own logic | the embedding app applies its own user authn/authz before constructing params | |
| 160 | +| HTTP response | JSON body | trusted as far as the management server is trusted | bytes are JSON-decoded into typed responses | |
| 161 | + |
| 162 | +## §7 Adversary model |
| 163 | + |
| 164 | +Main-model §7 applies. **Adjustments specific to the SDK**: |
| 165 | + |
| 166 | +- The "unauthenticated network peer reaching `:8080`/`:8443`" actor is |
| 167 | + *upstream* of the SDK and not in scope for SDK code. |
| 168 | +- An additional adversary worth naming: **a passive observer of HTTP |
| 169 | + access logs / reverse-proxy logs** when `HTTPGETOnly = true`. The |
| 170 | + signature lands in the URL and thus in any log that records URLs. |
| 171 | + Whether this is a §10 caller responsibility or a §11 misuse is the |
| 172 | + Q3 ruling. *(inferred — Q3)* |
| 173 | + |
| 174 | +## §8 Security properties the SDK provides |
| 175 | + |
| 176 | +### S1 — HMAC-SHA1 signature with `signatureversion=3` |
| 177 | + |
| 178 | +- **Property.** Every API call carries a v3 signature with an `expires` |
| 179 | + parameter, computed via `crypto/hmac` + `crypto/sha1` over the |
| 180 | + canonical parameter string. *(documented: `cloudstack/cloudstack.go` |
| 181 | + lines ~547–575)* |
| 182 | +- **Conditions.** Caller supplied a valid `secretKey`. |
| 183 | +- **Violation symptom.** A request leaves the SDK without a signature, |
| 184 | + or with a signature derived from input outside the canonical |
| 185 | + parameter string. |
| 186 | +- **Severity.** Security-critical, `VALID` per main-model §13. |
| 187 | + |
| 188 | +### S2 — TLS verification on by default |
| 189 | + |
| 190 | +- **Property.** The HTTP client uses stdlib TLS defaults; verification |
| 191 | + is on unless the caller passes `verifyssl = false`. |
| 192 | +- **Conditions.** Caller did not pass `verifyssl = false`. |
| 193 | +- **Violation symptom.** TLS verification skipped despite `verifyssl = |
| 194 | + true`. |
| 195 | +- **Severity.** Security-critical, `VALID`. |
| 196 | + |
| 197 | +### S3 — Constant-time secret-handling at the signer |
| 198 | + |
| 199 | +- **Property.** *(inferred — Q8)* The SDK does not compare secrets, so |
| 200 | + the question is whether the signer leaks the secret via timing / |
| 201 | + branch-predictable paths. The Go `crypto/hmac` is the only consumer. |
| 202 | +- **Conditions.** as above. |
| 203 | +- **Violation symptom.** Side-channel recovery of `secretKey`. |
| 204 | +- **Severity.** Security-critical if reachable; otherwise `OUT-OF-MODEL` |
| 205 | + per main-model §7 side-channel exclusion. |
| 206 | + |
| 207 | +## §9 Security properties the SDK does *not* provide |
| 208 | + |
| 209 | +- **No protection of `apiKey` / `secretKey` at rest.** The caller decides |
| 210 | + where to store them. *(inferred — Q1)* |
| 211 | +- **No defence when `verifyssl = false`.** Explicit caller-supplied |
| 212 | + TLS-verification disablement. |
| 213 | +- **No retry / rate-limit / circuit-breaker by default.** The async |
| 214 | + client polls; that is the only built-in scheduling. |
| 215 | +- **No re-validation of management-server response correctness.** |
| 216 | +- **No log-redaction of signatures or credentials by the SDK.** If the |
| 217 | + embedding application logs request URLs (which would include the |
| 218 | + signature when `HTTPGETOnly = true`), the signature ends up in the |
| 219 | + log. *(inferred — Q3)* |
| 220 | + |
| 221 | +### False-friend properties |
| 222 | + |
| 223 | +- **`HTTPGETOnly = true` is a transport choice, not "more secure".** It |
| 224 | + forces signatures into the URL where they will appear in any URL log. |
| 225 | +- **"HMAC-SHA1" is not a SHA1-broken construction.** Collision attacks |
| 226 | + on SHA1 do not extend to HMAC-SHA1. Reports flagging "SHA1 is |
| 227 | + deprecated" against the signer are `KNOWN-NON-FINDING` per the main |
| 228 | + model's §11a. |
| 229 | + |
| 230 | +## §10 Downstream responsibilities |
| 231 | + |
| 232 | +The embedding Go application MUST: |
| 233 | + |
| 234 | +1. Pass `verifyssl = true` unless connecting to a known dev cluster. |
| 235 | +2. Source `apiKey` / `secretKey` from a secret store, not from |
| 236 | + user-controlled input. |
| 237 | +3. Not log full request URLs when `HTTPGETOnly = true`. |
| 238 | +4. Apply its own timeouts and retries on top of the SDK as appropriate. |
| 239 | +5. Use the SDK version matched to the management-server version |
| 240 | + *(documented: README — "SDK releases tagged based on the ACS |
| 241 | + version")*. |
| 242 | + |
| 243 | +## §11 Known misuse patterns |
| 244 | + |
| 245 | +- Calling `NewAsyncClient(endpoint, key, secret, false)` in production |
| 246 | + (TLS verification disabled). |
| 247 | +- Embedding credentials in source / build artifacts. |
| 248 | +- Using `HTTPGETOnly = true` *and* shipping URL logs off-host. |
| 249 | +- Using a mismatched SDK / server major version and assuming the |
| 250 | + request shape will still verify. |
| 251 | + |
| 252 | +## §11a Known non-findings (recurring false positives) |
| 253 | + |
| 254 | +- **"HMAC-SHA1 — SHA1 is deprecated."** → `KNOWN-NON-FINDING` per main |
| 255 | + model §11a. |
| 256 | +- **"`InsecureSkipVerify: true` is hardcoded."** It is gated by the |
| 257 | + caller-supplied `verifyssl` argument. → `OUT-OF-MODEL: trusted-input`. |
| 258 | +- **"Signature appears in URL."** Only when caller sets `HTTPGETOnly = |
| 259 | + true`. → `OUT-OF-MODEL: non-default-build` if `false` is the default |
| 260 | + per Q3. Else escalate. |
| 261 | +- **"Examples in `examples/` have weak input handling."** Out of |
| 262 | + scope. → `OUT-OF-MODEL: unsupported-component`. |
| 263 | + |
| 264 | +## §12 Conditions that would change this delta |
| 265 | + |
| 266 | +- Change in default signing algorithm in the main model (SHA1 → SHA256). |
| 267 | +- Change in `HTTPGETOnly` default or new option that affects signature |
| 268 | + placement. |
| 269 | +- Addition of credential storage to the SDK. |
| 270 | + |
| 271 | +## §13 Triage dispositions |
| 272 | + |
| 273 | +Use the same table as the main model. |
| 274 | + |
| 275 | +## §14 Open questions for the maintainers |
| 276 | + |
| 277 | +**Q1.** Out-of-scope: where the caller stores `apiKey` / `secretKey` |
| 278 | +on disk. Confirm. |
| 279 | + |
| 280 | +**Q2.** Out-of-scope: revalidating management-server response |
| 281 | +correctness in the SDK. Confirm. |
| 282 | + |
| 283 | +**Q3.** `HTTPGETOnly` default and signature-in-URL leakage — is `false` |
| 284 | +the default and is "do not log URLs when `HTTPGETOnly = true`" a |
| 285 | +documented caller responsibility? *(maps to §5a, §6, §10, §11a)* |
| 286 | + |
| 287 | +**Q4.** Confirm the Go MSRV. |
| 288 | + |
| 289 | +**Q5.** Concurrency: is `cloudstack.Client` documented as safe for |
| 290 | +concurrent use across goroutines? Is the underlying `http.Client` |
| 291 | +shared or per-call? |
| 292 | + |
| 293 | +**Q6.** Confirm the §5 negative side-effect inventory: no env-var |
| 294 | +consumption, no signal handlers, no global mutation. |
| 295 | + |
| 296 | +**Q7.** Confirm `AsyncTimeout` default. |
| 297 | + |
| 298 | +**Q8.** Constant-time / side-channel posture at the signer — confirm |
| 299 | +the SDK delegates entirely to Go stdlib `crypto/hmac` and makes no |
| 300 | +additional claim. |
| 301 | + |
| 302 | +**Q9.** Meta — should this delta document live in `apache/cloudstack-go` |
| 303 | +at `docs/threat-model.md`, or only on the website? |
0 commit comments