krishvishal commented on code in PR #4535:
URL: https://github.com/apache/iggy/pull/4535#discussion_r4267735019
##########
gateways/kafka/src/server.rs:
##########
@@ -722,6 +1040,52 @@ async fn handle_connection(
}
}
+/// Looks up the Iggy user a client certificate named, within the
pre-authentication budget.
+///
+/// `None` closes the connection. Unlike a SASL refusal there is no response
to shape: the client
+/// has sent nothing yet, and a certificate the CA signed for an account that
does not exist is a
+/// provisioning mistake the gateway's log reports, not something the client
can correct.
+async fn resolve_certificate_principal(
+ ctx: &ConnectionContext<'_>,
+ username: &str,
+) -> Option<AuthenticatedPrincipal> {
+ let peer = ctx.peer;
+ let Some(resolver) = ctx.resolver else {
+ // Unreachable: `check_tls_wiring` refuses to start in this
combination. Fail closed
+ // anyway, since the alternative is admitting an unverified identity.
+ error!(%peer, "a client certificate named a principal but nothing can
look it up");
+ return None;
+ };
+ // The lookup is an Iggy call per handshake, so it shares the
authentication slots with SASL.
+ let lookup = within_auth_budget(ctx, "certificate identity lookup", |slot|
async move {
Review Comment:
This takes a SASL auth slot for the lookup. A burst of SASL logins then
starves mTLS admission, and stuck lookups can block SASL logins.
The `SaslAuthenticator` doc says the slot is meant for password checks, and
a lookup has none. I'd give lookups their own semaphore.
##########
gateways/kafka/src/bridge/iggy_bridge/mod.rs:
##########
@@ -243,13 +245,36 @@ async fn connect_client(config: &IggyBridgeConfig) ->
Result<IggyClient, BridgeE
let credentials =
Credentials::UsernamePassword(config.username.clone(),
config.password.clone());
- let client = IggyClientBuilder::new()
+ let mut builder = IggyClientBuilder::new()
.with_tcp()
.with_server_address(config.address.clone())
.with_auto_sign_in(AutoLogin::Enabled(credentials))
- .with_reconnection_max_retries(Some(RECONNECTION_RETRIES))
- .build()
- .map_err(BridgeError::Iggy)?;
+ .with_reconnection_max_retries(Some(RECONNECTION_RETRIES));
+ if config.tls.enabled {
+ builder = builder.with_tls_enabled(true);
+ if !config.tls.domain.is_empty() {
+ builder = builder.with_tls_domain(config.tls.domain.clone());
+ }
+ if let Some(ref ca_file) = config.tls.ca_file {
+ builder = builder.with_tls_ca_file(ca_file.clone());
+ }
+ }
+ let client = builder.build().map_err(BridgeError::Iggy)?;
with_request_timeout(client.connect()).await?;
Ok(client)
}
+
+impl std::fmt::Debug for IggyBridge {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("IggyBridge").finish_non_exhaustive()
+ }
+}
+
+/// The bridge's own session answers for the users client certificates name.
It runs as the
+/// bridge's Iggy user, so that account needs `read_users` for any certificate
to be admitted.
+#[async_trait]
+impl PrincipalResolver for IggyBridge {
+ async fn resolve(&self, username: &str) -> Result<AuthenticatedPrincipal,
AuthError> {
+ resolve_principal(self.client.as_ref(), username).await
Review Comment:
Cert lookups share the lockstep client with Produce. A stuck send makes new
mTLS connections wait and time out, and lookups slow Produce down too.
Could lookups get their own lazy client, like `probe_client` for topic
probes?
##########
gateways/kafka/README.md:
##########
@@ -240,10 +248,88 @@ Limits:
in Iggy ([docs/OFFSET_STORAGE.md](docs/OFFSET_STORAGE.md#limits)).
- Delivery is at-least-once. A consumer that stops before it commits reads
those records again.
+## Listeners and TLS ([#4510](https://github.com/apache/iggy/issues/4510))
+
+The gateway binds one listener per Kafka security protocol, named the way
Kafka's own `listeners`
+and `advertised.listeners` are. A client is pointed back, in Metadata and
FindCoordinator, at the
+listener it connected through and no other, so a client on the TLS port never
learns the plaintext
+one.
+
+```bash
+IGGY_KAFKA_LISTENERS=PLAINTEXT://127.0.0.1:9093,SSL://0.0.0.0:9094,SASL_SSL://0.0.0.0:9095
\
+
IGGY_KAFKA_ADVERTISED_LISTENERS=SSL://kafka.example.com:9094,SASL_SSL://kafka.example.com:9095
\
+ IGGY_KAFKA_TLS_CERT_FILE=/etc/iggy/kafka.pem
IGGY_KAFKA_TLS_KEY_FILE=/etc/iggy/kafka.key \
+ cargo run -p iggy-gateway-kafka
+```
+
+Each security protocol can be bound once, since the protocol is also the
listener's name.
+
+TLS is rustls. One certificate serves every `SSL` and `SASL_SSL` listener, and
the files are
+re-read every `IGGY_KAFKA_TLS_RELOAD_INTERVAL_SECS`: a rotated certificate
takes effect for new
+connections without a restart, a key that does not match leaves the previous
material serving and
+logs an error. Rotate by writing the new files elsewhere and renaming them
into place: a chain
+written in place can be read half written, and if what was read still parses
and matches the key,
+the truncated chain serves until the next interval. There is no self-signed
mode; mint a
+certificate (`docs/MANUAL_TESTING.md` has an `openssl` recipe) and give the
Kafka client the CA.
+
+The certificate's subject alternative names must cover every host in
+`IGGY_KAFKA_ADVERTISED_LISTENERS`: a client verifies the name of each broker
address Metadata
+hands it, so a host missing from the certificate fails the client's next
connection, not the
+first. Client certificates are checked against the CA only; there is no CRL or
OCSP revocation
+checking, so revoke access by deleting or deactivating the Iggy user a
certificate maps to.
Review Comment:
Deleting or deactivating the user only blocks new connections. An open
connection keeps the principal it got at handshake and can still run
DeleteTopics.
Either re-check the principal before DeleteTopics and AlterConfigs, or say
here that revocation only applies to new connections.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]