Add draft project security threat-model document#13293
Conversation
Adds a draft project-level security threat-model document (draft-THREAT-MODEL.md) at repo root, improving discoverability for automated security scanners running against this repository. The file follows the rubric format used by several other ASF projects piloting security-model discoverability. The "draft-" prefix signals this is a proposal for the PMC to review, correct, or reject — not a finalised maintainer-blessed model. Every claim carries a provenance tag (documented / inferred / maintainer) so reviewers can see where each claim originates; §14 collects open questions for the maintainers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #13293 +/- ##
============================================
- Coverage 18.10% 18.10% -0.01%
+ Complexity 16752 16749 -3
============================================
Files 6037 6037
Lines 542796 542796
Branches 66456 66456
============================================
- Hits 98291 98267 -24
- Misses 433460 433488 +28
+ Partials 11045 11041 -4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Markdown / typos / table-shape fixes per the CI lint output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
There's a lot of details in the draft that needs a better set of eyes, so assigning @DaanHoogland @vishesh92 who're also PMC leads on the work. |
| **Q1.** The model assumes CloudStack is "a clustered distributed | ||
| control plane deployed inside an operator-controlled datacenter | ||
| network", not a single-host appliance or a hosted SaaS. Confirm? *(maps | ||
| to §2)* |
There was a problem hiding this comment.
@potiuk , not sure if I should answer you but, the control plane could be clustered but for smaller clouds it can be a single instance of the management server. I am not sure if that is relevant for the question and I am not sure this is the proper way to answer the question.
| **Q2.** Are the SecondaryStorageVM, ConsoleProxyVM, and Virtual Router | ||
| treated as trusted-once-enrolled peers (proposed: **yes**, same shape as | ||
| agents), or do they get their own trust tier? *(maps to §2, §4 B5)* |
There was a problem hiding this comment.
yes, for as far as they are pointing inwards. they do have for some purposed outside interfaces which are supposed to be safe.
| one or more management-server instances (clustered behind a load balancer | ||
| in production), a MariaDB/MySQL database, one usage server, an optional | ||
| SecondaryStorageVM/ConsoleProxyVM/VirtualRouter set of system VMs, and a | ||
| per-hypervisor-host `cloudstack-agent` (for KVM/Hyper-V/baremetal) or |
There was a problem hiding this comment.
| per-hypervisor-host `cloudstack-agent` (for KVM/Hyper-V/baremetal) or | |
| per-hypervisor-host `cloudstack-agent` (for KVM//baremetal) or |
| **Q3.** Are external integrations (LDAP, SAML2 IdP, OAuth2 IdP, NSX | ||
| controller, Netscaler, Tungsten, S3-compatible storage, backup | ||
| providers) modeled as trusted control-plane peers (proposed: **yes**)? If | ||
| trusted, that licenses §3 item 2 and §11a trusted-input dispositions. | ||
| *(maps to §2, §3, §11a)* |
| **Q5.** Vendored upstream code under `systemvm/agent/noVNC/vendor/pako` | ||
| and bundled JaSypt / Bouncy Castle / JSch — is the policy "report | ||
| upstream; we pick up fixes on next sync" (proposed)? *(maps to §3 item 8, | ||
| §11a)* |
There was a problem hiding this comment.
We have no procedure to implement this yet (dependabot is not producing viable PRs for us) I would prefer we do. Any ideas @vishesh92 ?
| | B2 | Web UI → management server (`:8080`) | same as B1 plus session cookie | same as B1 | | ||
| | B3 | Browser → ConsoleProxyVM → hypervisor VNC socket | signed token issued by management server, embedded in URL; encrypted with `ConsoleProxyPasswordBasedEncryptor` *(documented: `server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java`, `ConsoleProxyPasswordBasedEncryptor.java`)* | implicit (signed-token possession) | | ||
| | B4 | Management server ↔ management server (cluster peers) | NIO + TLS, Root CA-issued certs *(documented: `framework/cluster/`, `framework/ca/`)* | peer-trust by valid cert | | ||
| | B5 | Management server → `cloudstack-agent` (KVM/Hyper-V/baremetal) | NIO + TLS on `:8250`; agent uses X.509 client cert issued by Root CA on first connect; cert provisioning is the `SetupKeyStoreCommand` shape *(documented: `agent/src/main/java/com/cloud/agent/Agent.java` `setupAgentKeystore`, `framework/ca/.../CAService.java`, `plugins/ca/root-ca/.../RootCAProvider.java`)*; trust strictness governed by `ca.plugin.root.auth.strictness` (**default `false`** — see §5a) and `ca.plugin.root.allow.expired.cert` (**default `true`** — see §5a) | peer-trust by valid cert | |
There was a problem hiding this comment.
default value for ca.plugin.root.auth.strictness is true for newer setups but for older setups, it remains false even after upgraded.
There was a problem hiding this comment.
ok, so if it is mentioned in the upgrade instructions it is not a concern.
|
|
||
| **Q4.** SecondaryStorageVM HTTP download surface — is the URL token | ||
| per-template ACL-checked, or is the SSVM URL itself a bearer credential | ||
| that any holder can replay? *(maps to §6, §11a)* |
There was a problem hiding this comment.
I think the only mitigation to unauthorised use is timed availability of the download(-token) Not sure how to respond to this. We’ll have to make sure/test this. cc @vishesh92.
@potiuk, I wonder why code analysis did not discover this?
| **Q6.** Is "an operator with `root` on a management-server host, the | ||
| JCEKS keystore + encryption keys, the Root CA private key, or MariaDB | ||
| credentials" out of scope (proposed: **yes**, `OUT-OF-MODEL: | ||
| adversary-not-in-scope`)? *(maps to §3 item 1, §9)* |
|
|
||
| | Knob | Default | Maintainer stance | Effect | | ||
| | --- | --- | --- | --- | | ||
| | `ca.plugin.root.auth.strictness` | **`false`** *(documented: `RootCAProvider.java` line 132)* | **maintainer ruling required**: is the default a supported production posture or a dev-mode setting operators must flip per §10? *(inferred — §14 Q12)* | When `false`, the management server's `RootCACustomTrustManager` does **not** require a client certificate from a peer attempting to connect on `:8250` (agent port) or cluster ports. A peer without a cert is allowed in. | |
There was a problem hiding this comment.
Default is true. It's false only when upgrading from really old cloudstack versions.
| **Q7.** Hypervisor bugs (libvirt / vSphere SDK / XenAPI / Hyper-V API / | ||
| KVM/QEMU itself) — out of scope, report upstream (proposed)? *(maps to | ||
| §3 item 3)* |
There was a problem hiding this comment.
yes, should not lead to any changes but deleting this question, right @potiuk ?
| | `enable.2fa.for.users` / `enable.2fa.for.api` | per-domain toggle *(documented: `plugins/user-two-factor-authenticators/`)* | dev-test default off; production posture depends on PMC ruling *(inferred — §14 Q18)* | When on, users must complete static-pin or TOTP 2FA after login. | | ||
| | `security.encryption.key`, `security.encryption.iv` | auto-generated at first boot *(documented: `framework/security/.../KeysManager.java`)* | trusted secret | Base64-encoded JaSypt master key + IV used to encrypt application-level secrets in the DB. | | ||
| | `auth.password.algorithm` (`hash.user.password`) | bcrypt / pbkdf2 / sha256salted *(documented: `plugins/user-authenticators/{pbkdf2,sha256salted}`)* | **maintainer ruling required**: which is the supported default for new deployments? `md5` and `plain-text` plugins still ship *(documented: `plugins/user-authenticators/{md5,plain-text}`)* — are these legacy-compat-only or in supported production? *(inferred — §14 Q19)* | governs how user passwords are stored | | ||
| | `api.signature.version` | accepts both v1 and v3 *(documented: `ApiServer.java` line ~1053)* | v1 lacks an `expires` parameter; v3 requires expiration | A request with v3 + an expired `expires` is rejected; a v1 request without `expires` is accepted | |
There was a problem hiding this comment.
This isn't configurable and I didn't find any references for api.signature.version in the code.
| | `security.encryption.key`, `security.encryption.iv` | auto-generated at first boot *(documented: `framework/security/.../KeysManager.java`)* | trusted secret | Base64-encoded JaSypt master key + IV used to encrypt application-level secrets in the DB. | | ||
| | `auth.password.algorithm` (`hash.user.password`) | bcrypt / pbkdf2 / sha256salted *(documented: `plugins/user-authenticators/{pbkdf2,sha256salted}`)* | **maintainer ruling required**: which is the supported default for new deployments? `md5` and `plain-text` plugins still ship *(documented: `plugins/user-authenticators/{md5,plain-text}`)* — are these legacy-compat-only or in supported production? *(inferred — §14 Q19)* | governs how user passwords are stored | | ||
| | `api.signature.version` | accepts both v1 and v3 *(documented: `ApiServer.java` line ~1053)* | v1 lacks an `expires` parameter; v3 requires expiration | A request with v3 + an expired `expires` is rejected; a v1 request without `expires` is accepted | | ||
| | `post.requests.and.timestamps.enforced` | per `isPostRequestsAndTimestampsEnforced` *(documented: `ApiServer.java` line ~1074)* | bounds `expires` to a maximum future offset | Prevents an attacker who steals a signed URL with a 10-year expiration from using it forever | |
There was a problem hiding this comment.
Global setting is enforce.post.requests.and.timestamps and not post.requests.and.timestamps.enforced
| | `ca.plugin.root.auth.strictness` | **`false`** *(documented: `RootCAProvider.java` line 132)* | **maintainer ruling required**: is the default a supported production posture or a dev-mode setting operators must flip per §10? *(inferred — §14 Q12)* | When `false`, the management server's `RootCACustomTrustManager` does **not** require a client certificate from a peer attempting to connect on `:8250` (agent port) or cluster ports. A peer without a cert is allowed in. | | ||
| | `ca.plugin.root.allow.expired.cert` | **`true`** *(documented: `RootCAProvider.java` line 138)* | **maintainer ruling required** *(inferred — §14 Q12)* | When `true`, an expired client cert is accepted during SSL handshake. | | ||
| | `ca.plugin.root.issuer.dn` | `CN=ca.cloudstack.apache.org` *(documented: same file line 128)* | configured at first management-server boot | Subject DN of the auto-generated self-signed Root CA. | | ||
| | `useforwardheader` (`use.forward.header`) | `false` *(inferred — §14 Q17)* | When `true`, the operator must restrict `proxy.forward.list` to the trusted reverse-proxy CIDR | When set, `ApiServlet.getClientAddress` honours `X-Forwarded-For` / configured headers *only* for source IPs in `proxy.forward.list` *(documented: `server/src/main/java/com/cloud/api/ApiServlet.java` lines 700–725)*. | |
There was a problem hiding this comment.
The name of the global setting is proxy.header.verify.
| | `ca.plugin.root.allow.expired.cert` | **`true`** *(documented: `RootCAProvider.java` line 138)* | **maintainer ruling required** *(inferred — §14 Q12)* | When `true`, an expired client cert is accepted during SSL handshake. | | ||
| | `ca.plugin.root.issuer.dn` | `CN=ca.cloudstack.apache.org` *(documented: same file line 128)* | configured at first management-server boot | Subject DN of the auto-generated self-signed Root CA. | | ||
| | `useforwardheader` (`use.forward.header`) | `false` *(inferred — §14 Q17)* | When `true`, the operator must restrict `proxy.forward.list` to the trusted reverse-proxy CIDR | When set, `ApiServlet.getClientAddress` honours `X-Forwarded-For` / configured headers *only* for source IPs in `proxy.forward.list` *(documented: `server/src/main/java/com/cloud/api/ApiServlet.java` lines 700–725)*. | | ||
| | `proxy.forward.list` | unset *(inferred — §14 Q17)* | required when `useforwardheader=true` | CIDR list of trusted reverse proxies. | |
There was a problem hiding this comment.
proxy.header.names is list of names to check for allowed ipaddresses from a proxy set header.
proxy.cidr is a list of cidrs for which "proxy.header.names" are honoured if the "Remote_Addr" is in this list.
| | `proxy.forward.list` | unset *(inferred — §14 Q17)* | required when `useforwardheader=true` | CIDR list of trusted reverse proxies. | | ||
| | `enable.2fa.for.users` / `enable.2fa.for.api` | per-domain toggle *(documented: `plugins/user-two-factor-authenticators/`)* | dev-test default off; production posture depends on PMC ruling *(inferred — §14 Q18)* | When on, users must complete static-pin or TOTP 2FA after login. | | ||
| | `security.encryption.key`, `security.encryption.iv` | auto-generated at first boot *(documented: `framework/security/.../KeysManager.java`)* | trusted secret | Base64-encoded JaSypt master key + IV used to encrypt application-level secrets in the DB. | | ||
| | `auth.password.algorithm` (`hash.user.password`) | bcrypt / pbkdf2 / sha256salted *(documented: `plugins/user-authenticators/{pbkdf2,sha256salted}`)* | **maintainer ruling required**: which is the supported default for new deployments? `md5` and `plain-text` plugins still ship *(documented: `plugins/user-authenticators/{md5,plain-text}`)* — are these legacy-compat-only or in supported production? *(inferred — §14 Q19)* | governs how user passwords are stored | |
There was a problem hiding this comment.
No mention of either auth.password.algorithm or hash.user.password in code.
Order is defined by user.password.encoders.order. Default is PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT. And to exclude user.password.encoders.exclude which has the default value - MD5,LDAP,PLAINTEXT.
| | `post.requests.and.timestamps.enforced` | per `isPostRequestsAndTimestampsEnforced` *(documented: `ApiServer.java` line ~1074)* | bounds `expires` to a maximum future offset | Prevents an attacker who steals a signed URL with a 10-year expiration from using it forever | | ||
| | `integration.api.port` (`:8096`) | typically disabled *(inferred — §14 Q20)* | When non-zero, exposes an *unauthenticated* admin API for integration testing | An open integration port is a complete RBAC bypass on the management server | | ||
| | Hypervisor enablement (which `plugins/hypervisors/*` are installed and configured) | per zone | operator-driven | An unused hypervisor plugin still ships but is not connected to any host | | ||
| | Hostname / SAN of management-server cert (`ca.plugin.root.management.cert.custom.san`) | unset *(inferred — §14 Q15)* | when set, included in the auto-generated cert SAN | governs which hostnames clients can use to reach the management server | |
There was a problem hiding this comment.
global setting is ca.framework.cert.management.custom.san
Summary
This PR adds an initial draft of a project-level security
threat-model document (
draft-THREAT-MODEL.md) so that automatedsecurity scanners running against this repository have a
maintainer-facing reference for which classes of findings are
in-scope vs. out-of-scope for the project.
The document follows the rubric format used by several other ASF
projects piloting improved security-model discoverability for
agentic scanners. Every claim carries a provenance tag:
the project website), cited inline.
knowledge; the PMC has not confirmed.
to this draft. (Zero in this initial draft.)
Draft stats:
§14 is the highest-leverage section: answering each question
either promotes one (inferred) tag to (maintainer) or corrects
the underlying claim.
Why "draft-" prefix?
The file is named
draft-THREAT-MODEL.mdrather thanSECURITY-THREAT-MODEL.mdbecause this is a proposal for thePMC to review — please correct, reject, or discuss as needed.
Once the PMC ratifies (or substantially edits) the content, the
file can be renamed in a follow-up PR and a discoverability
scaffold (
AGENTS.md→SECURITY.md→ the model) added soscanners can mechanically follow the chain.
What this is, and what it is not
This is not a security audit. It is a working triage document
— the reference a triager holds against an inbound report to
decide whether the report is about a CloudStack vulnerability or
about caller misuse / operator misconfiguration / an out-of-scope
concern.
The draft was generated by an automated agentic security scan
being piloted by the ASF Security team; the discoverability work
is independent of any specific scan run.
How to review
replaces the inferred claim with the correct one.
dispositions) — those govern how a vulnerability report would
be triaged.
Reply edits / corrections inline on the PR, or to the original
security@apache.orgthread, whichever fits the PMC's workflow.🤖 Generated with Claude Code