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    /// `bind` is not a literal `address:port`.
74    UnparsableBind {
75        /// What was written.
76        bind: String,
77    },
78    /// `[server.tls]` in a build compiled without the `tls` feature.
79    TlsUnsupported,
80    /// `insecure` is set and `[server.tls]` is configured: an
81    /// acknowledgement of something that is not true.
82    InsecureWithTls,
83    /// A `[server.tls]` key that must name a file names an empty string.
84    TlsPathMissing {
85        /// Which key.
86        key: &'static str,
87    },
88    /// `tls.crl` is configured. This server checks no revocation and says so
89    /// rather than accepting a key it would ignore.
90    RevocationUnsupported,
91}
92
93impl fmt::Display for Refusal {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::NoSections => {
97                f.write_str("no `sections` are configured: this server would serve nothing at all")
98            }
99            Self::NoClients => f.write_str(
100                "no `clients` are configured: nothing could ever be read. Add a client with \
101                 a `token` and the `applications` it may read, or an anonymous one with \
102                 `allow_anonymous = true`",
103            ),
104            Self::DuplicateSection {
105                application,
106                profile,
107            } => write!(
108                f,
109                "two `sections` claim `{application}`/`{profile}`; one application and \
110                 profile is served by exactly one section"
111            ),
112            Self::UnroutableSection {
113                application,
114                profile,
115                part,
116            } => write!(
117                f,
118                "the section `{application}`/`{profile}` has a `{part}` no request can \
119                 name: a path segment is up to 64 characters, starts with a letter or a \
120                 digit, and carries only letters, digits, `.`, `_` and `-`. The server \
121                 would start, report ready and answer `404` for that section forever"
122            ),
123            Self::DuplicateClient { name } => {
124                write!(f, "two `clients` are named `{name}`; names identify a caller in the audit log and must be unique")
125            }
126            Self::DuplicateToken => f.write_str(
127                "two `clients` share a `token`; the first listed would silently win every \
128                 request and the audit log would name the wrong caller",
129            ),
130            Self::WeakToken { client } => write!(
131                f,
132                "the `token` for client `{client}` is shorter than {MIN_TOKEN_LEN} characters"
133            ),
134            Self::AnonymousNotAllowed { client } => write!(
135                f,
136                "client `{client}` has no `token`, which makes it the anonymous caller; set \
137                 `allow_anonymous = true` to say that is intended, or give it a token"
138            ),
139            Self::SeveralAnonymousClients => f.write_str(
140                "more than one client has no `token`; there is one anonymous caller, so it \
141                 can have only one set of grants",
142            ),
143            Self::UnservedGrant {
144                client,
145                application,
146            } => write!(
147                f,
148                "client `{client}` is granted `{application}`, which no section serves; a \
149                 grant that matches nothing is a typo that reads as a working deployment"
150            ),
151            Self::ExposedBind { bind } => write!(
152                f,
153                "`bind` is `{bind}`, which is not loopback, and this server is terminating no \
154                 TLS: that would put configuration — secrets included — on the network in the \
155                 clear. Terminate TLS here with a `[server.tls]` section, or put a terminator \
156                 in front of it and set `insecure = true` to say so, or bind loopback"
157            ),
158            Self::UnparsableBind { bind } => write!(
159                f,
160                "`bind` is `{bind}`, which is not a literal `address:port`; a hostname is \
161                 refused rather than resolved"
162            ),
163            Self::TlsUnsupported => f.write_str(
164                "`[server.tls]` is configured, but this binary was built without the `tls` \
165                 feature and contains no TLS at all. Rebuild it with `--features tls`, or \
166                 remove `[server.tls]` and put a terminator in front",
167            ),
168            Self::InsecureWithTls => f.write_str(
169                "`insecure = true` is set and `[server.tls]` is configured. `insecure` \
170                 acknowledges that this server's own socket is unencrypted, which is no longer \
171                 true — remove it, so that removing the TLS section later refuses again \
172                 instead of quietly serving in the clear",
173            ),
174            Self::TlsPathMissing { key } => {
175                write!(f, "`tls.{key}` is empty; it has to name a PEM file")
176            }
177            Self::RevocationUnsupported => f.write_str(
178                "`tls.crl` is configured, but this server checks no certificate revocation and \
179                 will not pretend to. A CRL whose `nextUpdate` has passed is accepted silently \
180                 by default, so the list would stop being true the moment it stopped being \
181                 refreshed and nothing would report it; the one setting that refuses a stale \
182                 list refuses every client along with it, which turns a publishing hiccup into \
183                 an outage for every service at once. Remove the key. Issue short-lived client \
184                 certificates, and revoke the `token` — delete the client's line and restart — \
185                 which is the credential that actually authorises here",
186            ),
187        }
188    }
189}
190
191impl std::error::Error for Refusal {}