swarm-nats: give the queue a name, a bao-issued leaf, and require TLS
The queue listened in plaintext on 4222, reached by bridge IP or loopback,
and nothing in-tree opened it to another hive. It now has a name, serves a
certificate for that name alone, and refuses clients that do not speak TLS.
- `swarm.nats.domain`, default `nats.<swarm.domain>`, a sibling name like
`swarm.bao.domain`. The queue host answers it via `gateway.localNames`;
every other hive resolves it through the operator's DNS, as for bao.
- `pki/roles/swarm-nats` allows that one name (bare domain, no subdomains,
IPs or localhost, server flag). A `swarm-nats` cert-auth role and policy
may only `update` `pki/issue/swarm-nats`, written by
`swarm-bao-nats-tls-policy`. The login leaf is minted by glue-bao-tls and
paired by glue-nats-bao-identity. `deploy.bao.natsCommonName` is reserved
as a hive name.
- `swarm-bao-nats-tls` issues the leaf into a directory bound read-only into
the container, restarts nats when it rotates, and re-runs daily.
It joins glue-bao-readers-policy-order, so it is ordered after its policy
unit (`after` and `wants`, never `requires`) where the store is on the
same host. The policy unit joins the store's journald list.
- nats gets `tls {}`, with the key via `LoadCredential`, and no
`allow_non_tls`. `validateConfig` is now off in every mode, because the
build-time check loads a leaf that only exists at runtime.
- 4222 is also open on `wg-hive` when the host is on the mesh, never
host-wide.
- `statusPublish.natsUrl`, `queue.agentNatsUrl`, the controller's URL under
`singleHostSwarm`, and the auth responder all dial
`tls://<swarm.nats.domain>:<port>`. swarm-queue-client hands its CA file
to the NATS connection too, so hive-c0re and the controller trust the
leaf's root.
- docs/swarm/README.md: the queue URL and the one DNS record a multi-host
swarm needs.
module-eval-nats-tls pins the role, the policy, the served leaf, the
firewall, the ordering, and a scan of every `*_NATS_URL` and the
responder's URL across the host and its containers.
Closes #4626
This commit is contained in:
parent
244db367c4
commit
1d261b3fed
17 changed files with 748 additions and 108 deletions
|
|
@ -323,7 +323,7 @@ const MAX_RECONNECT_DELAY: std::time::Duration = std::time::Duration::from_mins(
|
|||
/// this process reads no file it was not pointed at.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueueConfig {
|
||||
/// `nats://host:port` for the swarm queue.
|
||||
/// `tls://<name>:<port>` for the swarm queue.
|
||||
pub url: String,
|
||||
/// Authelia's token endpoint, e.g. `https://auth.<swarm>/api/oidc/token`.
|
||||
pub token_endpoint: String,
|
||||
|
|
@ -335,8 +335,8 @@ pub struct QueueConfig {
|
|||
/// read here, and putting it in the environment would publish it to
|
||||
/// anything that can read `/proc/<pid>/environ`.
|
||||
pub client_secret_file: PathBuf,
|
||||
/// Extra trust anchor for the token endpoint, when it is not signed by
|
||||
/// a publicly-trusted CA.
|
||||
/// Extra trust anchor for the token endpoint and the queue itself, when
|
||||
/// they are not signed by a publicly-trusted CA.
|
||||
///
|
||||
/// Optional, and deliberately NOT part of the all-or-none group below: a
|
||||
/// swarm fronted by a public certificate needs no extra anchor, and
|
||||
|
|
@ -673,6 +673,7 @@ pub async fn connect(cfg: QueueConfig) -> Result<async_nats::Client, Error> {
|
|||
// `async-nats` do what it already does well — back off and try again.
|
||||
let http = build_http_client(&cfg)?;
|
||||
let url = cfg.url.clone();
|
||||
let ca_file = cfg.ca_file.clone();
|
||||
|
||||
// Shared across every invocation of the callback below, which is the
|
||||
// whole point: the callback fires per connection ATTEMPT, so without
|
||||
|
|
@ -681,7 +682,7 @@ pub async fn connect(cfg: QueueConfig) -> Result<async_nats::Client, Error> {
|
|||
let cache: std::sync::Arc<tokio::sync::Mutex<Option<CachedToken>>> =
|
||||
std::sync::Arc::new(tokio::sync::Mutex::new(None));
|
||||
|
||||
let client = async_nats::ConnectOptions::with_auth_callback(move |_nonce| {
|
||||
let mut options = async_nats::ConnectOptions::with_auth_callback(move |_nonce| {
|
||||
let http = http.clone();
|
||||
let cfg = cfg.clone();
|
||||
let cache = cache.clone();
|
||||
|
|
@ -745,13 +746,23 @@ pub async fn connect(cfg: QueueConfig) -> Result<async_nats::Client, Error> {
|
|||
// near expiry. (Before the cache, "runs the callback" meant "mints",
|
||||
// and this retry loop was a token-request loop. See
|
||||
// `MAX_RECONNECT_DELAY`.)
|
||||
.retry_on_initial_connect()
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|source| Error::Connect {
|
||||
url: url.clone(),
|
||||
source,
|
||||
})?;
|
||||
.retry_on_initial_connect();
|
||||
|
||||
// The queue's leaf chains to the swarm's own PKI root, which a host
|
||||
// process's platform store does not hold: the same anchor the token
|
||||
// endpoint needs. async-nats then trusts this file INSTEAD of the platform
|
||||
// roots, which is right here, because the queue has no public certificate.
|
||||
if let Some(path) = ca_file {
|
||||
options = options.add_root_certificates(path);
|
||||
}
|
||||
|
||||
let client = options
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|source| Error::Connect {
|
||||
url: url.clone(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue