Skip to content
Merged
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
39 changes: 16 additions & 23 deletions sdk/couchbase-core/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use std::sync::{Arc, Weak};
use std::time::Duration;

use crate::componentconfigs::{AgentComponentConfigs, HttpClientConfig};
use crate::httpx::request::{Auth, BasicAuth, BearerAuth};
use crate::kvclient_babysitter::{KvTarget, StdKvClientBabysitter};
use crate::kvendpointclientmanager::{
KvEndpointClientManager, KvEndpointClientManagerOptions, StdKvEndpointClientManager,
Expand Down Expand Up @@ -314,17 +315,7 @@ impl Agent {
let client_name = format!("couchbase-rs-core {build_version}");
info!("Creating new agent {client_name}");

let auth_mechanisms = if opts.auth_mechanisms.is_empty() {
if opts.tls_config.is_some() {
vec![AuthMechanism::Plain]
} else {
vec![
AuthMechanism::ScramSha512,
AuthMechanism::ScramSha256,
AuthMechanism::ScramSha1,
]
}
} else {
let auth_mechanisms = if !opts.auth_mechanisms.is_empty() {
if opts.tls_config.is_none() && opts.auth_mechanisms.contains(&AuthMechanism::Plain) {
warn!("PLAIN sends credentials in plaintext, this will cause credential leakage on the network");
} else if opts.tls_config.is_some()
Expand All @@ -336,6 +327,8 @@ impl Agent {
}

opts.auth_mechanisms
} else {
vec![]
};

let mut state = AgentState {
Expand Down Expand Up @@ -672,21 +665,24 @@ impl Agent {
for endpoint_config in http_configs.values() {
let endpoint = endpoint_config.endpoint.clone();
let host = get_host_port_from_uri(&endpoint)?;
let user_pass = match &endpoint_config.authenticator {
let auth = match &endpoint_config.authenticator {
Authenticator::PasswordAuthenticator(authenticator) => {
authenticator.get_credentials(&ServiceType::MGMT, host)?
let user_pass = authenticator.get_credentials(&ServiceType::MGMT, host)?;
Auth::BasicAuth(BasicAuth::new(user_pass.username, user_pass.password))
}
Authenticator::CertificateAuthenticator(_authenticator) => {
Auth::BasicAuth(BasicAuth::new("".to_string(), "".to_string()))
}
Authenticator::CertificateAuthenticator(authenticator) => {
authenticator.get_credentials(&ServiceType::MGMT, host)?
Authenticator::JwtAuthenticator(authenticator) => {
Auth::BearerAuth(BearerAuth::new(authenticator.get_token()))
}
};

match Self::fetch_http_config(
http_client.clone(),
endpoint,
endpoint_config.user_agent.clone(),
user_pass.username,
user_pass.password,
auth,
endpoint_config.bucket_name.clone(),
)
.await
Expand All @@ -709,8 +705,7 @@ impl Agent {
http_client: Arc<C>,
endpoint: String,
user_agent: String,
username: String,
password: String,
auth: Auth,
bucket_name: Option<String>,
) -> Result<ParsedConfig> {
debug!("Polling config from {}", &endpoint);
Expand All @@ -723,8 +718,7 @@ impl Agent {
http_client,
user_agent,
endpoint,
username,
password,
auth,
}
.get_terse_bucket_config(&GetTerseBucketConfigOptions {
bucket_name: &bucket_name,
Expand All @@ -739,8 +733,7 @@ impl Agent {
http_client,
user_agent,
endpoint,
username,
password,
auth,
}
.get_terse_cluster_config(&GetTerseClusterConfigOptions {
on_behalf_of_info: None,
Expand Down
4 changes: 4 additions & 0 deletions sdk/couchbase-core/src/auth_mechanism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub enum AuthMechanism {
ScramSha1,
ScramSha256,
ScramSha512,
OAuthBearer,
}

impl From<AuthMechanism> for Vec<u8> {
Expand All @@ -34,6 +35,7 @@ impl From<AuthMechanism> for Vec<u8> {
AuthMechanism::ScramSha1 => "SCRAM-SHA1",
AuthMechanism::ScramSha256 => "SCRAM-SHA256",
AuthMechanism::ScramSha512 => "SCRAM-SHA512",
AuthMechanism::OAuthBearer => "OAUTHBEARER",
};

txt.into()
Expand All @@ -49,6 +51,7 @@ impl TryFrom<&str> for AuthMechanism {
"SCRAM-SHA1" => AuthMechanism::ScramSha1,
"SCRAM-SHA256" => AuthMechanism::ScramSha256,
"SCRAM-SHA512" => AuthMechanism::ScramSha512,
"OAUTHBEARER" => AuthMechanism::OAuthBearer,
_ => {
return Err(Error::new_invalid_argument_error(
format!("unsupported auth mechanism {value}"),
Expand All @@ -68,6 +71,7 @@ impl From<AuthMechanism> for memdx::auth_mechanism::AuthMechanism {
AuthMechanism::ScramSha1 => memdx::auth_mechanism::AuthMechanism::ScramSha1,
AuthMechanism::ScramSha256 => memdx::auth_mechanism::AuthMechanism::ScramSha256,
AuthMechanism::ScramSha512 => memdx::auth_mechanism::AuthMechanism::ScramSha512,
AuthMechanism::OAuthBearer => memdx::auth_mechanism::AuthMechanism::OAuthBearer,
}
}
}
38 changes: 34 additions & 4 deletions sdk/couchbase-core/src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,26 @@
*
*/

use std::fmt::Debug;

use crate::auth_mechanism::AuthMechanism;
use crate::error::Result;
use crate::service_type::ServiceType;
use std::fmt::Debug;

#[derive(Debug, Clone, PartialEq, Hash)]
#[non_exhaustive]
pub enum Authenticator {
PasswordAuthenticator(PasswordAuthenticator),
CertificateAuthenticator(CertificateAuthenticator),
JwtAuthenticator(JwtAuthenticator),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct UserPassPair {
pub username: String,
pub password: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PasswordAuthenticator {
pub username: String,
pub password: String,
Expand All @@ -51,6 +52,14 @@ impl PasswordAuthenticator {
password: self.password.clone(),
})
}

pub fn get_auth_mechanisms(&self) -> Vec<AuthMechanism> {
vec![
AuthMechanism::ScramSha512,
AuthMechanism::ScramSha256,
AuthMechanism::ScramSha1,
]
}
}

impl From<PasswordAuthenticator> for Authenticator {
Expand Down Expand Up @@ -81,3 +90,24 @@ impl From<CertificateAuthenticator> for Authenticator {
Authenticator::CertificateAuthenticator(value)
}
}

#[derive(Debug, Clone, PartialEq, Hash)]
pub struct JwtAuthenticator {
pub token: String,
}

impl JwtAuthenticator {
pub fn get_token(&self) -> &str {
&self.token
}

pub fn get_auth_mechanisms(&self) -> Vec<AuthMechanism> {
vec![AuthMechanism::OAuthBearer]
}
}

impl From<JwtAuthenticator> for Authenticator {
fn from(value: JwtAuthenticator) -> Self {
Authenticator::JwtAuthenticator(value)
}
}
80 changes: 38 additions & 42 deletions sdk/couchbase-core/src/httpcomponent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::authenticator::Authenticator;
use crate::error;
use crate::error::ErrorKind;
use crate::httpx::client::Client;
use crate::httpx::request::{Auth, BasicAuth, BearerAuth};
use crate::retrybesteffort::BackoffCalculator;
use crate::service_type::ServiceType;
use crate::util::get_host_port_from_uri;
Expand All @@ -44,12 +45,37 @@ pub(crate) struct HttpComponentState {
}

pub(crate) struct HttpEndpointProperties {
pub username: String,
pub password: String,
pub auth: Auth,
pub endpoint: String,
pub endpoint_id: Option<String>,
}

pub(crate) fn auth_from_authenticator(
authenticator: &Authenticator,
service_type: &ServiceType,
host: &str,
) -> error::Result<Auth> {
match authenticator {
Authenticator::PasswordAuthenticator(authenticator) => {
let creds = authenticator.get_credentials(service_type, host.to_string())?;
Ok(Auth::BasicAuth(BasicAuth::new(
creds.username,
creds.password,
)))
}
Authenticator::CertificateAuthenticator(authenticator) => {
let creds = authenticator.get_credentials(service_type, host.to_string())?;
Ok(Auth::BasicAuth(BasicAuth::new(
creds.username,
creds.password,
)))
}
Authenticator::JwtAuthenticator(authenticator) => {
Ok(Auth::BearerAuth(BearerAuth::new(authenticator.get_token())))
}
}
}

impl<C: Client> HttpComponent<C> {
pub fn new(
service_type: ServiceType,
Expand Down Expand Up @@ -94,22 +120,14 @@ impl<C: Client> HttpComponent<C> {
};

let host = get_host_port_from_uri(found_endpoint)?;
let user_pass = match &state.authenticator {
Authenticator::PasswordAuthenticator(authenticator) => {
authenticator.get_credentials(&self.service_type, host)?
}
Authenticator::CertificateAuthenticator(a) => {
a.get_credentials(&self.service_type, host)?
}
};
let auth = auth_from_authenticator(&state.authenticator, &self.service_type, &host)?;

Ok((
self.client.clone(),
HttpEndpointProperties {
endpoint_id: None,
endpoint: found_endpoint.clone(),
username: user_pass.username,
password: user_pass.password,
auth,
},
))
}
Expand Down Expand Up @@ -145,22 +163,14 @@ impl<C: Client> HttpComponent<C> {
let endpoint = remaining_endpoints[endpoint_id];

let host = get_host_port_from_uri(endpoint)?;
let user_pass = match &state.authenticator {
Authenticator::PasswordAuthenticator(authenticator) => {
authenticator.get_credentials(&self.service_type, host)?
}
Authenticator::CertificateAuthenticator(a) => {
a.get_credentials(&self.service_type, host)?
}
};
let auth = auth_from_authenticator(&state.authenticator, &self.service_type, &host)?;

Ok(Some((
self.client.clone(),
HttpEndpointProperties {
endpoint_id: Some(endpoint_id.clone()),
endpoint: endpoint.clone(),
username: user_pass.username,
password: user_pass.password,
auth,
},
)))
}
Expand All @@ -172,7 +182,7 @@ impl<C: Client> HttpComponent<C> {
pub async fn orchestrate_endpoint<Resp, Fut>(
&self,
endpoint_id: Option<String>,
operation: impl Fn(Arc<C>, String, String, String, String) -> Fut + Send + Sync,
operation: impl Fn(Arc<C>, String, String, Auth) -> Fut + Send + Sync,
) -> error::Result<Resp>
where
C: Client,
Expand All @@ -186,8 +196,7 @@ impl<C: Client> HttpComponent<C> {
client,
endpoint_id,
endpoint_properties.endpoint,
endpoint_properties.username,
endpoint_properties.password,
endpoint_properties.auth,
)
.await;
}
Expand All @@ -205,8 +214,7 @@ impl<C: Client> HttpComponent<C> {
client,
endpoint_properties.endpoint_id.unwrap_or_default(),
endpoint_properties.endpoint,
endpoint_properties.username,
endpoint_properties.password,
endpoint_properties.auth,
)
.await
}
Expand All @@ -231,21 +239,9 @@ impl<C: Client> HttpComponent<C> {
let mut targets = Vec::with_capacity(remaining_endpoints.len());
for (_ep_id, endpoint) in remaining_endpoints {
let host = get_host_port_from_uri(endpoint)?;
let auth = auth_from_authenticator(&state.authenticator, &self.service_type, &host)?;

let user_pass = match &state.authenticator {
Authenticator::PasswordAuthenticator(authenticator) => {
authenticator.get_credentials(&self.service_type, host)?
}
Authenticator::CertificateAuthenticator(a) => {
a.get_credentials(&self.service_type, host)?
}
};

targets.push(T::new(
endpoint.clone(),
user_pass.username,
user_pass.password,
));
targets.push(T::new(endpoint.clone(), auth));
}

Ok((self.client.clone(), targets))
Expand Down Expand Up @@ -289,5 +285,5 @@ impl HttpComponentState {
}

pub(crate) trait NodeTarget {
fn new(endpoint: String, username: String, password: String) -> Self;
fn new(endpoint: String, auth: Auth) -> Self;
}
1 change: 1 addition & 0 deletions sdk/couchbase-core/src/httpx/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ impl Client for ReqwestClient {
Auth::BasicAuth(basic) => {
builder = builder.basic_auth(&basic.username, Some(&basic.password))
}
Auth::BearerAuth(bearer) => builder = builder.bearer_auth(&bearer.token),
Auth::OnBehalfOf(obo) => {
match &obo.password_or_domain {
OboPasswordOrDomain::Password(password) => {
Expand Down
16 changes: 15 additions & 1 deletion sdk/couchbase-core/src/httpx/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,24 @@ impl BasicAuth {
}
}

#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct BearerAuth {
pub(crate) token: String,
}

impl BearerAuth {
pub fn new(token: impl Into<String>) -> Self {
Self {
token: token.into(),
}
}
}

#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum Auth {
BasicAuth(BasicAuth),
BearerAuth(BearerAuth),
OnBehalfOf(OnBehalfOfInfo),
}

Expand Down
Loading