|
| 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 CloudMonkey 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 CloudMonkey (`apache/cloudstack-cloudmonkey`) |
| 28 | + — interactive shell and CLI for the CloudStack management server. |
| 29 | +- **Commit:** `76df16c` (HEAD of `main` at draft time). |
| 30 | +- **Date:** 2026-05-29. |
| 31 | +- **Authors:** ASF Security team draft. |
| 32 | +- **Status:** Draft delta over `cloudstack-threat-model-draft.md`. |
| 33 | +- **Version binding:** as of the commit above. |
| 34 | +- **Reporting:** as in the main model. |
| 35 | +- **Provenance legend:** as in the main model. |
| 36 | +- **Draft confidence:** 7 documented / 0 maintainer / 9 inferred. |
| 37 | + |
| 38 | +**About the project.** CloudMonkey is a Go-language interactive shell |
| 39 | +and command-line tool that simplifies CloudStack management |
| 40 | +*(documented: `README.md`)*. It is the modern rewrite of the legacy |
| 41 | +Python `cloudmonkey`. It is compatible with Apache CloudStack 4.9+. Its |
| 42 | +moving parts: the `cli/` interactive REPL with autocompletion, the |
| 43 | +`cmd/` command dispatch, the `config/` package that owns credential |
| 44 | +storage and TLS-verify policy, and a bundled `apis.json` describing the |
| 45 | +CloudStack API surface *(documented: `config/apis.json`)*. |
| 46 | + |
| 47 | +## §2 Scope and intended use |
| 48 | + |
| 49 | +**Primary intended use.** Run interactively from an operator's |
| 50 | +workstation or non-interactively from a CI / scripting context to drive |
| 51 | +the CloudStack JSON API. Used by admins and end users. |
| 52 | + |
| 53 | +**Deployment shape.** A single user-space binary, run on the operator's |
| 54 | +machine. No daemon, no listener. |
| 55 | + |
| 56 | +**Caller expectations.** The caller (the operator running `cmk`) is |
| 57 | +trusted to: |
| 58 | + |
| 59 | +- supply a `~/.cmk/config` (INI-style) file (or env-equivalent) with |
| 60 | + the management-server URL, API key, secret, and `verifycert` |
| 61 | + preference, |
| 62 | +- not run untrusted CloudMonkey extension scripts. |
| 63 | + |
| 64 | +**Component-family table.** |
| 65 | + |
| 66 | +| Family | Representative entry | Touches outside the process? | In this delta? | |
| 67 | +| --- | --- | --- | --- | |
| 68 | +| Config loader and signer (`config/config.go`) | `cfg.NewRequest` | **yes — network + creds** | yes | |
| 69 | +| REPL / autocompletion (`cli/`) | `cmk` interactive prompt | reads `~/.cmk/` config and history | yes | |
| 70 | +| Command dispatch (`cmd/`) | one-shot CLI invocation | as above | yes | |
| 71 | +| Bundled `apis.json` | static description of API surface | none | yes | |
| 72 | +| `snap/`, `Dockerfile` packaging | builds the binary for distribution | n/a at runtime | **out of model** *(§3)* | |
| 73 | +| `vendor/` | vendored Go dependencies | n/a | **out of model** *(§3)* — vendored upstream code is in-model only at the wrapper boundary | |
| 74 | + |
| 75 | +## §3 Out of scope (explicit non-goals) |
| 76 | + |
| 77 | +The main model's §3 applies. **Additional** out-of-scope items |
| 78 | +specific to CloudMonkey: |
| 79 | + |
| 80 | +1. **Where the operator stores `~/.cmk/config`.** File permissions on |
| 81 | + the config file are the operator's responsibility. *(inferred — Q1)* |
| 82 | +2. **Server-side correctness of CloudStack responses.** Same as the |
| 83 | + Go SDK delta. |
| 84 | +3. **TLS verification when `verifycert = false`.** When the operator |
| 85 | + sets `verifycert = false` in the config, the HTTP client uses |
| 86 | + `InsecureSkipVerify: true` *(documented: `config/config.go` line |
| 87 | + ~226)*. That is operator choice. |
| 88 | +4. **Operator typing wrong commands.** A wrong destructive command |
| 89 | + correctly executed is not a vulnerability. |
| 90 | +5. **`vendor/`, `snap/`, `Dockerfile`, `performrelease.sh`.** |
| 91 | +6. **The four sibling repos.** |
| 92 | + |
| 93 | +## §4 Trust boundaries and data flow |
| 94 | + |
| 95 | +The boundary is **the operator's invocation of `cmk` and its access to |
| 96 | +`~/.cmk/config`**. The config file is operator-supplied credentials |
| 97 | ++ endpoint + TLS-verify policy. Per-call API parameters typed at the |
| 98 | +REPL are likewise operator input. |
| 99 | + |
| 100 | +Bytes that come back from the management server (JSON responses) are |
| 101 | +trusted control-plane content per the main model B1 transition. |
| 102 | + |
| 103 | +**Per-call request shape** *(inferred — Q2)*: CloudMonkey uses the same |
| 104 | +HMAC-SHA1 signing flow as the Go SDK (likely via the SDK or a near-copy). |
| 105 | +See main model §8 P1 and §11a "SHA1 / constant-time" notes. |
| 106 | + |
| 107 | +## §5 Assumptions about the environment |
| 108 | + |
| 109 | +- **Host OS**: Linux, macOS, Windows; binaries published on GitHub |
| 110 | + Releases and as Docker / snap packages *(documented: `README.md`)*. |
| 111 | +- **Filesystem**: writes a config file under `~/.cmk/`; writes a |
| 112 | + shell-history file *(inferred — Q1)*. |
| 113 | +- **Network**: outbound HTTPS to the configured `apiurl`. |
| 114 | +- **Auth**: stdlib TLS, opt-out via `verifycert = false`. |
| 115 | +- **What CloudMonkey does not do**: no listener, no env-var |
| 116 | + consumption beyond shell defaults, no background process *(inferred |
| 117 | + — Q3)*. |
| 118 | + |
| 119 | +## §5a Build-time and configuration variants |
| 120 | + |
| 121 | +| Knob | Default | Stance | Effect | |
| 122 | +| --- | --- | --- | --- | |
| 123 | +| `verifycert` | `true` *(inferred — Q4)* | dev / self-signed | flips `InsecureSkipVerify` *(documented: `config/config.go` line ~226)* | |
| 124 | +| `apikey` / `secretkey` | none | operator config | persisted in `~/.cmk/config` | |
| 125 | +| `output` (`json`/`text`/`table`/`csv`) | per-config | display | not security-relevant | |
| 126 | +| `timeout` | per-config | bounds async polling | not security-relevant by itself | |
| 127 | +| history-file path | `~/.cmk/history` *(inferred — Q5)* | operator | may contain sensitive params (passwords, API keys typed inline) | |
| 128 | +| `theme`, profiles | per-config | operator | not security-relevant | |
| 129 | + |
| 130 | +## §6 Assumptions about inputs |
| 131 | + |
| 132 | +| Entry point | Parameter | Attacker-controllable in the model? | Caller must enforce | |
| 133 | +| --- | --- | --- | --- | |
| 134 | +| `~/.cmk/config` | URL, key, secret, verifycert | **no** — operator config | filesystem permissions; do not check into VCS | |
| 135 | +| Interactive REPL | typed command | **no** — operator input | nothing | |
| 136 | +| `-p` profile selection from command line | name | **no** | nothing | |
| 137 | +| Bundled `apis.json` | static description | **no** — ships with binary | n/a | |
| 138 | +| HTTP response | JSON body | trusted (B1) | typed-decoded | |
| 139 | + |
| 140 | +## §7 Adversary model |
| 141 | + |
| 142 | +Main-model §7 applies. **Adjustments specific to CloudMonkey**: |
| 143 | + |
| 144 | +- "Unauthenticated network peer reaching `:8080`" actor is upstream of |
| 145 | + CloudMonkey. |
| 146 | +- An additional adversary worth naming: **a local attacker on the |
| 147 | + operator's host with non-operator UID who can read `~/.cmk/config` |
| 148 | + or `~/.cmk/history`**. Whether CloudMonkey enforces strict file |
| 149 | + perms on these files is Q1. |
| 150 | + |
| 151 | +## §8 Security properties the CLI provides |
| 152 | + |
| 153 | +### C1 — HMAC-SHA1 signature per the main model (via shared signer logic) |
| 154 | + |
| 155 | +- **Property.** Each API call carries a valid HMAC-SHA1 signature, with |
| 156 | + the canonical parameter string. *(inferred — Q2; needs maintainer |
| 157 | + confirmation that CloudMonkey uses the same signer as the Go SDK or |
| 158 | + a vetted equivalent.)* |
| 159 | +- **Conditions / violation / severity.** As main model §8 P1. |
| 160 | + |
| 161 | +### C2 — TLS verification on by default |
| 162 | + |
| 163 | +- **Property.** *(inferred — Q4)* default `verifycert = true`. |
| 164 | +- **Conditions / violation / severity.** As Go-SDK delta §8 S2. |
| 165 | + |
| 166 | +### C3 — Strict permissions on `~/.cmk/config` |
| 167 | + |
| 168 | +- **Property.** *(inferred — Q1)* CloudMonkey writes its config with |
| 169 | + mode 0600 (or similar) on POSIX so that other users on the same host |
| 170 | + cannot read the API secret. |
| 171 | +- **Violation symptom.** Config file is world-readable after `cmk` |
| 172 | + writes it. |
| 173 | +- **Severity.** Security-critical, `VALID` per main-model §13. |
| 174 | + |
| 175 | +## §9 Security properties the CLI does *not* provide |
| 176 | + |
| 177 | +- **No protection against a local attacker on the same host with |
| 178 | + shell access.** If they can read `~/.cmk/config`, they have the API |
| 179 | + secret. |
| 180 | +- **No history-file scrubbing.** Sensitive parameters typed inline |
| 181 | + (e.g. `createUser password=...`) end up in `~/.cmk/history` |
| 182 | + *(inferred — Q5)*. |
| 183 | +- **No defence when `verifycert = false`.** |
| 184 | +- **No re-validation of management-server response correctness.** |
| 185 | + |
| 186 | +### False-friend properties |
| 187 | + |
| 188 | +- **`verifycert = false` is not a "test mode."** It silently disables |
| 189 | + TLS verification across all subsequent calls. |
| 190 | +- **HMAC-SHA1 — see main model §11a.** |
| 191 | + |
| 192 | +## §10 Downstream responsibilities |
| 193 | + |
| 194 | +The operator running `cmk` MUST: |
| 195 | + |
| 196 | +1. Keep `~/.cmk/config` mode 0600 and outside any shared/synced |
| 197 | + location. |
| 198 | +2. Set `verifycert = true` unless connecting to a known dev cluster. |
| 199 | +3. Avoid typing API secrets / passwords on the interactive prompt |
| 200 | + (they land in history). Prefer scripted invocations that read from |
| 201 | + a secret store. |
| 202 | +4. Rotate `apikey` / `secretkey` on suspicion of compromise. |
| 203 | +5. Match `cmk` version to the management-server version where API |
| 204 | + compatibility matters *(inferred — Q6)*. |
| 205 | + |
| 206 | +## §11 Known misuse patterns |
| 207 | + |
| 208 | +- Checking `~/.cmk/config` into a Git repo. |
| 209 | +- Setting `verifycert = false` in production. |
| 210 | +- Sharing `~/.cmk/` across users on a multi-user host. |
| 211 | +- Running `cmk` over an unencrypted SSH session that gets logged on |
| 212 | + the jump host. |
| 213 | + |
| 214 | +## §11a Known non-findings (recurring false positives) |
| 215 | + |
| 216 | +- **"HMAC-SHA1 — SHA1 is deprecated."** → `KNOWN-NON-FINDING` per main |
| 217 | + model §11a. |
| 218 | +- **"`InsecureSkipVerify` is reachable from a config setting."** Gated |
| 219 | + by `verifycert`. → `OUT-OF-MODEL: trusted-input`. |
| 220 | +- **"Config file contains plaintext credentials."** By design — see |
| 221 | + §10 item 1. → `BY-DESIGN: property-disclaimed`. |
| 222 | +- **"`vendor/` has CVE-X."** Vendored upstream. → `OUT-OF-MODEL: |
| 223 | + unsupported-component` (upstream pointer). |
| 224 | + |
| 225 | +## §12 Conditions that would change this delta |
| 226 | + |
| 227 | +- A credential-store integration (keychain / agent) replacing the |
| 228 | + plaintext config file. |
| 229 | +- A history-scrubbing feature. |
| 230 | +- Change in signing algorithm at the main-model layer. |
| 231 | + |
| 232 | +## §13 Triage dispositions |
| 233 | + |
| 234 | +Use the same table as the main model. |
| 235 | + |
| 236 | +## §14 Open questions for the maintainers |
| 237 | + |
| 238 | +**Q1.** Does CloudMonkey set strict permissions (mode 0600) on |
| 239 | +`~/.cmk/config` when it writes it? Does it warn the operator if the |
| 240 | +file is found with looser perms? |
| 241 | + |
| 242 | +**Q2.** Does CloudMonkey use `apache/cloudstack-go` as its signer, or |
| 243 | +does it have its own near-copy? If a copy, does it stay in sync? Does |
| 244 | +it set `signatureversion=3`? |
| 245 | + |
| 246 | +**Q3.** Confirm CloudMonkey has no background daemon, no |
| 247 | +env-var-credential channel, no listener. |
| 248 | + |
| 249 | +**Q4.** Confirm `verifycert` default — `true`? |
| 250 | + |
| 251 | +**Q5.** Does `~/.cmk/history` redact sensitive parameter values |
| 252 | +(passwords, secret keys typed inline)? Is history per-profile or |
| 253 | +shared? |
| 254 | + |
| 255 | +**Q6.** Compatibility matrix between `cmk` version and CloudStack |
| 256 | +version — where is it documented? |
| 257 | + |
| 258 | +**Q7.** Meta — should this delta live at `docs/threat-model.md` in |
| 259 | +`apache/cloudstack-cloudmonkey`? |
0 commit comments