use std::net::{IpAddr, Ipv4Addr, SocketAddr};
pub use crate::auth::AuthMode;
pub const DEFAULT_BIND: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 50051);
pub const AUTHENTICATION_ENABLED: bool = true;
pub const DEFAULT_EVENT_BUFFER: usize = 1024;
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub bind: SocketAddr,
pub event_buffer: usize,
pub ssh_bind_hint: String,
pub auth: AuthMode,
pub mutual_tls: bool,
pub require_registered_client_certificate: bool,
pub web_relying_parties: Vec<crate::webauthn::RelyingParty>,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
bind: DEFAULT_BIND,
event_buffer: DEFAULT_EVENT_BUFFER,
ssh_bind_hint: String::new(),
auth: AuthMode::Required,
mutual_tls: false,
require_registered_client_certificate: false,
web_relying_parties: Vec::new(),
}
}
}
impl ServerConfig {
pub fn with_bind(mut self, bind: SocketAddr) -> Self {
self.bind = bind;
if !self.authentication_enabled() && !self.is_loopback_bind() {
tracing::error!(
bind = %self.bind,
"gunnar-grpc bound to a NON-LOOPBACK address with the allow-all development \
escape hatch ON. Every control-plane mutation — principals, keys, grants, \
branch protection, repo create/delete — is now reachable by anyone who can \
reach this port. Git push/fetch are NOT affected (separate SSH listener, \
publickey auth), but this is an open admin surface. Drop the escape hatch, \
or bind loopback."
);
}
self
}
pub fn insecure_allow_all_for_local_development(mut self) -> Self {
self.auth = AuthMode::AllowAllInsecure;
tracing::warn!(
bind = %self.bind,
"gRPC AUTHENTICATION HAS BEEN TURNED OFF for this listener \
(insecure_allow_all_for_local_development). Every control-plane mutation \
(principals, keys, grants, branch protection, repo create/delete) is served to \
any caller that can reach this port. This is a LOCAL DEVELOPMENT switch and must \
never appear in a deployment."
);
self
}
pub fn authentication_enabled(&self) -> bool {
self.auth.is_required()
}
pub fn with_web_relying_parties(mut self, parties: Vec<crate::webauthn::RelyingParty>) -> Self {
self.web_relying_parties = parties;
self
}
pub fn with_registered_client_certificate_required(mut self, require: bool) -> Self {
self.require_registered_client_certificate = require;
self
}
pub fn with_mutual_tls(mut self, mutual: bool) -> Self {
self.mutual_tls = mutual;
self
}
pub fn with_event_buffer(mut self, event_buffer: usize) -> Self {
self.event_buffer = event_buffer.max(1);
self
}
pub fn with_ssh_bind_hint(mut self, hint: impl Into<String>) -> Self {
self.ssh_bind_hint = hint.into();
self
}
pub fn is_loopback_bind(&self) -> bool {
self.bind.ip().is_loopback()
}
}