fix(a7-3/s1): require TLS or local transport for daemon auth

Daemon modules that declare 'auth users' no longer accept credentials over a
remote plaintext connection: server_module_gate refuses at the config gate,
before any SCRAM challenge is sent, unless the connection is verified TLS with
a client certificate matching --client-cn, or a local/SSH transport (loopback
TCP peer or the --stdio pipe). --allow-unauthenticated does not relax this.

The TLS client-CN comparison now uses credentials_secure_equal (S2). Clients
sending --password-file to a non-loopback daemon must use --tls; validate_config
rejects the plaintext case before any network I/O.

Adds utils_sockaddr_is_loopback / utils_fd_peer_is_local / utils_host_is_loopback
helpers with unit tests, a client validation unit test, and integration tests
for the client-side plaintext rejection and the wrong-CN gate refusal.
This commit is contained in:
2026-09-12 19:02:05 +02:00
parent 2489d422e5
commit a7a1930e88
9 changed files with 312 additions and 11 deletions
+37
View File
@@ -69,6 +69,42 @@ static void test_validate_config_tls_requirements() {
config_delete(cfg);
}
/* A7-3/S1: --password-file sends daemon credentials, so it is only allowed
over TLS (which itself mandates a verified --cert/--key/--ca) or to a
loopback destination. A remote plaintext daemon is refused up front. */
static void test_validate_config_credentials_require_tls_or_loopback() {
/* Default host is 127.0.0.1 (loopback), so plaintext credentials are fine. */
Config* cfg = valid_client_config();
cfg->password_file = str_dup("creds.pw");
EXPECT_TRUE(validate_config(cfg));
/* localhost is loopback too. */
free(cfg->server_host);
cfg->server_host = str_dup("localhost");
EXPECT_TRUE(validate_config(cfg));
/* A clearly remote host over plaintext is refused before any network I/O. */
free(cfg->server_host);
cfg->server_host = str_dup("192.0.2.1");
EXPECT_FALSE(validate_config(cfg));
/* TLS makes the remote destination acceptable (cert/key/ca are required). */
cfg->use_tls = true;
EXPECT_FALSE(validate_config(cfg));
cfg->tls_cert = str_dup("cert.pem");
cfg->tls_key = str_dup("key.pem");
cfg->tls_ca = str_dup("ca.pem");
EXPECT_TRUE(validate_config(cfg));
/* No credentials: the remote plaintext rule does not apply. */
cfg->use_tls = false;
char* creds = cfg->password_file;
cfg->password_file = NULL;
EXPECT_TRUE(validate_config(cfg));
cfg->password_file = creds;
config_delete(cfg);
}
static void test_validate_config_delta_sendfile_constraints() {
Config* cfg = valid_client_config();
cfg->use_delta = true;
@@ -3117,6 +3153,7 @@ void test_client_cli() {
test_validate_config_append_verify_rejects_whole_file();
test_validate_config_incompatible_options();
test_validate_config_tls_requirements();
test_validate_config_credentials_require_tls_or_loopback();
test_validate_config_delta_sendfile_constraints();
test_cli_help();
test_cli_archive_flags();