Skip to main content

dynamic_config_server/config/
refusal.rs

1//! Why a configuration will not start a server.
2//!
3//! One enum and its rendering. Every variant names the key that fixes it,
4//! and none of them carries a token: a refusal is printed to a terminal and
5//! scraped into a log, which is the last place a credential should turn up.
6//! That property is tested in `tests/security.rs`, not asserted here.
7
8use std::fmt;
9
10use crate::auth::MIN_TOKEN_LEN;
11
12/// Why a configuration will not start a server.
13///
14/// Every variant's `Display` names the key that fixes it, and none of them
15/// carries a token: a refusal is printed to a terminal and scraped into a
16/// log, which is the last place a credential should turn up.
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum Refusal {
20    /// No `sections` — the server would serve nothing.
21    NoSections,
22    /// No `clients` — nobody could ever read anything.
23    NoClients,
24    /// Two sections claim the same application and profile.
25    DuplicateSection {
26        /// The application both claim.
27        application: String,
28        /// The profile both claim.
29        profile: String,
30    },
31    /// A section names an application or profile no route can carry, so
32    /// nothing could ever reach it.
33    UnroutableSection {
34        /// The application, as configured.
35        application: String,
36        /// The profile, as configured.
37        profile: String,
38        /// Which of the two was refused: `application` or `profile`.
39        part: &'static str,
40    },
41    /// Two clients share a name.
42    DuplicateClient {
43        /// The name.
44        name: String,
45    },
46    /// Two clients share a token.
47    DuplicateToken,
48    /// A configured token is shorter than [`MIN_TOKEN_LEN`].
49    WeakToken {
50        /// The client whose token is too short.
51        client: String,
52    },
53    /// A client has no token and `allow_anonymous` is not set.
54    AnonymousNotAllowed {
55        /// The client with no token.
56        client: String,
57    },
58    /// More than one client has no token, so "the anonymous caller" names
59    /// two different grants.
60    SeveralAnonymousClients,
61    /// A client is granted an application no section serves.
62    UnservedGrant {
63        /// The client.
64        client: String,
65        /// The application it was granted.
66        application: String,
67    },
68    /// A non-loopback `bind` with neither `tls` nor `insecure`.
69    ExposedBind {
70        /// The address.
71        bind: String,
72    },
73    /// `[kubernetes]` was present but unusable — an empty grant list, a
74    /// malformed `service_account`, or an environment that is not a
75    /// cluster. The reason names the fix; it never carries a token.
76    KubernetesAuth {
77        /// What was wrong, exactly.
78        reason: String,
79    },
80    /// `bind` is not a literal `address:port`.
81    UnparsableBind {
82        /// What was written.
83        bind: String,
84    },
85    /// `[server.tls]` in a build compiled without the `tls` feature.
86    TlsUnsupported,
87    /// `insecure` is set and `[server.tls]` is configured: an
88    /// acknowledgement of something that is not true.
89    InsecureWithTls,
90    /// A `[server.tls]` key that must name a file names an empty string.
91    TlsPathMissing {
92        /// Which key.
93        key: &'static str,
94    },
95    /// `tls.crl` is configured. This server checks no revocation and says so
96    /// rather than accepting a key it would ignore.
97    RevocationUnsupported,
98}
99
100impl fmt::Display for Refusal {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Self::NoSections => {
104                f.write_str("no `sections` are configured: this server would serve nothing at all")
105            }
106            Self::NoClients => f.write_str(
107                "no `clients` are configured: nothing could ever be read. Add a client with \
108                 a `token` and the `applications` it may read, or an anonymous one with \
109                 `allow_anonymous = true`",
110            ),
111            Self::DuplicateSection {
112                application,
113                profile,
114            } => write!(
115                f,
116                "two `sections` claim `{application}`/`{profile}`; one application and \
117                 profile is served by exactly one section"
118            ),
119            Self::UnroutableSection {
120                application,
121                profile,
122                part,
123            } => write!(
124                f,
125                "the section `{application}`/`{profile}` has a `{part}` no request can \
126                 name: a path segment is up to 64 characters, starts with a letter or a \
127                 digit, and carries only letters, digits, `.`, `_` and `-`. The server \
128                 would start, report ready and answer `404` for that section forever"
129            ),
130            Self::DuplicateClient { name } => {
131                write!(f, "two `clients` are named `{name}`; names identify a caller in the audit log and must be unique")
132            }
133            Self::DuplicateToken => f.write_str(
134                "two `clients` share a `token`; the first listed would silently win every \
135                 request and the audit log would name the wrong caller",
136            ),
137            Self::WeakToken { client } => write!(
138                f,
139                "the `token` for client `{client}` is shorter than {MIN_TOKEN_LEN} characters"
140            ),
141            Self::AnonymousNotAllowed { client } => write!(
142                f,
143                "client `{client}` has no `token`, which makes it the anonymous caller; set \
144                 `allow_anonymous = true` to say that is intended, or give it a token"
145            ),
146            Self::KubernetesAuth { reason } => {
147                write!(f, "[kubernetes] auth refused: {reason}")
148            }
149            Self::SeveralAnonymousClients => f.write_str(
150                "more than one client has no `token`; there is one anonymous caller, so it \
151                 can have only one set of grants",
152            ),
153            Self::UnservedGrant {
154                client,
155                application,
156            } => write!(
157                f,
158                "client `{client}` is granted `{application}`, which no section serves; a \
159                 grant that matches nothing is a typo that reads as a working deployment"
160            ),
161            Self::ExposedBind { bind } => write!(
162                f,
163                "`bind` is `{bind}`, which is not loopback, and this server is terminating no \
164                 TLS: that would put configuration — secrets included — on the network in the \
165                 clear. Terminate TLS here with a `[server.tls]` section, or put a terminator \
166                 in front of it and set `insecure = true` to say so, or bind loopback"
167            ),
168            Self::UnparsableBind { bind } => write!(
169                f,
170                "`bind` is `{bind}`, which is not a literal `address:port`; a hostname is \
171                 refused rather than resolved"
172            ),
173            Self::TlsUnsupported => f.write_str(
174                "`[server.tls]` is configured, but this binary was built without the `tls` \
175                 feature and contains no TLS at all. Rebuild it with `--features tls`, or \
176                 remove `[server.tls]` and put a terminator in front",
177            ),
178            Self::InsecureWithTls => f.write_str(
179                "`insecure = true` is set and `[server.tls]` is configured. `insecure` \
180                 acknowledges that this server's own socket is unencrypted, which is no longer \
181                 true — remove it, so that removing the TLS section later refuses again \
182                 instead of quietly serving in the clear",
183            ),
184            Self::TlsPathMissing { key } => {
185                write!(f, "`tls.{key}` is empty; it has to name a PEM file")
186            }
187            Self::RevocationUnsupported => f.write_str(
188                "`tls.crl` is configured, but this server checks no certificate revocation and \
189                 will not pretend to. A CRL whose `nextUpdate` has passed is accepted silently \
190                 by default, so the list would stop being true the moment it stopped being \
191                 refreshed and nothing would report it; the one setting that refuses a stale \
192                 list refuses every client along with it, which turns a publishing hiccup into \
193                 an outage for every service at once. Remove the key. Issue short-lived client \
194                 certificates, and revoke the `token` — delete the client's line and restart — \
195                 which is the credential that actually authorises here",
196            ),
197        }
198    }
199}
200
201impl std::error::Error for Refusal {}