This is an automated email from the ASF dual-hosted git repository.

HoustonPutman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 74442db  Add a draft security threat model + discoverability files 
(THREAT_MODEL.md, SECURITY.md, AGENTS.md) (#841)
74442db is described below

commit 74442db74face0d8ceab8e82698f89a6a38ff272
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Jul 18 00:17:24 2026 +0200

    Add a draft security threat model + discoverability files (THREAT_MODEL.md, 
SECURITY.md, AGENTS.md) (#841)
    
    * Add a draft security threat model + discoverability files 
(THREAT_MODEL.md, SECURITY.md, AGENTS.md)
    
    Generated-by: Claude Code
    
    * Fold in Houston Putman's review: namespace-scoped install, 
ClusterRole/Role, pass-through k8s options, [email protected]
    
    Addresses the 5 review comments on #841; flips the confirmed claims'
    provenance to *(maintainer)* and credits the 2026-07-15 review.
---
 AGENTS.md       |  32 ++++
 SECURITY.md     |  32 ++++
 THREAT_MODEL.md | 563 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 627 insertions(+)

diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..0ff338c
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,32 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+# Agent Guide for solr-operator
+
+This file is read by automated agents (security scanners, code
+analyzers, AI assistants) operating on this repository.
+
+## Security
+
+Security model: [SECURITY.md](./SECURITY.md)
+
+Agents that scan this repository should consult `SECURITY.md` and the
+threat model it links before reporting issues.
+
+This repo is the Kubernetes operator; its threat model is distinct from the 
Apache Solr search server (cross-referenced within).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..3f3bf13
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,32 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+# Security Policy
+
+## Reporting a Vulnerability
+
+`apache/solr-operator` follows the [Apache Software Foundation security 
process](https://www.apache.org/security/). Please report suspected
+vulnerabilities privately to `[email protected]`; do not open public
+GitHub issues or pull requests for security reports.
+
+## Threat Model
+
+What the project treats as in scope and out of scope, the security
+properties it provides and disclaims, the adversary model, and how
+findings are triaged are documented in [THREAT_MODEL.md](./THREAT_MODEL.md).
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
new file mode 100644
index 0000000..ab87f9d
--- /dev/null
+++ b/THREAT_MODEL.md
@@ -0,0 +1,563 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+# Apache Solr Operator — Threat Model (v0 DRAFT)
+
+## §1 Header
+
+- **Project:** Apache Solr Operator (`apache/solr-operator`) — the official
+  Kubernetes operator for Apache Solr. A kubebuilder/controller-runtime Go
+  controller that watches `solr.apache.org/v1beta1` custom resources
+  (`SolrCloud`, `SolrBackup`, `SolrPrometheusExporter`) and reconciles the
+  Kubernetes objects (StatefulSets, Services, ConfigMaps, Secrets, Ingresses,
+  PodDisruptionBudgets, ZookeeperClusters, PVCs) that make up a running Solr
+  deployment. It is **not** the Solr search server — the search server is
+  modelled separately in `apache/solr`'s `THREAT_MODEL.md`, cross-referenced
+  throughout.
+- **Written against:** `main` @ HEAD (2026-07).
+- **Author:** ASF Security team, via the threat-model-producer rubric
+  (Scovetta rubric), as a starting point for the Solr PMC.
+- **Status:** **v0 DRAFT** — produced by the ASF Security team *for the Solr
+  PMC to review, correct, own, and ratify*. **Reviewed by Houston Putman
+  (Solr PMC) on 2026-07-15; his corrections are folded in.** Not yet
+  ratified/merged. Remaining unconfirmed claims below are *(inferred)* and
+  routed to a §14 open question; do not treat those as a maintainer position
+  until the PMC has confirmed them.
+- **Version binding:** once ratified, versioned alongside the operator. A
+  report against operator version *N* is triaged against the model as it stood
+  at *N*, not at HEAD.
+- **Canonical role (proposed):** intended to be the operator's
+  **scanner-facing** security model — the authoritative basis for triaging an
+  automated finding as in- or out-of-model. It is deliberately *not* an
+  operator handbook; the operator's user guide (docs/) and Solr's own
+  "Securing Solr" guidance remain the human source of truth for deployment.
+  This role split is a §14 question for the PMC to confirm.
+- **Reporting cross-reference:** §8-violating findings via the ASF security
+  process (report to `[email protected]`, see
+  <https://solr.apache.org/security.html>); §3/§9 findings closed citing this
+  document. The Solr Operator has no separate disclosure channel from Solr —
+  §14 confirms.
+- **Provenance legend:** *(documented)* = stated in the repo's code, CRD field
+  docs, or the operator user guide (cited); *(maintainer)* = confirmed by a
+  Solr PMC member in response to this draft (**Houston Putman, 2026-07-15**);
+  *(inferred)* =
+  reasoned from code/CRD/RBAC structure, not yet confirmed — each has a
+  matching §14 open question.
+- **Draft confidence:** ~14 documented / 0 maintainer / 27 inferred. This is a
+  v0: the *(documented)* claims are lifted from CRD field comments, the RBAC
+  manifests, and the user guide; everything about *intent, contract, and
+  scope* is still hypothesis awaiting the PMC.
+
+**What it is.** The Solr Operator is a **cluster-scoped Kubernetes
+controller**. An operator/admin installs it once (typically Helm), granting it
+a `ClusterRole`; from then on any user who can create a `SolrCloud` (or
+`SolrBackup` / `SolrPrometheusExporter`) custom resource in a watched
+namespace causes the operator — acting with its own cluster-wide privileges —
+to create and manage the backing Kubernetes objects and to make authenticated
+HTTP calls into the Solr pods it manages. The defining security fact is that
+the operator is a **privileged intermediary**: it turns a namespaced,
+relatively low-privilege action (creating a CR) into high-privilege cluster
+mutations (creating Secrets, exec-ing into pods, managing StatefulSets),
+across the namespaces it watches (all namespaces by default, though it can
+equally be installed namespace-scoped, watching only specific namespaces).
+
+## §2 Scope and intended use
+
+The Solr Operator is a **long-running in-cluster controller**, not a library
+and not a user-facing service. Roles:
+
+- **CRD author / namespace tenant** — a user (human or CI identity) with RBAC
+  permission to create/update `SolrCloud`, `SolrBackup`,
+  `SolrPrometheusExporter` objects in a watched namespace. **Semi-trusted at
+  most**: they author the *desired state* but do not themselves hold the
+  operator's cluster privileges. This is the primary adversary of interest
+  (§7). *(inferred — Q-tenant.)*
+- **Cluster operator / admin** — installs the operator, grants its RBAC
+  (a cluster-wide `ClusterRole`, or namespace-scoped `Role`s when the operator
+  watches specific namespaces), chooses watched namespaces, controls the Helm
+  values. **Fully trusted** — owns the operator's blast radius.
+  *(maintainer — ClusterRole/Role distinction confirmed by HoustonPutman
+  2026-07-15.)*
+- **The managed Solr (search server) and its clients** — out of scope here;
+  modelled by `apache/solr`'s `THREAT_MODEL.md`. The operator is a *client* of
+  Solr (it makes authenticated admin API calls), not the thing serving
+  queries.
+
+**Component families.**
+
+| Family | Entry point | Privilege / exposure | In model? |
+| --- | --- | --- | --- |
+| **CRD field handling** (`api/v1beta1`) | 
`SolrCloud`/`SolrBackup`/`SolrPrometheusExporter` spec | tenant-authored 
desired state consumed by a privileged controller | **Yes (primary)** |
+| **Reconciliation controllers** (`controllers/`) | watch → reconcile loop | 
acts with the operator ServiceAccount's cluster RBAC | **Yes (primary)** |
+| **Operator RBAC** (`config/rbac/role.yaml`) | the `ClusterRole` (or 
namespace-scoped `Role`s) bound to the operator SA | 
secrets/pods-exec/statefulsets across watched namespaces | **Yes (blast 
radius)** |
+| **Secret provisioning** (`controllers/util/solr_security_util.go`) | 
bootstrap basic-auth + `security.json` secrets | generates & stores Solr admin 
credentials | **Yes** |
+| **TLS handling** (`solr_tls_util.go`, `main.go` client cert) | 
mounted/provisioned certs; operator→Solr mTLS client | trust of Solr server 
certs | **Yes** |
+| **Backup subsystem** (`SolrBackup`, `solr_backup_repo_util.go`) | repo 
config + cloud credentials (S3/GCS/volume) | reads backup-repo credential 
secrets | **Yes** |
+| **ZooKeeper coupling** | provisions `ZookeeperCluster` (pravega), ZK ACL 
secrets | cluster state/config store for SolrCloud | **Yes (trust ZK 
provider)** |
+| Admission / validation webhooks | — | — | **No — none shipped** (§3) |
+| **The managed Solr search server** | Solr HTTP API | query/index/admin | 
**No — `apache/solr` model** (§3) |
+| Kubernetes control plane (API server, kubelet, etcd) | — | the platform | 
**No** (§3) |
+
+## §3 Out of scope (explicit non-goals)
+
+- **Apache Solr itself (the search server).** Everything about the Solr HTTP
+  API — query/update/admin surface, SSRF via `shards`/streaming, Solr auth/
+  authz plugin correctness, RCE via risky Solr features — is modelled by
+  `apache/solr`'s `THREAT_MODEL.md`. This model covers only how the *operator*
+  configures, credentials, and talks to Solr. *(documented — separate repo;
+  Q-scope.)*
+- **The Kubernetes control plane and platform.** The API server, admission
+  control, RBAC enforcement engine, kubelet, etcd, the CNI, and the node OS
+  are the platform's trust base. The operator assumes k8s enforces the RBAC it
+  is granted. A finding whose precondition is "the k8s API server mis-enforces
+  RBAC" or "etcd is readable" is out of model. *(inferred — Q-platform.)*
+- **Cluster-admin as adversary.** Anyone who can edit the operator's
+  `ClusterRole`, read arbitrary Secrets cluster-wide, or `exec` into the
+  operator pod has already won; they are not a meaningful adversary to model
+  at this layer. *(inferred — Q-admin.)*
+- **The ZooKeeper implementation.** The operator provisions a
+  `ZookeeperCluster` via the external pravega zookeeper-operator (a separate
+  project) and trusts it. Securing/patching ZooKeeper and that operator is out
+  of model. *(inferred — Q-zk.)*
+- **Third-party dependency CVEs** (controller-runtime, client-go, the
+  zookeeper-operator, container base images) — case-by-case: in model only
+  when there is a reasonable operator-side mitigation; otherwise routed to the
+  dependency's own project.
+- **Code shipped for development/testing** — `tests/`, `hack/`, `example/`,
+  `dev-docs/`, and `*_test.go`. Unsupported for production; threat-model
+  separately if ever promoted. *(inferred — Q-scope.)*
+- **Pass-through Kubernetes options on dependent resources.** Custom
+  Kubernetes options a tenant sets that the operator copies verbatim onto the
+  dependent objects it creates (StatefulSet, Service, Pod, etc.) are native
+  Kubernetes fields passed straight through, so their behaviour and security
+  semantics are Kubernetes's, not the operator's. *(maintainer — confirmed by
+  HoustonPutman 2026-07-15.)*
+
+## §4 Trust boundaries and data flow
+
+There are **two** trust boundaries, and conflating them is the most common
+triage error for an operator:
+
+1. **Tenant → operator (the CR boundary).** A namespace tenant writes a
+   `SolrCloud`/`SolrBackup`/`SolrPrometheusExporter` spec. The operator reads
+   that spec and acts on it *with the operator ServiceAccount's cluster
+   privileges*, not the tenant's. This is the boundary this model cares about
+   most: fields in the CR are **attacker-controllable input to a privileged
+   process** (§6).
+2. **Operator → Solr (the HTTP boundary).** The operator is an authenticated
+   *client* of the Solr pods it manages (basic-auth as `k8s-oper`, optionally
+   over mTLS). Trust flows the other way here: the operator trusts the Solr
+   server it dials (see the `tls-skip-verify-server` default, §5a).
+
+```
+tenant ─(create/patch CR)─► [k8s API + RBAC] ─► operator reconcile loop
+                                                    │ acts as operator SA 
(ClusterRole)
+                                                    ├─► create/patch Secrets, 
ConfigMaps, Services
+                                                    ├─► create/patch 
StatefulSets, Deployments, PDBs, Ingress, PVCs
+                                                    ├─► pods/exec (create) 
into managed pods
+                                                    └─► provision 
ZookeeperCluster (pravega CRD)
+operator ─(HTTPS admin API, basic-auth k8s-oper, optional mTLS)─► managed Solr 
pods
+   (TLS server-cert verification OFF by default: tls-skip-verify-server=true)
+```
+
+**Reachability precondition (triager's test).** A finding is in-model only if
+it is reachable by a **CRD author who is not a cluster-admin**, and the effect
+crosses from the tenant's authority into the operator's cluster authority in a
+way the tenant should not be able to reach — e.g. a CR field that makes the
+operator read a Secret the tenant could not read directly, mount an arbitrary
+volume, or exec somewhere it should not. A finding that merely requires
+cluster-admin, or that lands in the Solr search server, is out of model
+(§3). *(inferred — Q-tenant/Q-reach.)*
+
+## §5 Assumptions about the environment
+
+- **Kubernetes enforces RBAC and namespace isolation.** The operator assumes
+  the API server correctly gates who may create CRs and correctly scopes the
+  operator's own `ClusterRole`. *(inferred — Q-platform.)*
+- **The operator runs with a cluster-scoped ServiceAccount** granting the
+  `config/rbac/role.yaml` `ClusterRole`; by default it **watches all
+  namespaces** (`watchNamespaces: ""` in Helm values → whole-cluster cache and
+  reconcile). *(documented — `helm/solr-operator/values.yaml`, `main.go`
+  `watch-namespaces` flag; Q-scope-default.)*
+- **Only one active controller** via leader election
+  (`leader-elect` defaults true). *(documented — `main.go`.)*
+- **The pravega ZooKeeper operator** is present when SolrCloud provisions its
+  own ZK. *(inferred — Q-zk.)*
+- **Secrets live in the same namespace** as the SolrCloud/exporter for
+  credentials the operator reads (e.g. ZK ACL secret refs are documented as
+  same-namespace). *(documented — `common_types.go` ZookeeperACL comment.)*
+- The operator opens outbound HTTPS to managed Solr pods and to the k8s API;
+  it does not expose an inbound service of its own beyond metrics/health.
+  *(inferred — Q-listeners.)*
+
+## §5a Configuration variants — the knobs that move the model
+
+1. **`watchNamespaces` (Helm `watchNamespaces`, `--watch-namespaces`)** —
+   **default empty = watch and reconcile every namespace** with a single
+   cluster-scoped `ClusterRole`. This maximises blast radius: a CR author in
+   *any* namespace can drive the privileged operator. Restricting to an
+   explicit namespace list (and narrowing the RBAC to `Role`/`RoleBinding`) is
+   the hardened posture the code comments themselves recommend. *(documented —
+   `main.go` comments explicitly suggest per-namespace operators + narrowed
+   RBAC; Q-scope-default.)*
+2. **`tls-skip-verify-server` (`main.go`, default `true`)** — the operator's
+   HTTP client that calls Solr **does not verify the Solr server's
+   certificate chain or hostname by default** ("insecure … accepts any
+   certificate"). Operator→Solr traffic is therefore not authenticated
+   server-side unless the admin sets this false and supplies a CA. *(documented
+   — `main.go` flag help text; Q-tls-default.)*
+3. **`SolrCloud.spec.solrSecurity` absent (default)** — if the CR author does
+   not set `solrSecurity`, the operator provisions Solr **with no
+   authentication** (Solr's own insecure-default posture). Auth is opt-in per
+   SolrCloud. *(documented — `authentication-and-authorization.adoc`; the
+   managed-Solr consequence is `apache/solr`'s model; Q-auth-default.)*
+4. **`solrSecurity.probesRequireAuth` (default `false`)** — probe endpoints
+   are reachable unauthenticated by default; the bootstrapped `security.json`
+   deliberately allows unauthenticated access to probe paths. *(documented —
+   CRD comment + auth doc; Q-probes.)*
+5. **Bootstrap `security.json` vs user-provided** — by default the operator
+   generates a `security.json` and random passwords for `admin`, `solr`,
+   `k8s-oper`; alternatively the tenant supplies `basicAuthSecret` +
+   `bootstrapSecurityJson`. The generated path uses non-cryptographic
+   randomness (§8/§9). *(documented — `solr_security_util.go`; Q-rng.)*
+6. **RBAC creation (`rbac.create`, `serviceAccount.create` Helm, default
+   true)** — the chart creates the cluster `ClusterRole`/binding by default.
+   *(documented — `values.yaml`.)*
+
+**Wave-1 ruling needed (Q-scope-default / Q-tls-default / Q-auth-default):**
+for each insecure default above, confirm whether it is the *supported
+production posture* (so a report against it is `VALID`) or a
+dev-convenience the operator is documented to change (so it is
+`OUT-OF-MODEL: non-default-config`).
+
+## §6 Assumptions about inputs
+
+The operator's primary untrusted input is **the CR spec**, authored by a
+namespace tenant and consumed by a privileged controller. There are **no
+admission/validation webhooks** (§3), so field validation is limited to
+kubebuilder OpenAPI schema constraints (enums, minimums) — anything not
+schema-constrained is accepted as authored. *(inferred — Q-validation.)*
+
+| Boundary / source | Input | Attacker (tenant) controllable? | Enforced by / 
who owns |
+| --- | --- | --- | --- |
+| `SolrCloud` spec | pod/container overrides, `customSolrKubeOptions`, 
volumes, env, image | **yes** | only OpenAPI schema; operator applies as-is to 
a privileged StatefulSet |
+| `SolrCloud.spec.solrSecurity.basicAuthSecret` / `bootstrapSecurityJson` | 
secret name refs | **yes** | operator reads named Secret in the SolrCloud 
namespace |
+| `SolrCloud.spec.zookeeperRef...acl.secret` | ZK ACL secret ref | **yes** | 
must be same-namespace (documented) |
+| `SolrCloud` TLS options | keystore/truststore secret refs, mounted cert 
paths | **yes** | operator mounts/reads referenced secrets |
+| `SolrBackup` spec | `repositoryName`, `location`, repo credentials | **yes** 
| operator reads backup-repo credential secrets (S3/GCS/volume) |
+| `SolrPrometheusExporter` spec | exporter config, image, scrape target | 
**yes** | applied to a Deployment |
+| operator → Solr | Solr server TLS certificate | Solr side | **not verified 
by default** (`tls-skip-verify-server`) |
+| operator → k8s API | watch/list responses (CRs, Secrets) | k8s-mediated | 
trusted (RBAC-scoped) |
+
+The central §14 question is which of these tenant-controllable fields can be
+used to make the operator's cluster privileges do something the tenant could
+not do directly — e.g. does the operator constrain which Secrets / volumes /
+hostPaths a CR may reference to the CR's own namespace, or can a CR reference
+resources elsewhere? *(inferred — Q-crossns / Q-podspec.)*
+
+## §7 Adversary model
+
+- **Namespace tenant / CRD author (primary).** Holds RBAC to create/patch
+  `SolrCloud`/`SolrBackup`/`SolrPrometheusExporter` in a watched namespace,
+  but does **not** hold the operator's cluster privileges. In scope: they try
+  to craft a CR that makes the privileged operator read a Secret they could
+  not read, mount a volume/hostPath they should not, exec into or co-opt a pod
+  they do not own, or otherwise escalate out of their namespace. *(inferred —
+  Q-tenant.)*
+- **Compromised managed Solr pod (secondary).** A Solr pod the operator dials.
+  In scope to the extent that, because `tls-skip-verify-server` defaults on,
+  an on-path actor or a rogue endpoint could impersonate Solr to the operator
+  and harvest the `k8s-oper` credentials the operator presents. *(inferred —
+  Q-tls-default.)*
+- **Out of scope:** cluster-admin (can edit the operator's RBAC — already
+  won); the k8s control plane; the pravega ZK operator; the Solr search
+  server's own client-facing adversaries (that is `apache/solr`'s model); an
+  attacker who already holds the operator ServiceAccount token.
+
+## §8 Security properties the project provides
+
+Each property: statement · violation symptom · severity · provenance.
+
+1. **Privilege intermediation is mediated only by k8s RBAC.** The operator
+   acts on a CR strictly within the resource kinds/verbs its `ClusterRole`
+   grants; it does not escalate beyond them. *Violation:* the operator
+   performs a cluster action outside its granted RBAC, or a CR field causes an
+   action the operator's own model did not intend (a *confused-deputy* across
+   namespaces). *Severity:* critical. *(inferred — Q-tenant/Q-crossns.)*
+2. **Per-SolrCloud credential isolation.** Bootstrap basic-auth /
+   `security.json` secrets are generated per SolrCloud, owned via
+   `OwnerReference`, and stored in the SolrCloud's namespace; passwords are
+   randomized per SolrCloud. *Violation:* one tenant's SolrCloud credentials
+   are readable by, or reused across, another tenant. *Severity:* high.
+   *(documented — `solr_security_util.go`, auth doc; Q-secret-scope.)*
+3. **The operator does not persist Solr admin credentials beyond the
+   bootstrap secret.** Once `security.json` is bootstrapped the operator does
+   not update it and only holds the `k8s-oper` read-only credential for its own
+   calls. *Violation:* operator logs or status leak admin/solr passwords.
+   *Severity:* high. *(documented — auth doc "operator will not update it",
+   `k8s-oper` read-only; Q-credleak.)*
+4. **TLS to Solr is supported for client and inter-node traffic.** The
+   operator can provision/consume keystores and present a client cert (mTLS)
+   to Solr. *Violation:* the operator cannot be configured to use TLS where
+   the CR requests it, or downgrades silently. *Severity:* medium (the
+   *default* is the weaker posture — see §9). *(documented — 
`solr_tls_util.go`,
+   `tls.adoc`; Q-tls-default.)*
+5. **Leader election ensures a single active reconciler.** *Violation:* two
+   controllers fight over the same resources, producing divergent state.
+   *Severity:* correctness (availability). *(documented — `main.go`.)*
+6. **Reconcile convergence / desired-state correctness.** The managed objects
+   converge to the CR spec. *Violation:* drift or wrong object generated.
+   *Severity:* correctness, unless it crosses a security boundary (then see
+   property 1). *(inferred — Q-correctness.)*
+
+## §9 Security properties the project does *not* provide
+
+- **No admission/validation webhook.** The operator ships **no** validating or
+  mutating admission webhook (no `config/webhook`, no webhook entries in
+  `PROJECT`). Cross-field or policy validation of a CR that OpenAPI schema
+  cannot express is **not** enforced; malformed-but-schema-valid specs are
+  accepted. If you need to constrain what tenants may put in a `SolrCloud`
+  (images, volumes, hostPaths, security context), that is the cluster's
+  admission-policy responsibility (Gatekeeper/Kyverno/PSA), not the operator's.
+  *(documented — absence in repo; Q-validation.)*
+  - *False friend:* the CRD's OpenAPI schema (enums like
+    `authenticationType: Basic`, `+kubebuilder:validation:Minimum`) looks like
+    input validation but only enforces shape, not policy or trust.
+- **The operator does not verify Solr's TLS server certificate by default.**
+  `tls-skip-verify-server` defaults to `true` — operator→Solr HTTPS accepts
+  any certificate. This is a confidentiality/authentication gap for the
+  operator's own admin-API calls (it presents `k8s-oper` credentials to
+  whatever answers). *(documented — `main.go`; Q-tls-default.)*
+- **Bootstrap passwords are not generated with a cryptographic RNG.** The
+  bootstrap `admin`/`solr`/`k8s-oper` passwords are produced with `math/rand`
+  seeded from `time.Now().UnixNano()` (and the salt via `math/rand`'s
+  `rand.Read`), not `crypto/rand`. Treat the generated passwords as
+  convenience bootstrap credentials, not high-entropy secrets; rotate via the
+  Solr Security API. *(inferred — `solr_security_util.go`; Q-rng.)*
+  - *False friend:* "the operator generates a random password" reads as
+    cryptographically strong; it is not, by construction.
+- **The operator does not author your Kubernetes authorization.** Who may
+  create a `SolrCloud`, and how broad the operator's `ClusterRole` is, are the
+  cluster-admin's decisions. An over-broad grant (operator watching all
+  namespaces with cluster secrets access) is an install-time choice (§5a/§10).
+- **No tenant isolation beyond what k8s gives you.** The operator does not
+  add a second authorization layer over the CR API; if two teams can create
+  CRs in the same watched scope, the operator does not partition them.
+  *(inferred — Q-tenant.)*
+- **No protection of ZooKeeper, the container images, or the node.** (§3.)
+- **Well-known attack classes the operator itself cannot fully close and
+  leaves to the cluster:** confused-deputy / cross-namespace secret or volume
+  reference via CR fields; privileged-pod or hostPath escape via unconstrained
+  pod-spec overrides; credential harvest via unverified TLS to a spoofed Solr;
+  supply-chain of the managed container images. One sentence each — the point
+  is to put the integrator on notice.
+
+## §10 Downstream responsibilities (cluster operator / admin)
+
+- **Scope the operator down.** Prefer per-namespace operators with a narrowed
+  `Role`/`RoleBinding` over the default all-namespaces `ClusterRole`; the
+  code comments themselves recommend this. Restrict who may create Solr CRs
+  via RBAC. *(documented — `main.go`; Q-scope-default.)*
+- **Constrain CR content with cluster admission policy.** Because there is no
+  operator webhook (§9), use Pod Security Admission / Gatekeeper / Kyverno to
+  bound images, volumes, hostPaths, and security contexts that tenants can
+  request through a `SolrCloud`.
+- **Set `tls-skip-verify-server=false` and supply a CA** if operator→Solr
+  traffic must be authenticated/confidential; enable TLS for client and
+  inter-node Solr traffic. *(documented — `main.go`, `tls.adoc`.)*
+- **Enable Solr auth per SolrCloud** (`solrSecurity`) rather than relying on
+  the unauthenticated default; rotate the bootstrap `admin`/`solr` passwords
+  via the Solr Security API and treat the generated ones as low-entropy
+  bootstrap values. Delete the bootstrap secret after capturing the admin
+  password. *(documented — auth doc.)*
+- **Secure ZooKeeper and its ACLs** for SolrCloud; provide ZK ACL secrets in
+  the correct namespace.
+- **Protect backup-repository credentials** (S3/GCS/volume secrets) and the
+  buckets/paths they grant.
+- Keep the operator, controller-runtime, and the managed container images
+  patched.
+
+## §11 Known misuse patterns
+
+- **Running the operator cluster-wide with broad RBAC in a multi-tenant
+  cluster** and letting many teams create Solr CRs, so the operator becomes a
+  shared confused-deputy across namespace boundaries.
+- **Leaving `tls-skip-verify-server` at its insecure default** on a network
+  where the operator→Solr path is not otherwise protected, exposing the
+  `k8s-oper` credentials to a spoofed endpoint.
+- **Relying on the bootstrap-generated passwords long-term** instead of
+  rotating them, given their non-cryptographic origin.
+- **Deploying SolrCloud with no `solrSecurity`** and exposing the resulting
+  unauthenticated Solr (this then becomes the `apache/solr` model's #1 misuse).
+- **Expecting the operator to validate/PSP-gate tenant pod specs** — it does
+  not (no webhook); unconstrained overrides can request privileged/hostPath
+  pods.
+
+## §11a Known non-findings (recurring false positives)
+
+- **"The operator has broad cluster RBAC (secrets, pods/exec, statefulsets
+  cluster-wide)"** — that is the operator's designed authority, granted at
+  install by a cluster-admin. `BY-DESIGN` unless a *tenant* (non-admin) can
+  make the operator use it outside the tenant's own namespace/authority
+  (then §8 property 1, possibly `VALID`). (§8/§9.)
+- **"`pods/exec` create permission"** — used for legitimate cluster-operation
+  commands against managed Solr pods; a designed capability, not an escalation
+  on its own. `BY-DESIGN`. (§8.)
+- **"No admission webhook / weak CR validation"** — accurate but by design;
+  policy validation is the cluster's job (§9/§10). `BY-DESIGN:
+  property-disclaimed`, or `VALID-HARDENING` if a specific missing check makes
+  a §11 misuse trivially easy.
+- **"Solr is unauthenticated by default" / "SSRF/RCE in Solr"** — that is the
+  managed search server, `OUT-OF-MODEL: separate-component`; route to
+  `apache/solr`'s model. (§3.)
+- **"Uses `math/rand` for passwords"** — a real hardening item (tracked in §9);
+  triage as `VALID-HARDENING` unless the PMC rules the bootstrap credentials
+  are not intended to be cryptographic secrets, in which case
+  `BY-DESIGN`. This is a §14 question, not a settled non-finding.
+- **Findings in `tests/`, `hack/`, `example/`** — `OUT-OF-MODEL:
+  unsupported-component` (§3).
+- **Third-party operator/dependency issues (pravega ZK operator,
+  controller-runtime)** — route to the dependency's project unless there is an
+  operator-side mitigation.
+
+## §12 Conditions that would change this model
+
+- Adding an admission/validation webhook (changes §6/§9 materially).
+- Changing an insecure default: `watchNamespaces`, `tls-skip-verify-server`,
+  the auth/probe defaults, or the bootstrap RNG.
+- A new CRD or a new tenant-controllable field that reaches a privileged
+  action (new confused-deputy surface).
+- Supporting new backup repositories or credential sources.
+- The operator taking on any inbound request surface of its own beyond
+  metrics/health.
+- A report that cannot be routed to a §13 disposition → revise §8/§9.
+
+## §13 Triage dispositions
+
+| Disposition | Meaning | Licensed by |
+| --- | --- | --- |
+| `VALID` | A §8 property breaks for an in-scope actor — chiefly a non-admin 
tenant escalating out of their namespace via a CR field. | §8, §6, §7 |
+| `VALID-HARDENING` | No §8 break, but a §11 misuse (e.g. missing CR-field 
constraint, weak-RNG passwords) is easy enough to warrant hardening. | §11 |
+| `OUT-OF-MODEL: trusted-input` | Requires control of install-time config the 
model marks trusted (Helm values, the `ClusterRole`). | §6/§10 |
+| `OUT-OF-MODEL: adversary-not-in-scope` | Requires cluster-admin, 
control-plane, or ZK-operator compromise. | §7 |
+| `OUT-OF-MODEL: non-default-config` | Only manifests under a 
discouraged/opted-in setting, once the PMC confirms which defaults are 
supported vs dev-only. | §5a |
+| `OUT-OF-MODEL: separate-component` | Lands in the Solr search server, the 
pravega ZK operator, or another repo's model. | §3 |
+| `OUT-OF-MODEL: unsupported-component` | `tests/`, `hack/`, `example/`, 
`*_test.go`. | §3 |
+| `BY-DESIGN: property-disclaimed` | Concerns a property §9 disclaims (broad 
RBAC by design, no webhook, unverified TLS default before ruling). | §9 |
+| `KNOWN-NON-FINDING` | Matches §11a. | §11a |
+| `MODEL-GAP` | Cannot be routed → revise the model. | triggers §12 |
+
+## §14 Open questions for the maintainers
+
+Every *(inferred)* tag above routes to one of these. Proposed answers are
+stated for the PMC to confirm, correct, or strike. This is a v0, so the list
+is long by design.
+
+**Wave 1 — scope, roles, and the load-bearing defaults.**
+
+- **Q-scope.** Confirm the scope split: this model covers only the operator;
+  the Solr search server, the pravega ZK operator, and `tests/`/`hack/`/
+  `example/` are out (routed to their own models / unsupported). (§2/§3.)
+- **Q-tenant / Q-admin.** Confirm the two-actor model: a **namespace tenant /
+  CR author** is the primary (semi-trusted) adversary and a **cluster-admin**
+  is fully trusted and out of scope. Is a CR author expected to be trusted or
+  untrusted in the deployments you design for? (§2/§7.)
+- **Q-scope-default.** Is the default **all-namespaces** watch with a
+  cluster-wide `ClusterRole` the *supported production posture* (so
+  cross-namespace confused-deputy findings are `VALID`), or is per-namespace
+  scoping the expected production install (making all-namespaces
+  `OUT-OF-MODEL: non-default-config`)? (§5a/§8.)
+- **Q-tls-default.** Is `tls-skip-verify-server=true` (no Solr server-cert
+  verification) the supported default, or a dev-convenience operators must
+  flip? A report of operator-credential exposure to a spoofed Solr — `VALID`
+  or `OUT-OF-MODEL: non-default-config`? (§5a/§8/§9.)
+- **Q-auth-default.** Is provisioning Solr with **no** `solrSecurity`
+  (unauthenticated) a supported operator posture, or is it expected that
+  production SolrClouds always set `solrSecurity`? (§5a.)
+
+**Wave 2 — the confused-deputy surface (the crux).**
+
+- **Q-crossns / Q-podspec.** For each tenant-controllable CR field that names
+  a Kubernetes object (secret refs, volume sources, ZK ACL secret, TLS
+  keystore secrets, backup-repo credential secrets) or overrides the pod spec
+  (`customSolrKubeOptions`, volumes, security context, image): does the
+  operator constrain these to the CR's own namespace and to non-privileged
+  shapes, or can a CR author make the operator read/mount something they could
+  not access directly? This determines most `VALID` vs `BY-DESIGN` calls.
+  (§6/§8.)
+- **Q-validation.** Confirm there is no admission/validation webhook and that
+  CR validation is intentionally left to OpenAPI schema + cluster admission
+  policy (PSA/Gatekeeper/Kyverno). (§6/§9.)
+- **Q-secret-scope / Q-credleak.** Confirm per-SolrCloud credential isolation
+  and that admin/solr passwords are never written to logs/status. (§8.)
+
+**Wave 3 — credentials, TLS, and platform.**
+
+- **Q-rng.** The bootstrap `admin`/`solr`/`k8s-oper` passwords and salt use
+  `math/rand` seeded by time, not `crypto/rand`. Is this a hardening item to
+  fix (`VALID-HARDENING`), or are these deemed low-value bootstrap credentials
+  expected to be rotated, making it `BY-DESIGN`? (§8/§9/§11a.)
+- **Q-zk.** Confirm ZooKeeper (and the pravega operator) is a trusted,
+  out-of-model dependency, and that ZK ACL security is the operator's
+  responsibility. (§3/§5.)
+- **Q-platform / Q-listeners / Q-correctness.** Confirm the k8s control plane
+  is trusted; confirm the operator exposes no inbound surface beyond
+  metrics/health; confirm reconcile correctness is a correctness (not
+  security) property unless it crosses a boundary. (§5/§8.)
+
+**Wave 4 — meta / ownership.**
+
+- **Q-doc.** Confirm the disclosure channel (`[email protected]`,
+  shared with Solr) and the document's role: scanner-facing canonical model,
+  complementary to the operator user guide. Should this `THREAT_MODEL.md` live
+  in `apache/solr-operator` and be linked from an `AGENTS.md`/`SECURITY.md`, as
+  the Solr search-server model is? (§1/§15.)
+
+## §15 Appendix — document roles and source map
+
+**Document roles (proposed; awaiting PMC).** This `THREAT_MODEL.md` is intended
+as the operator's **scanner-facing** security model — the authoritative basis
+for triaging automated findings against the operator (in/out-of-scope, the
+§8/§9 property lists, §11a non-findings, §13 dispositions). It is deliberately
+*not* an operator handbook. The human sources of truth remain:
+
+- **Solr Operator user guide** (authentication/authorization, TLS,
+  ZooKeeper, backup) — the `docs/` Antora site, e.g.
+  `docs/modules/solr-cloud/pages/authentication-and-authorization.adoc` and
+  `.../tls.adoc`.
+- **Apache Solr search-server threat model** — `apache/solr`'s
+  `THREAT_MODEL.md` (the managed-Solr surface: query/update/admin APIs, SSRF,
+  Solr auth/authz, risky features). This operator model cross-references it for
+  everything about the search server itself.
+- **Securing Solr** (Solr Reference Guide) —
+  
<https://solr.apache.org/guide/solr/latest/deployment-guide/securing-solr.html>
+- **Security advisories + reporting** — 
<https://solr.apache.org/security.html>.
+
+**Source map (what grounded this v0).** The *(documented)* claims are lifted
+from: `config/rbac/role.yaml` (the operator `ClusterRole`); `main.go`
+(`watch-namespaces`, `leader-elect`, `tls-skip-verify-server` defaults, the
+mTLS client cert plumbing); `helm/solr-operator/values.yaml` 
(`watchNamespaces`,
+`rbac.create`, `serviceAccount.create`); `api/v1beta1/solrcloud_types.go`
+(`SolrSecurityOptions`: `authenticationType`, `basicAuthSecret`,
+`probesRequireAuth`, `bootstrapSecurityJson`); `api/v1beta1/common_types.go`
+(`ZookeeperACL` same-namespace secret refs); 
`controllers/util/solr_security_util.go`
+(bootstrap secret generation, `math/rand` passwords/salt, per-SolrCloud
+randomization, "operator will not update security.json"); and
+`docs/modules/solr-cloud/pages/authentication-and-authorization.adoc`
+(k8s-oper read-only, random admin/solr passwords, probe-auth default). The
+absence of `config/webhook` and of webhook entries in `PROJECT` grounds the
+"no admission webhook" claim. Everything about *intent and contract* is
+*(inferred)* and routed to §14 for the PMC.


Reply via email to