Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 117 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions sqlx-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub enum Error {
#[error("error occurred while attempting to establish a TLS connection: {0}")]
Tls(#[source] BoxDynError),

#[error("error occured during gssapi negotiation: {0}")]
GssApi(#[from] BoxDynError),

/// Unexpected or invalid data encountered while communicating with the database.
///
/// This should indicate there is a programming error in a SQLx driver or there
Expand Down
1 change: 1 addition & 0 deletions sqlx-postgres/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ whoami = { version = "1.2.1", default-features = false }

serde = { version = "1.0.144", features = ["derive"] }
serde_json = { version = "1.0.85", features = ["raw_value"] }
cross-krb5 = { version = "0.4.2" }

[dependencies.sqlx-core]
workspace = true
Expand Down
5 changes: 5 additions & 0 deletions sqlx-postgres/src/connection/establish.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::connection::gssapi;
use crate::HashMap;

use crate::common::StatementCache;
Expand Down Expand Up @@ -97,6 +98,10 @@ impl PgConnection {
.await?;
}

Authentication::Gss => {
gssapi::authenticate(&mut stream, options).await?;
}

Authentication::Sasl(body) => {
sasl::authenticate(&mut stream, options, body).await?;
}
Expand Down
45 changes: 45 additions & 0 deletions sqlx-postgres/src/connection/gssapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::borrow::Cow;

use crate::error::Error;
use cross_krb5::InitiateFlags;

use crate::{
connection::PgStream,
message::{Authentication, AuthenticationGss, GssResponse},
PgConnectOptions,
};

pub async fn authenticate(stream: &mut PgStream, options: &PgConnectOptions) -> Result<(), Error> {
let PgConnectOptions {
host,
gssapi_target_principal: gssapi_principal,
..
} = options;
let principal = gssapi_principal
.as_ref()
.map(Cow::Borrowed)
.unwrap_or(Cow::Owned(format!("postgres/{host}")));
let (mut ctx, token) =
cross_krb5::ClientCtx::new(InitiateFlags::empty(), None, &principal, None)
.map_err(|e| Error::GssApi(e.into()))?;
let msg = GssResponse { token: &token };
stream.send(msg).await?;
loop {
let token = match stream.recv_expect().await? {
Authentication::GssContinue(AuthenticationGss { token }) => token,
other => return Err(err_protocol!("expected GssContinue but receiver {other:?}")),
};
match ctx.step(&token).map_err(|e| Error::GssApi(e.into()))? {
cross_krb5::Step::Finished((_context, last_token)) => {
if let Some(last_token) = last_token {
stream.send(GssResponse { token: &last_token }).await?;
}
return Ok(());
}
cross_krb5::Step::Continue((pending, token)) => {
ctx = pending;
stream.send(GssResponse { token: &token }).await?;
}
}
}
}
1 change: 1 addition & 0 deletions sqlx-postgres/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub use self::stream::PgStream;
pub(crate) mod describe;
mod establish;
mod executor;
mod gssapi;
mod sasl;
mod stream;
mod tls;
Expand Down
13 changes: 13 additions & 0 deletions sqlx-postgres/src/message/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub enum Authentication {
/// again using the 4-byte random salt.
Md5Password(AuthenticationMd5Password),

/// The frontend must initiate GSSAPI negotiation
Gss,

/// GSSAPI token reponse for continuing the security context
GssContinue(AuthenticationGss),

/// The frontend must now initiate a SASL negotiation,
/// using one of the SASL mechanisms listed in the message.
///
Expand Down Expand Up @@ -75,6 +81,8 @@ impl BackendMessage for Authentication {

Authentication::Md5Password(AuthenticationMd5Password { salt })
}
7 => Authentication::Gss,
8 => Authentication::GssContinue(AuthenticationGss { token: buf }),

10 => Authentication::Sasl(AuthenticationSasl(buf)),
11 => Authentication::SaslContinue(AuthenticationSaslContinue::decode(buf)?),
Expand Down Expand Up @@ -191,3 +199,8 @@ impl ProtocolDecode<'_> for AuthenticationSaslFinal {
Ok(Self { verifier })
}
}

#[derive(Debug)]
pub struct AuthenticationGss {
pub token: Bytes,
}
20 changes: 20 additions & 0 deletions sqlx-postgres/src/message/gssapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::message::{FrontendMessage, FrontendMessageFormat};

pub struct GssResponse<'g> {
pub(crate) token: &'g [u8],
}
impl<'g> FrontendMessage for GssResponse<'g> {
const FORMAT: FrontendMessageFormat = FrontendMessageFormat::PasswordPolymorphic;

fn body_size_hint(&self) -> std::num::Saturating<usize> {
let mut size = std::num::Saturating(0);
size += 4;
size += self.token.len();
size
}

fn encode_body(&self, buf: &mut Vec<u8>) -> Result<(), sqlx_core::Error> {
buf.extend_from_slice(&self.token);
Ok(())
}
}
4 changes: 3 additions & 1 deletion sqlx-postgres/src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod data_row;
mod describe;
mod execute;
mod flush;
mod gssapi;
mod notification;
mod parameter_description;
mod parameter_status;
Expand All @@ -30,7 +31,7 @@ mod startup;
mod sync;
mod terminate;

pub use authentication::{Authentication, AuthenticationSasl};
pub use authentication::{Authentication, AuthenticationGss, AuthenticationSasl};
pub use backend_key_data::BackendKeyData;
pub use bind::Bind;
pub use close::Close;
Expand All @@ -41,6 +42,7 @@ pub use describe::Describe;
pub use execute::Execute;
#[allow(unused_imports)]
pub use flush::Flush;
pub use gssapi::GssResponse;
pub use notification::Notification;
pub use parameter_description::ParameterDescription;
pub use parameter_status::ParameterStatus;
Expand Down
8 changes: 8 additions & 0 deletions sqlx-postgres/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct PgConnectOptions {
pub(crate) username: String,
pub(crate) password: Option<String>,
pub(crate) database: Option<String>,
pub(crate) gssapi_target_principal: Option<String>,
pub(crate) ssl_mode: PgSslMode,
pub(crate) ssl_root_cert: Option<CertificateInput>,
pub(crate) ssl_client_cert: Option<CertificateInput>,
Expand Down Expand Up @@ -75,6 +76,7 @@ impl PgConnectOptions {
username,
password: var("PGPASSWORD").ok(),
database,
gssapi_target_principal: var("PGPRINCIPAL").ok(),
ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from),
// As of writing, the implementation of `From<String>` only looks for
Expand Down Expand Up @@ -336,6 +338,12 @@ impl PgConnectOptions {
self
}

/// Sets the targeted principal in case of attempted Kerberos negotiation
/// If left out and Kerberos is challenged, uses 'postgres/<hostname>'
pub fn gssapi_target_principal(mut self, target_principal: &str) -> Self {
self.gssapi_target_principal = Some(target_principal.to_owned());
self
}
/// Sets the capacity of the connection's statement cache in a number of stored
/// distinct statements. Caching is handled using LRU, meaning when the
/// amount of queries hits the defined limit, the oldest statement will get
Expand Down
Loading