Skip to content

Add draft project security threat-model document#13293

Open
potiuk wants to merge 2 commits into
apache:mainfrom
potiuk:asf-security/draft-threat-model-2026-05-30
Open

Add draft project security threat-model document#13293
potiuk wants to merge 2 commits into
apache:mainfrom
potiuk:asf-security/draft-threat-model-2026-05-30

Conversation

@potiuk
Copy link
Copy Markdown
Member

@potiuk potiuk commented May 30, 2026

Summary

This PR adds an initial draft of a project-level security
threat-model document (draft-THREAT-MODEL.md) so that automated
security 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:

  • (documented) — paraphrased from public artefacts (this repo or
    the project website), cited inline.
  • (inferred) — synthesised from code structure or domain
    knowledge; the PMC has not confirmed.
  • (maintainer) — confirmed by a CloudStack PMC member in response
    to this draft. (Zero in this initial draft.)

Draft stats:

  • ~88 documented claims
  • ~64 inferred claims (each maps to a §14 question)
  • 38 open questions for maintainers in §14

§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.md rather than
SECURITY-THREAT-MODEL.md because this is a proposal for the
PMC 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.mdSECURITY.md → the model) added so
scanners 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

  1. §14 first. Each answer either confirms one (inferred) tag or
    replaces the inferred claim with the correct one.
  2. After that, please skim §3 (out-of-scope) and §13 (triage
    dispositions) — those govern how a vulnerability report would
    be triaged.

Reply edits / corrections inline on the PR, or to the original
security@apache.org thread, whichever fits the PMC's workflow.

🤖 Generated with Claude Code

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
Copy link
Copy Markdown

codecov Bot commented May 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 18.10%. Comparing base (7308dad) to head (f792725).

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     
Flag Coverage Δ
uitests 3.51% <ø> (ø)
unittests 19.26% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Markdown / typos / table-shape fixes per the CI lint output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yadvr yadvr requested review from DaanHoogland and vishesh92 June 1, 2026 07:16
@yadvr
Copy link
Copy Markdown
Member

yadvr commented Jun 1, 2026

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.

Comment thread draft-THREAT-MODEL.md
Comment on lines +825 to +828
**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)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment thread draft-THREAT-MODEL.md
Comment on lines +830 to +832
**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)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, for as far as they are pointing inwards. they do have for some purposed outside interfaces which are supposed to be safe.

Comment thread draft-THREAT-MODEL.md
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
per-hypervisor-host `cloudstack-agent` (for KVM/Hyper-V/baremetal) or
per-hypervisor-host `cloudstack-agent` (for KVM//baremetal) or

Comment thread draft-THREAT-MODEL.md
Comment on lines +834 to +838
**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)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Comment thread draft-THREAT-MODEL.md
Comment on lines +844 to +847
**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)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have no procedure to implement this yet (dependabot is not producing viable PRs for us) I would prefer we do. Any ideas @vishesh92 ?

Comment thread draft-THREAT-MODEL.md
| 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 |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default value for ca.plugin.root.auth.strictness is true for newer setups but for older setups, it remains false even after upgraded.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so if it is mentioned in the upgrade instructions it is not a concern.

Comment thread draft-THREAT-MODEL.md

**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)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread draft-THREAT-MODEL.md
Comment on lines +849 to +852
**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)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Comment thread draft-THREAT-MODEL.md

| 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. |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default is true. It's false only when upgrading from really old cloudstack versions.

Comment thread draft-THREAT-MODEL.md
Comment on lines +854 to +856
**Q7.** Hypervisor bugs (libvirt / vSphere SDK / XenAPI / Hyper-V API /
KVM/QEMU itself) — out of scope, report upstream (proposed)? *(maps to
§3 item 3)*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, should not lead to any changes but deleting this question, right @potiuk ?

Comment thread draft-THREAT-MODEL.md
| `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 |
Copy link
Copy Markdown
Member

@vishesh92 vishesh92 Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't configurable and I didn't find any references for api.signature.version in the code.

Comment thread draft-THREAT-MODEL.md
| `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 |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global setting is enforce.post.requests.and.timestamps and not post.requests.and.timestamps.enforced

Comment thread draft-THREAT-MODEL.md
| `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)*. |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the global setting is proxy.header.verify.

Comment thread draft-THREAT-MODEL.md
| `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. |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread draft-THREAT-MODEL.md
| `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 |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread draft-THREAT-MODEL.md
| `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 |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global setting is ca.framework.cert.management.custom.san

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants