Skip to content

Commit e157e2c

Browse files
authored
Enable anonymous access to GCS buckets (#322)
* Gcp skip signature * Skip signing if empty bearer token
1 parent 7272f70 commit e157e2c

File tree

3 files changed

+66
-36
lines changed

3 files changed

+66
-36
lines changed

src/gcp/builder.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717

1818
use crate::client::{http_connector, HttpConnector, TokenCredentialProvider};
19+
use crate::config::ConfigValue;
1920
use crate::gcp::client::{GoogleCloudStorageClient, GoogleCloudStorageConfig};
2021
use crate::gcp::credential::{
2122
ApplicationDefaultCredentials, InstanceCredentialProvider, ServiceAccountCredentials,
@@ -109,6 +110,8 @@ pub struct GoogleCloudStorageBuilder {
109110
client_options: ClientOptions,
110111
/// Credentials
111112
credentials: Option<GcpCredentialProvider>,
113+
/// Skip signing requests
114+
skip_signature: ConfigValue<bool>,
112115
/// Credentials for sign url
113116
signing_credentials: Option<GcpSigningCredentialProvider>,
114117
/// The [`HttpConnector`] to use
@@ -161,6 +164,9 @@ pub enum GoogleConfigKey {
161164
/// See [`GoogleCloudStorageBuilder::with_application_credentials`].
162165
ApplicationCredentials,
163166

167+
/// Skip signing request
168+
SkipSignature,
169+
164170
/// Client options
165171
Client(ClientConfigKey),
166172
}
@@ -172,6 +178,7 @@ impl AsRef<str> for GoogleConfigKey {
172178
Self::ServiceAccountKey => "google_service_account_key",
173179
Self::Bucket => "google_bucket",
174180
Self::ApplicationCredentials => "google_application_credentials",
181+
Self::SkipSignature => "google_skip_signature",
175182
Self::Client(key) => key.as_ref(),
176183
}
177184
}
@@ -189,6 +196,7 @@ impl FromStr for GoogleConfigKey {
189196
"google_service_account_key" | "service_account_key" => Ok(Self::ServiceAccountKey),
190197
"google_bucket" | "google_bucket_name" | "bucket" | "bucket_name" => Ok(Self::Bucket),
191198
"google_application_credentials" => Ok(Self::ApplicationCredentials),
199+
"google_skip_signature" | "skip_signature" => Ok(Self::SkipSignature),
192200
_ => match s.strip_prefix("google_").unwrap_or(s).parse() {
193201
Ok(key) => Ok(Self::Client(key)),
194202
Err(_) => Err(Error::UnknownConfigurationKey { key: s.into() }.into()),
@@ -208,6 +216,7 @@ impl Default for GoogleCloudStorageBuilder {
208216
client_options: ClientOptions::new().with_allow_http(true),
209217
url: None,
210218
credentials: None,
219+
skip_signature: Default::default(),
211220
signing_credentials: None,
212221
http_connector: None,
213222
}
@@ -288,6 +297,7 @@ impl GoogleCloudStorageBuilder {
288297
GoogleConfigKey::ApplicationCredentials => {
289298
self.application_credentials_path = Some(value.into())
290299
}
300+
GoogleConfigKey::SkipSignature => self.skip_signature.parse(value),
291301
GoogleConfigKey::Client(key) => {
292302
self.client_options = self.client_options.with_config(key, value)
293303
}
@@ -312,6 +322,7 @@ impl GoogleCloudStorageBuilder {
312322
GoogleConfigKey::ServiceAccountKey => self.service_account_key.clone(),
313323
GoogleConfigKey::Bucket => self.bucket_name.clone(),
314324
GoogleConfigKey::ApplicationCredentials => self.application_credentials_path.clone(),
325+
GoogleConfigKey::SkipSignature => Some(self.skip_signature.to_string()),
315326
GoogleConfigKey::Client(key) => self.client_options.get_config_value(key),
316327
}
317328
}
@@ -389,6 +400,14 @@ impl GoogleCloudStorageBuilder {
389400
self
390401
}
391402

403+
/// If enabled, [`GoogleCloudStorage`] will not fetch credentials and will not sign requests.
404+
///
405+
/// This can be useful when interacting with public GCS buckets that deny authorized requests.
406+
pub fn with_skip_signature(mut self, skip_signature: bool) -> Self {
407+
self.skip_signature = skip_signature.into();
408+
self
409+
}
410+
392411
/// Set the credential provider overriding any other options
393412
pub fn with_credentials(mut self, credentials: GcpCredentialProvider) -> Self {
394413
self.credentials = Some(credentials);
@@ -546,14 +565,15 @@ impl GoogleCloudStorageBuilder {
546565
)) as _
547566
};
548567

549-
let config = GoogleCloudStorageConfig::new(
550-
gcs_base_url,
568+
let config = GoogleCloudStorageConfig {
569+
base_url: gcs_base_url,
551570
credentials,
552571
signing_credentials,
553572
bucket_name,
554-
self.retry_config,
555-
self.client_options,
556-
);
573+
retry_config: self.retry_config,
574+
client_options: self.client_options,
575+
skip_signature: self.skip_signature.get()?,
576+
};
557577

558578
let http_client = http.connect(&config.client_options)?;
559579
Ok(GoogleCloudStorage {

src/gcp/client.rs

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::client::s3::{
2525
ListResponse,
2626
};
2727
use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpResponse};
28+
use crate::gcp::credential::CredentialExt;
2829
use crate::gcp::{GcpCredential, GcpCredentialProvider, GcpSigningCredentialProvider, STORE};
2930
use crate::multipart::PartId;
3031
use crate::path::{Path, DELIMITER};
@@ -144,30 +145,21 @@ pub(crate) struct GoogleCloudStorageConfig {
144145
pub retry_config: RetryConfig,
145146

146147
pub client_options: ClientOptions,
148+
149+
pub skip_signature: bool,
147150
}
148151

149152
impl GoogleCloudStorageConfig {
150-
pub(crate) fn new(
151-
base_url: String,
152-
credentials: GcpCredentialProvider,
153-
signing_credentials: GcpSigningCredentialProvider,
154-
bucket_name: String,
155-
retry_config: RetryConfig,
156-
client_options: ClientOptions,
157-
) -> Self {
158-
Self {
159-
base_url,
160-
credentials,
161-
signing_credentials,
162-
bucket_name,
163-
retry_config,
164-
client_options,
165-
}
166-
}
167-
168153
pub(crate) fn path_url(&self, path: &Path) -> String {
169154
format!("{}/{}/{}", self.base_url, self.bucket_name, path)
170155
}
156+
157+
pub(crate) async fn get_credential(&self) -> Result<Option<Arc<GcpCredential>>> {
158+
Ok(match self.skip_signature {
159+
false => Some(self.credentials.get_credential().await?),
160+
true => None,
161+
})
162+
}
171163
}
172164

173165
/// A builder for a put request allowing customisation of the headers and query string
@@ -304,8 +296,8 @@ impl GoogleCloudStorageClient {
304296
&self.config
305297
}
306298

307-
async fn get_credential(&self) -> Result<Arc<GcpCredential>> {
308-
self.config.credentials.get_credential().await
299+
async fn get_credential(&self) -> Result<Option<Arc<GcpCredential>>> {
300+
self.config.get_credential().await
309301
}
310302

311303
/// Create a signature from a string-to-sign using Google Cloud signBlob method.
@@ -341,7 +333,7 @@ impl GoogleCloudStorageClient {
341333
let response = self
342334
.client
343335
.post(&url)
344-
.bearer_auth(&credential.bearer)
336+
.with_bearer_auth(credential.as_deref())
345337
.json(&body)
346338
.retryable(&self.config.retry_config)
347339
.idempotent(true)
@@ -493,7 +485,7 @@ impl GoogleCloudStorageClient {
493485

494486
self.client
495487
.request(Method::DELETE, &url)
496-
.bearer_auth(&credential.bearer)
488+
.with_bearer_auth(credential.as_deref())
497489
.header(CONTENT_TYPE, "application/octet-stream")
498490
.header(CONTENT_LENGTH, "0")
499491
.query(&[("uploadId", multipart_id)])
@@ -538,7 +530,7 @@ impl GoogleCloudStorageClient {
538530
let response = self
539531
.client
540532
.request(Method::POST, &url)
541-
.bearer_auth(&credential.bearer)
533+
.with_bearer_auth(credential.as_deref())
542534
.query(&[("uploadId", upload_id)])
543535
.body(data)
544536
.retryable(&self.config.retry_config)
@@ -594,7 +586,7 @@ impl GoogleCloudStorageClient {
594586
}
595587

596588
builder
597-
.bearer_auth(&credential.bearer)
589+
.with_bearer_auth(credential.as_deref())
598590
// Needed if reqwest is compiled with native-tls instead of rustls-tls
599591
// See https://github.com/apache/arrow-rs/pull/3921
600592
.header(CONTENT_LENGTH, 0)
@@ -640,11 +632,8 @@ impl GetClient for GoogleCloudStorageClient {
640632
request = request.query(&[("generation", version)]);
641633
}
642634

643-
if !credential.bearer.is_empty() {
644-
request = request.bearer_auth(&credential.bearer);
645-
}
646-
647635
let response = request
636+
.with_bearer_auth(credential.as_deref())
648637
.with_get_options(options)
649638
.send_retry(&self.config.retry_config)
650639
.await
@@ -696,7 +685,7 @@ impl ListClient for Arc<GoogleCloudStorageClient> {
696685
.client
697686
.request(Method::GET, url)
698687
.query(&query)
699-
.bearer_auth(&credential.bearer)
688+
.with_bearer_auth(credential.as_deref())
700689
.send_retry(&self.config.retry_config)
701690
.await
702691
.map_err(|source| Error::ListRequest { source })?

src/gcp/credential.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717

1818
use super::client::GoogleCloudStorageClient;
19+
use crate::client::builder::HttpRequestBuilder;
1920
use crate::client::retry::RetryExt;
2021
use crate::client::token::TemporaryToken;
2122
use crate::client::{HttpClient, HttpError, TokenProvider};
@@ -424,7 +425,7 @@ impl TokenProvider for InstanceCredentialProvider {
424425
/// Since the connection is local we need to enable http access and don't actually use the client object passed in.
425426
/// Respects the `GCE_METADATA_HOST`, `GCE_METADATA_ROOT`, and `GCE_METADATA_IP`
426427
/// environment variables.
427-
///
428+
///
428429
/// References: <https://googleapis.dev/python/google-auth/latest/reference/google.auth.environment_vars.html>
429430
async fn fetch_token(
430431
&self,
@@ -494,7 +495,7 @@ impl TokenProvider for InstanceSigningCredentialProvider {
494495
/// Since the connection is local we need to enable http access and don't actually use the client object passed in.
495496
/// Respects the `GCE_METADATA_HOST`, `GCE_METADATA_ROOT`, and `GCE_METADATA_IP`
496497
/// environment variables.
497-
///
498+
///
498499
/// References: <https://googleapis.dev/python/google-auth/latest/reference/google.auth.environment_vars.html>
499500
async fn fetch_token(
500501
&self,
@@ -854,6 +855,26 @@ impl GCSAuthorizer {
854855
}
855856
}
856857

858+
pub(crate) trait CredentialExt {
859+
/// Apply bearer authentication to the request if the credential is not None
860+
fn with_bearer_auth(self, credential: Option<&GcpCredential>) -> Self;
861+
}
862+
863+
impl CredentialExt for HttpRequestBuilder {
864+
fn with_bearer_auth(self, credential: Option<&GcpCredential>) -> Self {
865+
match credential {
866+
Some(credential) => {
867+
if credential.bearer.is_empty() {
868+
self
869+
} else {
870+
self.bearer_auth(&credential.bearer)
871+
}
872+
}
873+
None => self,
874+
}
875+
}
876+
}
877+
857878
#[cfg(test)]
858879
mod tests {
859880
use super::*;

0 commit comments

Comments
 (0)