tustvold commented on code in PR #5259:
URL: https://github.com/apache/arrow-rs/pull/5259#discussion_r1438417509
##########
object_store/src/azure/credential.rs:
##########
@@ -137,33 +141,47 @@ pub mod authority_hosts {
pub const AZURE_PUBLIC_CLOUD: &str = "https://login.microsoftonline.com";
}
-pub(crate) trait CredentialExt {
- /// Apply authorization to requests against azure storage accounts
- ///
<https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage>
- fn with_azure_authorization(self, credential: &AzureCredential, account:
&str) -> Self;
+/// Authorize a [`Request`] with an [`AzureAuthorizer`]
+#[derive(Debug)]
+pub struct AzureAuthorizer<'a> {
+ credential: &'a AzureCredential,
+ delegation_key: Option<&'a UserDelegationKey>,
Review Comment:
This feels a little out of place here, as it is only used for URL signing
and not the general case of request signing?
##########
object_store/src/azure/client.rs:
##########
@@ -324,6 +333,45 @@ impl AzureClient {
Ok(())
}
+ /// Make a Get User Delegation Key request
+ ///
<https://docs.microsoft.com/en-us/rest/api/storageservices/get-user-delegation-key>
+ pub async fn get_user_delegation_key(
+ &self,
+ start: &DateTime<Utc>,
Review Comment:
I think I prefer the approach on AwsAuthorizer where the date is stored on
the authorizer and used for testing, but the public methods take an expiry.
##########
object_store/src/azure/credential.rs:
##########
@@ -205,6 +286,156 @@ fn add_if_exists<'a>(h: &'a HeaderMap, key: &HeaderName)
-> &'a str {
.unwrap_or_default()
}
+fn string_to_sign_sas(
+ u: &Url,
+ method: &Method,
+ account: &str,
+ start: &DateTime<Utc>,
+ end: &DateTime<Utc>,
+) -> (String, String, String, String, String) {
+ let signed_resource = if u
+ .query()
+ .map(|q| q.contains("comp=list"))
+ .unwrap_or_default()
+ {
+ "c"
+ } else {
+ "b"
+ }
+ .to_string();
+
+ //
https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
+ let signed_permissions = match *method {
+ // read and list permissions
+ Method::GET => match signed_resource.as_str() {
+ "c" => "rl",
+ "b" => "r",
+ _ => unreachable!(),
+ },
+ // write permissions (also allows crating a new blob in a sub-key)
+ Method::PUT => "w",
+ // delete permissions
+ Method::DELETE => "d",
+ // other methods are not used in any of the current operations
+ _ => "",
+ }
+ .to_string();
+ let signed_start = start.to_rfc3339_opts(SecondsFormat::Secs, true);
+ let signed_expiry = end.to_rfc3339_opts(SecondsFormat::Secs, true);
+ let canonicalized_resource = if
u.host_str().unwrap_or_default().contains(account) {
+ format!("/blob/{}{}", account, u.path())
+ } else {
+ // NOTE: in case of the emulator, the account name is not part of the
host
+ // but the path starts with the account name
+ format!("/blob{}", u.path())
+ };
+
+ (
+ signed_resource,
+ signed_permissions,
+ signed_start,
+ signed_expiry,
+ canonicalized_resource,
+ )
+}
+
+fn string_to_sign_service_sas(
+ u: &Url,
+ method: &Method,
+ account: &str,
+ start: &DateTime<Utc>,
+ end: &DateTime<Utc>,
+) -> (String, HashMap<&'static str, String>) {
+ let (signed_resource, signed_permissions, signed_start, signed_expiry,
canonicalized_resource) =
+ string_to_sign_sas(u, method, account, start, end);
+
+ //
https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#version-2020-12-06-and-later
+ let string_to_sign = format!(
+ "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
+ signed_permissions,
+ signed_start,
+ signed_expiry,
+ canonicalized_resource,
+ "", // signed identifier
+ "", // signed ip
+ "", // signed protocol
+ &AZURE_VERSION.to_str().unwrap(), // signed version
+ signed_resource, // signed resource
+ "", // signed snapshot time
+ "", // signed encryption scope
+ "", // rscc - response header:
Cache-Control
+ "", // rscd - response header:
Content-Disposition
+ "", // rsce - response header:
Content-Encoding
+ "", // rscl - response header:
Content-Language
+ "", // rsct - response header:
Content-Type
+ );
+
+ let mut pairs = HashMap::new();
+ pairs.insert("sv", AZURE_VERSION.to_str().unwrap().to_string());
+ pairs.insert("sp", signed_permissions);
+ pairs.insert("st", signed_start);
+ pairs.insert("se", signed_expiry);
+ pairs.insert("sr", signed_resource);
+
+ (string_to_sign, pairs)
+}
+
+fn string_to_sign_user_delegation_sas(
+ u: &Url,
+ method: &Method,
+ account: &str,
+ start: &DateTime<Utc>,
+ end: &DateTime<Utc>,
+ delegation_key: &UserDelegationKey,
+) -> (String, HashMap<&'static str, String>) {
+ let (signed_resource, signed_permissions, signed_start, signed_expiry,
canonicalized_resource) =
+ string_to_sign_sas(u, method, account, start, end);
+
+ //
https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas#version-2020-12-06-and-later
Review Comment:
Same as above, I think a doc comment is more discoverable
##########
object_store/src/azure/credential.rs:
##########
@@ -205,6 +286,156 @@ fn add_if_exists<'a>(h: &'a HeaderMap, key: &HeaderName)
-> &'a str {
.unwrap_or_default()
}
+fn string_to_sign_sas(
+ u: &Url,
+ method: &Method,
+ account: &str,
+ start: &DateTime<Utc>,
+ end: &DateTime<Utc>,
+) -> (String, String, String, String, String) {
+ let signed_resource = if u
+ .query()
+ .map(|q| q.contains("comp=list"))
Review Comment:
Does signing a list request even make sense?
##########
object_store/src/azure/client.rs:
##########
@@ -600,6 +648,18 @@ impl BlockList {
}
}
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct UserDelegationKey {
+ pub signed_oid: String,
+ pub signed_tid: String,
+ pub signed_start: String,
+ pub signed_expiry: String,
+ pub signed_service: String,
+ pub signed_version: String,
+ pub value: String,
Review Comment:
Do these fields need to be public? Do we expect users to use this directly?
##########
object_store/src/azure/credential.rs:
##########
@@ -205,6 +286,156 @@ fn add_if_exists<'a>(h: &'a HeaderMap, key: &HeaderName)
-> &'a str {
.unwrap_or_default()
}
+fn string_to_sign_sas(
+ u: &Url,
+ method: &Method,
+ account: &str,
+ start: &DateTime<Utc>,
+ end: &DateTime<Utc>,
+) -> (String, String, String, String, String) {
+ let signed_resource = if u
+ .query()
+ .map(|q| q.contains("comp=list"))
+ .unwrap_or_default()
+ {
+ "c"
+ } else {
+ "b"
+ }
+ .to_string();
+
+ //
https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
+ let signed_permissions = match *method {
+ // read and list permissions
+ Method::GET => match signed_resource.as_str() {
+ "c" => "rl",
+ "b" => "r",
+ _ => unreachable!(),
+ },
+ // write permissions (also allows crating a new blob in a sub-key)
+ Method::PUT => "w",
+ // delete permissions
+ Method::DELETE => "d",
+ // other methods are not used in any of the current operations
+ _ => "",
+ }
+ .to_string();
+ let signed_start = start.to_rfc3339_opts(SecondsFormat::Secs, true);
+ let signed_expiry = end.to_rfc3339_opts(SecondsFormat::Secs, true);
+ let canonicalized_resource = if
u.host_str().unwrap_or_default().contains(account) {
+ format!("/blob/{}{}", account, u.path())
+ } else {
+ // NOTE: in case of the emulator, the account name is not part of the
host
+ // but the path starts with the account name
+ format!("/blob{}", u.path())
+ };
+
+ (
+ signed_resource,
+ signed_permissions,
+ signed_start,
+ signed_expiry,
+ canonicalized_resource,
+ )
+}
+
+fn string_to_sign_service_sas(
+ u: &Url,
+ method: &Method,
+ account: &str,
+ start: &DateTime<Utc>,
+ end: &DateTime<Utc>,
+) -> (String, HashMap<&'static str, String>) {
+ let (signed_resource, signed_permissions, signed_start, signed_expiry,
canonicalized_resource) =
+ string_to_sign_sas(u, method, account, start, end);
+
+ //
https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#version-2020-12-06-and-later
Review Comment:
Perhaps we could move this to a doc comments on the method, to make it
easier to find
##########
object_store/src/azure/client.rs:
##########
@@ -324,6 +333,45 @@ impl AzureClient {
Ok(())
}
+ /// Make a Get User Delegation Key request
+ ///
<https://docs.microsoft.com/en-us/rest/api/storageservices/get-user-delegation-key>
+ pub async fn get_user_delegation_key(
Review Comment:
Note that AzureClient is not public, and so neither is this method. Perhaps
we could expose it on AzureAuthorizer?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]