polyc-controller 2026.9.0

Conversation CRD + kube reconciler for the polychrome control plane.
//! Secret names the platform mints for itself, and the guard that keeps an
//! author-supplied reference off them.
//!
//! A `ToolService` carries `spec.auth.bearerSecretRef` — a Secret name chosen
//! by whoever wrote the resource — and a remote URL, also author-chosen. Both
//! the control plane (when it composes a turn's connectors) and this crate's
//! reconciler (when it health-checks one) resolve that Secret and present its
//! value as an `Authorization` header to that URL. Without a guard, naming
//! [`STATE_ATTESTATION_SECRET`] ships the key the journal signs its roots with
//! to a URL the author controls, and forged attestations defeat verified
//! replay. Naming a `polychrome-wallet-key-…` Secret does the same for the
//! control plane's signing roles.
//!
//! So the names live here, in the crate that also defines
//! [`BearerSecretRef`](crate::BearerSecretRef), and every path that resolves
//! one calls [`check_secret_ref`] first. `polychrome state bootstrap` — the
//! command that writes this material — names the same constants, so the
//! written set and the refused set cannot drift.
//!
//! The guard **refuses**; it never sanitizes. A connector naming reserved
//! material is dropped rather than dialed without auth, because "resolve to
//! empty and dial anyway" is still an author-controlled request carrying
//! whatever the remote infers from an unauthenticated call.

/// Secret holding the State certificate authority's certificate and key. It
/// mints the client and server identities for both planes, so it is the
/// sharpest single name on this list.
///
/// `polychrome state bootstrap` writes it to a namespace of its own, which is
/// not the namespace a resolver reads a `bearerSecretRef` from — so a
/// reference to this name resolves to nothing there anyway. It stays reserved
/// regardless: the name belongs to the platform, and a cluster mid-migration
/// still has the real authority under it.
pub const STATE_MTLS_CA_SECRET: &str = "polychrome-state-mtls-ca";

/// Secret holding State's own server identity and key.
pub const STATE_MTLS_SERVER_SECRET: &str = "polychrome-state-mtls-server";

/// Secret holding the control plane's client identity for the State dial, plus
/// the workload identity State admits it under.
pub const STATE_MTLS_CLIENT_SECRET: &str = "polychrome-state-mtls-client";

/// Secret holding the key the journal signs its roots with.
pub const STATE_ATTESTATION_SECRET: &str = "polychrome-state-attestation";

/// The State plane's four fixed Secret names, in the order
/// `polychrome state bootstrap` writes them.
///
/// The first goes to the authority namespace and the other three to the
/// target namespace. All four are refused here, because the guard reserves
/// names rather than locations.
pub const RESERVED_SECRET_NAMES: [&str; 4] = [
    STATE_MTLS_CA_SECRET,
    STATE_MTLS_SERVER_SECRET,
    STATE_MTLS_CLIENT_SECRET,
    STATE_ATTESTATION_SECRET,
];

/// Prefix of the per-role Secrets holding the control plane's signing keys.
///
/// The full name appends the hex sha256 of the custody reference, which is
/// derivable from public strings — so the whole prefix is reserved, not the
/// individual digests.
pub const CONTROL_KEY_SECRET_PREFIX: &str = "polychrome-wallet-key-";

/// A resolution refused because the referenced name belongs to the platform's
/// own key material rather than to a connector's token.
///
/// The message names the reservation, not the contents: after the authority
/// moved out of the target namespace, `polychrome-state-mtls-ca` is a reserved
/// name there that holds nothing at all, and it is still refused.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error(
    "Secret \"{secret}\" is a name Polychrome reserves for its own key material, so a tool \
     service cannot read it. Point spec.auth.bearerSecretRef.name at a Secret you created for \
     this connector's token."
)]
pub struct ReservedSecret {
    /// The refused Secret name, echoed back so the message names what to change.
    pub secret: String,
}

/// Refuse an author-supplied Secret name that points at platform key material.
///
/// Call this before reading any Secret whose name came out of a custom
/// resource's spec.
///
/// # Errors
///
/// Returns [`ReservedSecret`] when `name` is one of [`RESERVED_SECRET_NAMES`]
/// or starts with [`CONTROL_KEY_SECRET_PREFIX`].
pub fn check_secret_ref(name: &str) -> Result<(), ReservedSecret> {
    if RESERVED_SECRET_NAMES.contains(&name) || name.starts_with(CONTROL_KEY_SECRET_PREFIX) {
        return Err(ReservedSecret {
            secret: name.to_owned(),
        });
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    #![allow(clippy::pedantic, clippy::nursery, missing_docs)]
    use super::*;

    /// Every name `polychrome state bootstrap` mints for the State plane is
    /// refused, and the refusal names the Secret so the author knows what to
    /// change.
    #[test]
    fn each_state_secret_is_refused_by_name() {
        for name in RESERVED_SECRET_NAMES {
            let err = check_secret_ref(name).expect_err("reserved name must be refused");
            assert_eq!(err.secret, name);
            assert!(
                err.to_string().contains(name),
                "the refusal must name the Secret it refused"
            );
        }
    }

    /// The control plane's signing keys live behind a derivable digest, so the
    /// whole prefix is reserved rather than a fixed list of names.
    #[test]
    fn the_control_signing_key_prefix_is_refused() {
        let name = format!("{CONTROL_KEY_SECRET_PREFIX}{}", "ab".repeat(32));
        let err = check_secret_ref(&name).expect_err("a control signing key must be refused");
        assert_eq!(err.secret, name);
    }

    /// An ordinary Secret a connector's author created still resolves — the
    /// guard refuses reserved material, it does not gate connector auth.
    #[test]
    fn an_author_chosen_secret_still_resolves() {
        for name in [
            "acme-mcp-token",
            "polychrome-state-mtls",
            "my-polychrome-state-attestation",
            "polychrome-wallet",
            "",
        ] {
            assert!(
                check_secret_ref(name).is_ok(),
                "{name} is not reserved and must resolve"
            );
        }
    }
}