Greater support for generating an account or user-delegated SAS. Adds S3 generics/methods for `get_account_sas`, `get_user_delegation_key`, `get_user_delegation_sas`, `revoke_user_delegation_keys`. Closes #26
This commit is contained in:
Hong Ooi 2020-04-09 21:43:07 +10:00 коммит произвёл GitHub
Родитель ee7f935480
Коммит b2e209c39f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 679 добавлений и 83 удалений

Просмотреть файл

@ -22,6 +22,7 @@ Imports:
AzureRMR (>= 2.3.0)
Suggests:
knitr,
rmarkdown,
jsonlite,
testthat,
processx

Просмотреть файл

@ -44,12 +44,20 @@ S3method(delete_storage_file,blob_container)
S3method(delete_storage_file,file_share)
S3method(file_share,character)
S3method(file_share,file_endpoint)
S3method(get_account_sas,az_storage)
S3method(get_account_sas,default)
S3method(get_account_sas,storage_endpoint)
S3method(get_storage_metadata,adls_filesystem)
S3method(get_storage_metadata,blob_container)
S3method(get_storage_metadata,file_share)
S3method(get_storage_properties,adls_filesystem)
S3method(get_storage_properties,blob_container)
S3method(get_storage_properties,file_share)
S3method(get_user_delegation_key,az_resource)
S3method(get_user_delegation_key,blob_endpoint)
S3method(get_user_delegation_sas,az_storage)
S3method(get_user_delegation_sas,blob_endpoint)
S3method(get_user_delegation_sas,default)
S3method(list_adls_filesystems,adls_endpoint)
S3method(list_adls_filesystems,character)
S3method(list_blob_containers,blob_endpoint)
@ -69,6 +77,7 @@ S3method(print,adls_filesystem)
S3method(print,blob_container)
S3method(print,file_share)
S3method(print,storage_endpoint)
S3method(revoke_user_delegation_keys,az_storage)
S3method(set_storage_metadata,adls_filesystem)
S3method(set_storage_metadata,blob_container)
S3method(set_storage_metadata,file_share)
@ -131,10 +140,13 @@ export(download_blob)
export(download_from_url)
export(file_endpoint)
export(file_share)
export(get_account_sas)
export(get_adls_file_acl)
export(get_adls_file_status)
export(get_storage_metadata)
export(get_storage_properties)
export(get_user_delegation_key)
export(get_user_delegation_sas)
export(list_adls_files)
export(list_adls_filesystems)
export(list_azure_files)
@ -153,6 +165,7 @@ export(multiupload_azure_file)
export(multiupload_blob)
export(release_lease)
export(renew_lease)
export(revoke_user_delegation_keys)
export(set_storage_metadata)
export(sign_request)
export(storage_container)

Просмотреть файл

@ -4,6 +4,14 @@
- Basic support for the new file storage permissions API. When uploading files, they will be created with default filesystem parameters: "inherit" permissions, "now" creation/modified datetimes, and unset attributes. (This has no impact on blob and ADLSgen2.)
- Remove a redundant API call to set the Content-Type after a blob or file storage upload.
- `list_storage_containers` and related methods will now check for a continuation marker to avoid returning prematurely (thanks to @StatKalli for reporting and providing a fix).
- Extended support for generating an account SAS:
- Add `get_account_sas` S3 generic, with methods for `az_storage` resource objects and client endpoints. The `az_storage$get_account_sas` R6 method now simply calls the S3 method.
- Generating the SAS now uses internal R code, rather than making an API call. The resulting SAS should also work with azcopy.
- Add support for generating a user delegation SAS. Note that _using_ a user delegation SAS has always worked.
- New S3 generics `get_user_delegation_key` and `get_user_delegation_sas`, with methods for `az_storage` objects and blob endpoints. Similar R6 methods added for `az_storage` objects.
- New `revoke_user_delegation_keys` generic and methods to invalidate all user delegation keys for a storage account (and all SAS's generated with those keys).
- See `?sas` for more information.
- The `silent` argument for `call_azcopy` now uses the `azure_storage_azcopy_silent` system option for its default value, falling back to FALSE if this is unset.
- Bug fixes for internal functions.
# AzureStor 3.1.1

Просмотреть файл

@ -7,7 +7,9 @@
#' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class:
#' - `new(...)`: Initialize a new storage object. See 'Initialization'.
#' - `list_keys()`: Return the access keys for this account.
#' - `get_account_sas(...)`: Return an account shared access signature (SAS). See 'Shared access signatures' for more details.
#' - `get_account_sas(...)`: Return an account shared access signature (SAS). See 'Shared access signatures' below.
#' - `get_user_delegation_key(...)`: Returns a key that can be used to construct a user delegation SAS.
#' - `revoke_user_delegation_keys()`: Revokes all user delegation keys for the account. This also renders all SAS's obtained via such keys invalid.
#' - `get_blob_endpoint(key, sas)`: Return the account's blob storage endpoint, along with an access key and/or a SAS. See 'Endpoints' for more details
#' - `get_file_endpoint(key, sas)`: Return the account's file storage endpoint.
#' - `regen_key(key)`: Regenerates (creates a new value for) an access key. The argument `key` can be 1 or 2.
@ -18,15 +20,27 @@
#' @section Shared access signatures:
#' The simplest way for a user to access files and data in a storage account is to give them the account's access key. This gives them full control of the account, and so may be a security risk. An alternative is to provide the user with a _shared access signature_ (SAS), which limits access to specific resources and only for a set length of time.
#'
#' To create an account SAS, call the `get_account_sas()` method with the following arguments:
#' - `start`: The starting access date/time, as a `Date` or `POSIXct` value. Defaults to the current time.
#' - `expiry`: The ending access date/time, as a `Date` or `POSIXct` value. Defaults to 8 hours after the start time.
#' - `services`: Which services to allow access to. A string containing a combination of the letters `b`, `f`, `q`, `t` for blob, file, queue and table access. Defaults to `bfqt`.
#' - `permissions`: Which permissions to grant. A string containing a combination of the letters `r` (read), `w` (write), `d` (delete), `l` (list), `a` (add), `c` (create), `u` (update) , `p` (process). Defaults to `r`.
#' - `resource_types`: Which levels of the resource type hierarchy to allow access to. A string containing a combination of the letters `s` (service), `c` (container), `o` (object). Defaults to `sco`.
#' - ip: An IP address or range to grant access to.
#' - `protocol`: Which protocol to allow, either `"http"`, `"http,https"` or `"https"`. Defaults to NULL, which is the same as `"http,https"`.
#' - `key`: the access key used to sign (authorise) the SAS.
#' AzureStor supports two kinds of SAS: account and user delegation, with the latter applying only to blob and ADLS2 storage. To create an account SAS, call the `get_account_sas()` method. This has the following signature:
#'
#' ```
#' get_account_sas(key=self$list_keys()[1], start=NULL, expiry=NULL, services="bqtf", permissions="rl",
#' resource_types="sco", ip=NULL, protocol=NULL)
#' ```
#'
#' To create a user delegation SAS, you must first create a user delegation _key_. This takes the place of the account's access key in generating the SAS. The `get_user_delegation_key()` method has the following signature:
#'
#' ```
#' get_user_delegation_key(token=self$token, key_start=NULL, key_expiry=NULL)
#' ```
#'
#' Once you have a user delegation key, you can use it to obtain a user delegation sas. The `get_user_delegation_sas()` method has the following signature:
#'
#' ```
#' get_user_delegation_sas(key, resource, start=NULL, expiry=NULL, permissions="rl",
#' resource_types="c", ip=NULL, protocol=NULL, snapshot_time=NULL)
#' ```
#'
#' See the [Shared access signatures][sas] page for more information about SAS.
#'
#' @section Endpoints:
#' The client-side interaction with a storage account is via an _endpoint_. A storage account can have several endpoints, one for each type of storage supported: blob, file, queue and table.
@ -41,7 +55,9 @@
#' [blob_endpoint], [file_endpoint],
#' [create_storage_account], [get_storage_account], [delete_storage_account], [Date], [POSIXt],
#' [Azure Storage Provider API reference](https://docs.microsoft.com/en-us/rest/api/storagerp/),
#' [Azure Storage Services API reference](https://docs.microsoft.com/en-us/rest/api/storageservices/)
#' [Azure Storage Services API reference](https://docs.microsoft.com/en-us/rest/api/storageservices/),
#' [Create an account SAS](https://docs.microsoft.com/en-us/rest/api/storageservices/create-account-sas),
#' [Create a user delegation SAS](https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas)
#'
#' @examples
#' \dontrun{
@ -55,10 +71,6 @@
#' # regenerate a key
#' stor$regen_key(1)
#'
#' # generate a shared access signature for blob storage, expiring in 7 days time
#' today <- Sys.time()
#' stor$get_account_sas(expiry=today + 7*24*60*60, services="b", permissions="rw")
#'
#' # storage endpoints
#' stor$get_blob_endpoint()
#' stor$get_file_endpoint()
@ -75,28 +87,29 @@ public=list(
sapply(keys, `[[`, "value")
},
get_account_sas=function(start=NULL, expiry=NULL, services="bqtf", permissions="r",
resource_types="sco", ip=NULL, protocol=NULL, key=NULL)
get_account_sas=function(key=self$list_keys()[1], start=NULL, expiry=NULL, services="bqtf", permissions="rl",
resource_types="sco", ip=NULL, protocol=NULL)
{
dates <- private$set_sas_dates(start, expiry)
parms <- list(keyToSign=key,
signedExpiry=dates$expiry, signedIp=ip, signedPermission=permissions, signedProtocol=protocol,
signedResourceTypes=resource_types, signedServices=services, signedStart=dates$start)
self$do_operation("listAccountSas", body=parms, encode="json", http_verb="POST")$accountSasToken
get_account_sas(self, key=key, start=start, expiry=expiry, services=services, permissions=permissions,
resource_types=resource_types, ip=ip, protocol=protocol)
},
# hide for now
#get_service_sas=function(start=NULL, expiry=NULL, path=NULL, service=NULL, permissions="r",
#ip=NULL, protocol=NULL, key=NULL)
#{
#dates <- private$set_sas_dates(start, expiry)
#parms <- list(keyToSign=key, canonicalizedResource=path,
#signedExpiry=dates$expiry, signedIp=ip, signedPermission=permissions, signedProtocol=protocol,
#signedResource=service, signedStart=dates$start)
get_user_delegation_key=function(token=self$token, key_start=NULL, key_expiry=NULL)
{
get_user_delegation_key(self, token=token, key_start=key_start, key_expiry=key_expiry)
},
#self$do_operation("listServiceSas", body=parms, encode="json", http_verb="POST")$serviceSasToken
#},
revoke_user_delegation_keys=function()
{
revoke_user_delegation_keys(self)
},
get_user_delegation_sas=function(key, resource, start=NULL, expiry=NULL, permissions="rl",
resource_types="c", ip=NULL, protocol=NULL, snapshot_time=NULL)
{
get_user_delegation_sas(self, key=key, resource=resource, start=start, expiry=expiry, permissions=permissions,
resource_types=resource_types, ip=ip, protocol=protocol, snapshot_time=snapshot_time)
},
get_blob_endpoint=function(key=self$list_keys()[1], sas=NULL, token=NULL)
{
@ -140,27 +153,4 @@ public=list(
cat(AzureRMR::format_public_methods(self))
invisible(NULL)
}
),
private=list(
set_sas_dates=function(start, expiry)
{
if(is.null(start))
start <- Sys.time()
else if(!inherits(start, "POSIXt"))
start <- as.POSIXct(start, origin="1970-01-01")
if(is.null(expiry)) # by default, 8 hours after start
expiry <- start + 8 * 60 * 60
else if(!inherits(expiry, "POSIXt"))
expiry <- as.POSIXct(expiry, origin="1970-01-01")
if(inherits(start, c("POSIXt", "Date")))
start <- strftime(start, "%Y-%m-%dT%H:%M:%SZ", tz="UTC")
if(inherits(expiry, c("POSIXt", "Date")))
expiry <- strftime(expiry, "%Y-%m-%dT%H:%M:%SZ", tz="UTC")
list(start=start, expiry=expiry)
}
))

Просмотреть файл

@ -2,7 +2,7 @@
#'
#' @param ... Arguments to pass to AzCopy on the commandline. If no arguments are supplied, a help screen is printed.
#' @param env A named character vector of environment variables to set for AzCopy.
#' @param silent Whether to print the output from AzCopy to the screen; also sets whether an error return code from AzCopy will be propagated to an R error.
#' @param silent Whether to print the output from AzCopy to the screen; also sets whether an error return code from AzCopy will be propagated to an R error. Defaults to the value of the `azure_storage_azcopy_silent` option, or FALSE if this is unset.
#'
#' @details
#' AzureStor has the ability to use the Microsoft AzCopy commandline utility to transfer files. To enable this, ensure the processx package is installed and set the argument `use_azcopy=TRUE` in any call to an upload or download function; AzureStor will then call AzCopy to perform the file transfer rather than relying on its own code. You can also call AzCopy directly with the `call_azcopy` function.
@ -45,8 +45,9 @@
#' @aliases azcopy
#' @rdname azcopy
#' @export
call_azcopy <- function(..., env=NULL, silent=FALSE)
call_azcopy <- function(..., env=NULL, silent=getOption("azure_storage_azcopy_silent", FALSE))
{
silent <- as.logical(silent)
args <- as.character(unlist(list(...)))
invisible(processx::run(get_azcopy_path(), args, env=env, echo_cmd=!silent, echo=!silent, error_on_status=!silent))
}

290
R/sas.R Normal file
Просмотреть файл

@ -0,0 +1,290 @@
#' Generate shared access signatures
#'
#' The simplest way for a user to access files and data in a storage account is to give them the account's access key. This gives them full control of the account, and so may be a security risk. An alternative is to provide the user with a _shared access signature_ (SAS), which limits access to specific resources and only for a set length of time. AzureStor supports generating two kinds of SAS: account and user delegation, with the latter applying only to blob and ADLS2 storage.
#'
#' Listed here are S3 generics and methods to obtain a SAS for accessing storage; in addition, the [`az_storage`] resource class has R6 methods for `get_account_sas`, `get_user_delegation_key` and `revoke_user_delegation_keys` which simply call the corresponding S3 method.
#'
#' Note that you don't need to worry about these methods if you have been _given_ a SAS, and only want to use it to access a storage account.
#'
#' @param account An object representing a storage account. Depending on the generic, this can be one of the following: an Azure resource object (of class `az_storage`); a client storage endpoint (of class `storage_endpoint`); a _blob_ storage endpoint (of class `blob_endpoint`); or a string with the name of the account.
#' @param key For `get_account_sas`, the _account_ key, which controls full access to the storage account. For `get_user_delegation_sas`, a _user delegation_ key, as obtained from `get_user_delegation_key`.
#' @param token For `get_user_delegation_key`, an AAD token from which to obtain user details. The token must have `https://storage.azure.com` as its audience.
#' @param resource For `get_user_delegation_sas`, the resource for which the SAS is valid. This can be either the name of a blob container, or a blob. If the latter, it should include the container as well (`containername/blobname`).
#' @param start,expiry The start and end dates for the account or user delegation SAS. These should be `Date` or `POSIXct` values, or strings coercible to such. If not supplied, the default is to generate start and expiry values for a period of 8 hours, starting from the current time.
#' @param key_start,key_expiry For `get_user_delegation_key`, the start and end dates for the user delegation key.
#' @param services For `get_account_sas`, the storage service(s) for which the SAS is valid. Defaults to `bqtf`, meaning blob (including ADLS2), queue, table and file storage.
#' @param permissions For `get_account_sas` and `get_user_delegation_sas`, the permissions that the SAS grants. The default `rl` (read and list) essentially means read-only access.
#' @param resource_types The resource types for which the SAS is valid. For `get_account_sas` the default is `sco` meaning service, container and object. For `get_user_delegation_sas` the default is `c` meaning container-level access (including blobs within the container).
#' @param ip The IP address(es) or IP address range(s) for which the SAS is valid. The default is not to restrict access by IP.
#' @param protocol The protocol required to use the SAS. Possible values are `https` meaning HTTPS-only, or `https,http` meaning HTTP is also allowed. Note that the storage account itself may require HTTPS, regardless of what the SAS allows.
#' @param snapshot_time For `get_user_delegation_sas`, the blob snapshot for which the SAS is valid. Only required if `resource_types="bs"`.
#' @param auth_api_version The storage API version to use for authenticating.
#' @param ... Arguments passed to lower-level functions.
#'
#' @details
#' An **account SAS** is secured with the storage account key. An account SAS delegates access to resources in one or more of the storage services. All of the operations available via a user delegation SAS are also available via an account SAS. You can also delegate access to read, write, and delete operations on blob containers, tables, queues, and file shares.
#'
#' A **user delegation SAS** is a SAS secured with Azure AD credentials. It's recommended that you use Azure AD credentials when possible as a security best practice, rather than using the account key, which can be more easily compromised. When your application design requires shared access signatures, use Azure AD credentials to create a user delegation SAS for superior security.
#'
#' Every SAS is signed with a key. To create a user delegation SAS, you must first request a **user delegation key**, which is then used to sign the SAS. The user delegation key is analogous to the account key used to sign a service SAS or an account SAS, except that it relies on your Azure AD credentials. To request the user delegation key, call `get_user_delegation_key`. With the user delegation key, you can then create the SAS with `get_user_delegation_sas`.
#'
#' See the examples and Microsoft Docs pages below for how to specify arguments like the services, permissions, and resource types. Also, while not explicitly mentioned in the documentation, ADLSgen2 storage can use any SAS that is valid for blob storage.
#' @seealso
#' [blob_endpoint], [file_endpoint],
#' [Date], [POSIXt],
#' [Azure Storage Provider API reference](https://docs.microsoft.com/en-us/rest/api/storagerp/),
#' [Azure Storage Services API reference](https://docs.microsoft.com/en-us/rest/api/storageservices/),
#' [Create an account SAS](https://docs.microsoft.com/en-us/rest/api/storageservices/create-account-sas),
#' [Create a user delegation SAS](https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas)
#'
#' @examples
#' # account SAS valid for 7 days
#' get_account_sas("mystorage", "access_key", start=Sys.Date(), expiry=Sys.Date() + 7)
#'
#' # SAS with read/write/create/delete permissions
#' get_account_sas("mystorage", "access_key", permissions="rwcd")
#'
#' # SAS limited to blob (+ADLS2) and file storage
#' get_account_sas("mystorage", "access_key", services="bf")
#'
#' # SAS for file storage, allows access to files only (not shares)
#' get_account_sas("mystorage", "access_key", services="f", resource_types="o")
#'
#' # getting the key from an endpoint object
#' endp <- storage_endpoint("https://mystorage.blob.core.windows.net", key="access_key")
#' get_account_sas(endp, permissions="rwcd")
#'
#' \dontrun{
#'
#' # user delegation key valid for 24 hours
#' token <- AzureRMR::get_azure_token("https://storage.azure.com", "mytenant", "app_id")
#' endp <- storage_endpoint("https://mystorage.blob.core.windows.net", token=token)
#' userkey <- get_user_delegation_key(endp, start=Sys.Date(), expiry=Sys.Date() + 1)
#'
#' # user delegation SAS for a container
#' get_user_delegation_sas(endp, userkey, resource="mycontainer")
#'
#' # user delegation SAS for a specific file, read/write/create/delete access
#' # (order of permissions is important!)
#' get_user_delegation_sas(endp, userkey, resource="mycontainer/myfile",
#' resource_types="b", permissions="rcwd")
#'
#' }
#' @aliases sas shared-access-signature shared_access_signature
#' @rdname sas
#' @export
get_account_sas <- function(account, ...)
{
UseMethod("get_account_sas")
}
#' @rdname sas
#' @export
get_account_sas.az_storage <- function(account, key=account$list_keys()[1], ...)
{
get_account_sas(account$name, key, ...)
}
#' @rdname sas
#' @export
get_account_sas.storage_endpoint <- function(account, key=account$key, ...)
{
if(is.null(key))
stop("Must have access key to generate SAS", call.=FALSE)
acctname <- sub("\\..*", "", httr::parse_url(account$url)$hostname)
get_account_sas(acctname, key=key, ...)
}
#' @rdname sas
#' @export
get_account_sas.default <- function(account, key, start=NULL, expiry=NULL, services="bqtf", permissions="rl",
resource_types="sco", ip=NULL, protocol=NULL,
auth_api_version=getOption("azure_storage_api_version"), ...)
{
dates <- make_sas_dates(start, expiry)
sig_str <- paste(
account,
permissions,
services,
resource_types,
dates$start,
dates$expiry,
ip,
protocol,
auth_api_version,
"", # to ensure string always ends with newline
sep="\n"
)
sig <- sign_sha256(sig_str, key)
parts <- list(
sv=auth_api_version,
ss=services,
srt=resource_types,
sp=permissions,
st=dates$start,
se=dates$expiry,
sip=ip,
spr=protocol,
sig=sig
)
parts <- parts[!sapply(parts, is_empty)]
parts <- sapply(parts, utils::URLencode, reserved=TRUE)
paste(names(parts), parts, sep="=", collapse="&")
}
#' @rdname sas
#' @export
get_user_delegation_key <- function(account, ...)
{
UseMethod("get_user_delegation_key")
}
#' @rdname sas
#' @export
get_user_delegation_key.az_resource <- function(account, token=account$token, ...)
{
endp <- account$get_blob_endpoint(key=NULL, token=token)
get_user_delegation_key(endp, token=endp$token, ...)
}
#' @rdname sas
#' @export
get_user_delegation_key.blob_endpoint <- function(account, token=account$token, key_start, key_expiry, ...)
{
if(is.null(token))
stop("Must have AAD token to get user delegation key", call.=FALSE)
account$key <- account$sas <- NULL
account$token <- token
dates <- make_sas_dates(key_start, key_expiry)
body <- list(KeyInfo=list(
Start=list(dates$start),
Expiry=list(dates$expiry)
))
res <- call_storage_endpoint(account, "", options=list(restype="service", comp="userdelegationkey"),
body=render_xml(body), http_verb="POST")
res <- unlist(res, recursive=FALSE)
class(res) <- "user_delegation_key"
res
}
#' @rdname sas
#' @export
revoke_user_delegation_keys <- function(account)
{
UseMethod("revoke_user_delegation_keys")
}
#' @rdname sas
#' @export
revoke_user_delegation_keys.az_storage <- function(account)
{
account$do_operation("revokeUserDelegationKeys", http_verb="POST")
invisible(NULL)
}
#' @rdname sas
#' @export
get_user_delegation_sas <- function(account, ...)
{
UseMethod("get_user_delegation_sas")
}
#' @rdname sas
#' @export
get_user_delegation_sas.az_storage <- function(account, key, ...)
{
get_user_delegation_sas(account$name, key, ...)
}
#' @rdname sas
#' @export
get_user_delegation_sas.blob_endpoint <- function(account, key, ...)
{
acctname <- sub("\\..*", "", httr::parse_url(account$url)$hostname)
get_user_delegation_sas(acctname, key, ...)
}
#' @rdname sas
#' @export
get_user_delegation_sas.default <- function(account, key, resource, start=NULL, expiry=NULL, permissions="rl",
resource_types="c", ip=NULL, protocol=NULL, snapshot_time=NULL,
auth_api_version=getOption("azure_storage_api_version"), ...)
{
stopifnot(inherits(key, "user_delegation_key"))
dates <- make_sas_dates(start, expiry)
resource <- file.path("/blob", account, resource)
sig_str <- paste(
permissions,
dates$start,
dates$expiry,
resource,
key$SignedOid,
key$SignedTid,
key$SignedStart,
key$SignedExpiry,
key$SignedService,
key$SignedVersion,
ip,
protocol,
auth_api_version,
resource_types,
snapshot_time,
"",
"",
"",
"",
"Application/octet-stream",
sep="\n"
)
sig <- sign_sha256(sig_str, key$Value)
parts <- list(
sv=auth_api_version,
sr=resource_types,
st=dates$start,
se=dates$expiry,
sp=permissions,
sip=ip,
spr=protocol,
skoid=key$SignedOid,
sktid=key$SignedTid,
skt=key$SignedStart,
ske=key$SignedExpiry,
sks=key$SignedService,
skv=key$SignedVersion,
rsct="Application/octet-stream",
sig=sig
)
parts <- parts[!sapply(parts, is_empty)]
parts <- sapply(parts, utils::URLencode, reserved=TRUE)
paste(names(parts), parts, sep="=", collapse="&")
}
make_sas_dates <- function(start=NULL, expiry=NULL)
{
if(is.null(start))
start <- Sys.time()
else if(!inherits(start, "POSIXt"))
start <- as.POSIXct(start, origin="1970-01-01")
if(is.null(expiry)) # by default, 8 hours after start
expiry <- start + 8 * 60 * 60
else if(!inherits(expiry, "POSIXt"))
expiry <- as.POSIXct(expiry, origin="1970-01-01")
if(inherits(start, c("POSIXt", "Date")))
start <- strftime(start, "%Y-%m-%dT%H:%M:%SZ", tz="UTC")
if(inherits(expiry, c("POSIXt", "Date")))
expiry <- strftime(expiry, "%Y-%m-%dT%H:%M:%SZ", tz="UTC")
list(start=start, expiry=expiry)
}

Просмотреть файл

@ -57,8 +57,7 @@ make_signature <- function(key, verb, acct_name, resource, options, headers)
options, sep="\n")
sig <- sub("\n$", "", sig) # undocumented, found thanks to Tsuyoshi Matsuzaki's blog post
hash <- openssl::sha256(charToRaw(sig), openssl::base64_decode(key))
paste0("SharedKey ", acct_name, ":", openssl::base64_encode(hash))
paste0("SharedKey ", acct_name, ":", sign_sha256(sig, key))
}

Просмотреть файл

@ -244,3 +244,19 @@ delete_confirmed <- function(confirm, name, type)
isTRUE(ok)
}
render_xml <- function(lst)
{
xml <- xml2::as_xml_document(lst)
rc <- rawConnection(raw(0), "w")
on.exit(close(rc))
xml2::write_xml(xml, rc)
rawToChar(rawConnectionValue(rc))
}
sign_sha256 <- function(string, key)
{
openssl::base64_encode(openssl::sha256(charToRaw(string), openssl::base64_decode(key)))
}

Просмотреть файл

@ -13,7 +13,9 @@ The following methods are available, in addition to those provided by the \link[
\itemize{
\item \code{new(...)}: Initialize a new storage object. See 'Initialization'.
\item \code{list_keys()}: Return the access keys for this account.
\item \code{get_account_sas(...)}: Return an account shared access signature (SAS). See 'Shared access signatures' for more details.
\item \code{get_account_sas(...)}: Return an account shared access signature (SAS). See 'Shared access signatures' below.
\item \code{get_user_delegation_key(...)}: Returns a key that can be used to construct a user delegation SAS.
\item \code{revoke_user_delegation_keys()}: Revokes all user delegation keys for the account. This also renders all SAS's obtained via such keys invalid.
\item \code{get_blob_endpoint(key, sas)}: Return the account's blob storage endpoint, along with an access key and/or a SAS. See 'Endpoints' for more details
\item \code{get_file_endpoint(key, sas)}: Return the account's file storage endpoint.
\item \code{regen_key(key)}: Regenerates (creates a new value for) an access key. The argument \code{key} can be 1 or 2.
@ -29,17 +31,18 @@ Initializing a new object of this class can either retrieve an existing storage
The simplest way for a user to access files and data in a storage account is to give them the account's access key. This gives them full control of the account, and so may be a security risk. An alternative is to provide the user with a \emph{shared access signature} (SAS), which limits access to specific resources and only for a set length of time.
To create an account SAS, call the \code{get_account_sas()} method with the following arguments:
\itemize{
\item \code{start}: The starting access date/time, as a \code{Date} or \code{POSIXct} value. Defaults to the current time.
\item \code{expiry}: The ending access date/time, as a \code{Date} or \code{POSIXct} value. Defaults to 8 hours after the start time.
\item \code{services}: Which services to allow access to. A string containing a combination of the letters \code{b}, \code{f}, \code{q}, \code{t} for blob, file, queue and table access. Defaults to \code{bfqt}.
\item \code{permissions}: Which permissions to grant. A string containing a combination of the letters \code{r} (read), \code{w} (write), \code{d} (delete), \code{l} (list), \code{a} (add), \code{c} (create), \code{u} (update) , \code{p} (process). Defaults to \code{r}.
\item \code{resource_types}: Which levels of the resource type hierarchy to allow access to. A string containing a combination of the letters \code{s} (service), \code{c} (container), \code{o} (object). Defaults to \code{sco}.
\item ip: An IP address or range to grant access to.
\item \code{protocol}: Which protocol to allow, either \code{"http"}, \code{"http,https"} or \code{"https"}. Defaults to NULL, which is the same as \code{"http,https"}.
\item \code{key}: the access key used to sign (authorise) the SAS.
AzureStor supports two kinds of SAS: account and user delegation, with the latter applying only to blob and ADLS2 storage. To create an account SAS, call the \code{get_account_sas()} method. This has the following signature:\preformatted{get_account_sas(key=self$list_keys()[1], start=NULL, expiry=NULL, services="bqtf", permissions="rl",
resource_types="sco", ip=NULL, protocol=NULL)
}
To create a user delegation SAS, you must first create a user delegation \emph{key}. This takes the place of the account's access key in generating the SAS. The \code{get_user_delegation_key()} method has the following signature:\preformatted{get_user_delegation_key(token=self$token, key_start=NULL, key_expiry=NULL)
}
Once you have a user delegation key, you can use it to obtain a user delegation sas. The \code{get_user_delegation_sas()} method has the following signature:\preformatted{get_user_delegation_sas(key, resource, start=NULL, expiry=NULL, permissions="rl",
resource_types="c", ip=NULL, protocol=NULL, snapshot_time=NULL)
}
See the \link[=sas]{Shared access signatures} page for more information about SAS.
}
\section{Endpoints}{
@ -65,10 +68,6 @@ stor$list_keys()
# regenerate a key
stor$regen_key(1)
# generate a shared access signature for blob storage, expiring in 7 days time
today <- Sys.time()
stor$get_account_sas(expiry=today + 7*24*60*60, services="b", permissions="rw")
# storage endpoints
stor$get_blob_endpoint()
stor$get_file_endpoint()
@ -79,5 +78,7 @@ stor$get_file_endpoint()
\link{blob_endpoint}, \link{file_endpoint},
\link{create_storage_account}, \link{get_storage_account}, \link{delete_storage_account}, \link{Date}, \link{POSIXt},
\href{https://docs.microsoft.com/en-us/rest/api/storagerp/}{Azure Storage Provider API reference},
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/}{Azure Storage Services API reference}
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/}{Azure Storage Services API reference},
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/create-account-sas}{Create an account SAS},
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas}{Create a user delegation SAS}
}

Просмотреть файл

@ -5,14 +5,15 @@
\alias{azcopy}
\title{Call the azcopy file transfer utility}
\usage{
call_azcopy(..., env = NULL, silent = FALSE)
call_azcopy(..., env = NULL,
silent = getOption("azure_storage_azcopy_silent", FALSE))
}
\arguments{
\item{...}{Arguments to pass to AzCopy on the commandline. If no arguments are supplied, a help screen is printed.}
\item{env}{A named character vector of environment variables to set for AzCopy.}
\item{silent}{Whether to print the output from AzCopy to the screen; also sets whether an error return code from AzCopy will be propagated to an R error.}
\item{silent}{Whether to print the output from AzCopy to the screen; also sets whether an error return code from AzCopy will be propagated to an R error. Defaults to the value of the \code{azure_storage_azcopy_silent} option, or FALSE if this is unset.}
}
\value{
A list, invisibly, with the following components:

142
man/sas.Rd Normal file
Просмотреть файл

@ -0,0 +1,142 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/sas.R
\name{get_account_sas}
\alias{get_account_sas}
\alias{sas}
\alias{shared-access-signature}
\alias{shared_access_signature}
\alias{get_account_sas.az_storage}
\alias{get_account_sas.storage_endpoint}
\alias{get_account_sas.default}
\alias{get_user_delegation_key}
\alias{get_user_delegation_key.az_resource}
\alias{get_user_delegation_key.blob_endpoint}
\alias{revoke_user_delegation_keys}
\alias{revoke_user_delegation_keys.az_storage}
\alias{get_user_delegation_sas}
\alias{get_user_delegation_sas.az_storage}
\alias{get_user_delegation_sas.blob_endpoint}
\alias{get_user_delegation_sas.default}
\title{Generate shared access signatures}
\usage{
get_account_sas(account, ...)
\method{get_account_sas}{az_storage}(account, key = account$list_keys()[1], ...)
\method{get_account_sas}{storage_endpoint}(account, key = account$key, ...)
\method{get_account_sas}{default}(account, key, start = NULL,
expiry = NULL, services = "bqtf", permissions = "rl",
resource_types = "sco", ip = NULL, protocol = NULL,
auth_api_version = getOption("azure_storage_api_version"), ...)
get_user_delegation_key(account, ...)
\method{get_user_delegation_key}{az_resource}(account, token = account$token, ...)
\method{get_user_delegation_key}{blob_endpoint}(account,
token = account$token, key_start, key_expiry, ...)
revoke_user_delegation_keys(account)
\method{revoke_user_delegation_keys}{az_storage}(account)
get_user_delegation_sas(account, ...)
\method{get_user_delegation_sas}{az_storage}(account, key, ...)
\method{get_user_delegation_sas}{blob_endpoint}(account, key, ...)
\method{get_user_delegation_sas}{default}(account, key, resource,
start = NULL, expiry = NULL, permissions = "rl",
resource_types = "c", ip = NULL, protocol = NULL,
snapshot_time = NULL,
auth_api_version = getOption("azure_storage_api_version"), ...)
}
\arguments{
\item{account}{An object representing a storage account. Depending on the generic, this can be one of the following: an Azure resource object (of class \code{az_storage}); a client storage endpoint (of class \code{storage_endpoint}); a \emph{blob} storage endpoint (of class \code{blob_endpoint}); or a string with the name of the account.}
\item{...}{Arguments passed to lower-level functions.}
\item{key}{For \code{get_account_sas}, the \emph{account} key, which controls full access to the storage account. For \code{get_user_delegation_sas}, a \emph{user delegation} key, as obtained from \code{get_user_delegation_key}.}
\item{start, expiry}{The start and end dates for the account or user delegation SAS. These should be \code{Date} or \code{POSIXct} values, or strings coercible to such. If not supplied, the default is to generate start and expiry values for a period of 8 hours, starting from the current time.}
\item{services}{For \code{get_account_sas}, the storage service(s) for which the SAS is valid. Defaults to \code{bqtf}, meaning blob (including ADLS2), queue, table and file storage.}
\item{permissions}{For \code{get_account_sas} and \code{get_user_delegation_sas}, the permissions that the SAS grants. The default \code{rl} (read and list) essentially means read-only access.}
\item{resource_types}{The resource types for which the SAS is valid. For \code{get_account_sas} the default is \code{sco} meaning service, container and object. For \code{get_user_delegation_sas} the default is \code{c} meaning container-level access (including blobs within the container).}
\item{ip}{The IP address(es) or IP address range(s) for which the SAS is valid. The default is not to restrict access by IP.}
\item{protocol}{The protocol required to use the SAS. Possible values are \code{https} meaning HTTPS-only, or \verb{https,http} meaning HTTP is also allowed. Note that the storage account itself may require HTTPS, regardless of what the SAS allows.}
\item{auth_api_version}{The storage API version to use for authenticating.}
\item{token}{For \code{get_user_delegation_key}, an AAD token from which to obtain user details. The token must have \verb{https://storage.azure.com} as its audience.}
\item{key_start, key_expiry}{For \code{get_user_delegation_key}, the start and end dates for the user delegation key.}
\item{resource}{For \code{get_user_delegation_sas}, the resource for which the SAS is valid. This can be either the name of a blob container, or a blob. If the latter, it should include the container as well (\code{containername/blobname}).}
\item{snapshot_time}{For \code{get_user_delegation_sas}, the blob snapshot for which the SAS is valid. Only required if \code{resource_types="bs"}.}
}
\description{
The simplest way for a user to access files and data in a storage account is to give them the account's access key. This gives them full control of the account, and so may be a security risk. An alternative is to provide the user with a \emph{shared access signature} (SAS), which limits access to specific resources and only for a set length of time. AzureStor supports generating two kinds of SAS: account and user delegation, with the latter applying only to blob and ADLS2 storage.
}
\details{
Listed here are S3 generics and methods to obtain a SAS for accessing storage; in addition, the \code{\link{az_storage}} resource class has R6 methods for \code{get_account_sas}, \code{get_user_delegation_key} and \code{revoke_user_delegation_keys} which simply call the corresponding S3 method.
Note that you don't need to worry about these methods if you have been \emph{given} a SAS, and only want to use it to access a storage account.
An \strong{account SAS} is secured with the storage account key. An account SAS delegates access to resources in one or more of the storage services. All of the operations available via a user delegation SAS are also available via an account SAS. You can also delegate access to read, write, and delete operations on blob containers, tables, queues, and file shares.
A \strong{user delegation SAS} is a SAS secured with Azure AD credentials. It's recommended that you use Azure AD credentials when possible as a security best practice, rather than using the account key, which can be more easily compromised. When your application design requires shared access signatures, use Azure AD credentials to create a user delegation SAS for superior security.
Every SAS is signed with a key. To create a user delegation SAS, you must first request a \strong{user delegation key}, which is then used to sign the SAS. The user delegation key is analogous to the account key used to sign a service SAS or an account SAS, except that it relies on your Azure AD credentials. To request the user delegation key, call \code{get_user_delegation_key}. With the user delegation key, you can then create the SAS with \code{get_user_delegation_sas}.
See the examples and Microsoft Docs pages below for how to specify arguments like the services, permissions, and resource types. Also, while not explicitly mentioned in the documentation, ADLSgen2 storage can use any SAS that is valid for blob storage.
}
\examples{
# account SAS valid for 7 days
get_account_sas("mystorage", "access_key", start=Sys.Date(), expiry=Sys.Date() + 7)
# SAS with read/write/create/delete permissions
get_account_sas("mystorage", "access_key", permissions="rwcd")
# SAS limited to blob (+ADLS2) and file storage
get_account_sas("mystorage", "access_key", services="bf")
# SAS for file storage, allows access to files only (not shares)
get_account_sas("mystorage", "access_key", services="f", resource_types="o")
# getting the key from an endpoint object
endp <- storage_endpoint("https://mystorage.blob.core.windows.net", key="access_key")
get_account_sas(endp, permissions="rwcd")
\dontrun{
# user delegation key valid for 24 hours
token <- AzureRMR::get_azure_token("https://storage.azure.com", "mytenant", "app_id")
endp <- storage_endpoint("https://mystorage.blob.core.windows.net", token=token)
userkey <- get_user_delegation_key(endp, start=Sys.Date(), expiry=Sys.Date() + 1)
# user delegation SAS for a container
get_user_delegation_sas(endp, userkey, resource="mycontainer")
# user delegation SAS for a specific file, read/write/create/delete access
# (order of permissions is important!)
get_user_delegation_sas(endp, userkey, resource="mycontainer/myfile",
resource_types="b", permissions="rcwd")
}
}
\seealso{
\link{blob_endpoint}, \link{file_endpoint},
\link{Date}, \link{POSIXt},
\href{https://docs.microsoft.com/en-us/rest/api/storagerp/}{Azure Storage Provider API reference},
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/}{Azure Storage Services API reference},
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/create-account-sas}{Create an account SAS},
\href{https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas}{Create a user delegation SAS}
}

4
tests/testthat/setup.R Normal file
Просмотреть файл

@ -0,0 +1,4 @@
make_name <- function(n=20)
{
paste0(sample(letters, n, TRUE), collapse="")
}

Просмотреть файл

@ -1,4 +1,4 @@
context("Blob client interface, extra")
context("Azcopy")
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
app <- Sys.getenv("AZ_TEST_APP_ID")
@ -11,15 +11,20 @@ if(tenant == "" || app == "" || password == "" || subscription == "")
rgname <- Sys.getenv("AZ_TEST_STORAGE_RG")
storname <- Sys.getenv("AZ_TEST_STORAGE_NOHNS")
sas <- Sys.getenv("AZ_TEST_STORAGE_AZCOPY_SAS")
if(rgname == "" || storname == "" || sas == "")
if(rgname == "" || storname == "")
skip("Azcopy client tests skipped: resource names not set")
set_azcopy_path()
if(is.null(.AzureStor$azcopy) || is.na(.AzureStor$azcopy))
skip("Azcopy tests skipped: not detected")
if(Sys.getenv("_R_CHECK_CRAN_INCOMING_") != "")
skip("Azcopy tests skipped: tests being run from devtools::check")
opt_sil <- getOption("azure_storage_azcopy_silent")
options(azure_storage_azcopy_silent="TRUE")
stor <- AzureRMR::az_rm$new(tenant=tenant, app=app, password=password)$
get_subscription(subscription)$
get_resource_group(rgname)$
@ -28,6 +33,7 @@ stor <- AzureRMR::az_rm$new(tenant=tenant, app=app, password=password)$
token_svc <- AzureRMR::get_azure_token("https://storage.azure.com/", tenant=tenant, app=app, password=password)
token_usr <- AzureRMR::get_azure_token("https://storage.azure.com/", tenant=tenant, app=cli_app)
key <- stor$list_keys()[1]
sas <- stor$get_account_sas(permissions="rwdla")
bl_svc <- stor$get_blob_endpoint(key=NULL, sas=NULL, token=token_svc)
bl_usr <- stor$get_blob_endpoint(key=NULL, sas=NULL, token=token_usr)
@ -49,8 +55,8 @@ files_identical <- function(set1, set2)
test_that("call_azcopy works",
{
expect_output(azc1 <- call_azcopy())
expect_output(azc2 <- call_azcopy("help"))
expect_output(azc1 <- call_azcopy(silent=FALSE))
expect_output(azc2 <- call_azcopy("help", silent=FALSE))
expect_identical(substr(azc1$stdout, 1, 200), substr(azc2$stdout, 1, 200))
})
@ -101,6 +107,7 @@ test_that("azcopy works with sas",
teardown(
{
options(azure_storage_azcopy_silent=opt_sil)
conts <- list_blob_containers(bl_svc)
lapply(conts, delete_blob_container, confirm=FALSE)
conts <- list_adls_filesystems(ad_key)

123
tests/testthat/test10_sas.R Normal file
Просмотреть файл

@ -0,0 +1,123 @@
context("Account and user SAS")
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
app <- Sys.getenv("AZ_TEST_APP_ID")
#cliapp <- Sys.getenv("AZ_TEST_NATIVE_APP_ID")
password <- Sys.getenv("AZ_TEST_PASSWORD")
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
if(tenant == "" || app == "" || password == "" || subscription == "")
skip("SAS tests skipped: ARM credentials not set")
rgname <- Sys.getenv("AZ_TEST_STORAGE_RG")
storname <- Sys.getenv("AZ_TEST_STORAGE_HNS")
if(rgname == "" || storname == "")
skip("SAS tests skipped: resource names not set")
sub <- AzureRMR::az_rm$new(tenant=tenant, app=app, password=password)$get_subscription(subscription)
stor <- sub$get_resource_group(rgname)$get_storage_account(storname)
options(azure_storage_progress_bar=FALSE)
dates <- c(Sys.Date() - 1, Sys.Date() + 5)
token <- AzureRMR::get_azure_token("https://storage.azure.com", tenant, app=app, password=password)
test_that("Account SAS works 0",
{
key <- stor$list_keys()[1]
sas <- get_account_sas(storname, key, permissions="rwlc")
bl <- stor$get_blob_endpoint(key=NULL, sas=sas)
expect_silent(list_storage_containers(bl))
expect_silent(cont <- create_storage_container(bl, make_name()))
expect_silent(storage_upload(cont, "../resources/iris.csv"))
})
test_that("Account SAS works 1",
{
sas <- stor$get_account_sas(permissions="rwlc")
expect_type(sas, "character")
bl <- stor$get_blob_endpoint(key=NULL, sas=sas)
expect_silent(list_storage_containers(bl))
expect_silent(cont <- create_storage_container(bl, make_name()))
expect_silent(storage_upload(cont, "../resources/iris.csv"))
})
test_that("Account SAS works 2",
{
bl0 <- stor$get_blob_endpoint(key=NULL, sas=NULL, token=NULL)
expect_error(get_account_sas(bl0, permissions="rwlc"))
bl0 <- stor$get_blob_endpoint()
sas <- get_account_sas(bl0, permissions="rwlc")
bl <- stor$get_blob_endpoint(key=NULL, sas=sas)
expect_silent(list_storage_containers(bl))
expect_silent(cont <- create_storage_container(bl, make_name()))
expect_silent(storage_upload(cont, "../resources/iris.csv"))
})
test_that("User delegation key works 1",
{
ukey <- stor$get_user_delegation_key(token=token, key_start=dates[1], key_expiry=dates[2])
expect_is(ukey, "user_delegation_key")
expect_type(ukey$Value, "character")
})
test_that("User delegation key works 2",
{
bl <- stor$get_blob_endpoint(key=NULL, token=token)
ukey <- get_user_delegation_key(bl, key_start=dates[1], key_expiry=dates[2])
expect_is(ukey, "user_delegation_key")
expect_type(ukey$Value, "character")
})
test_that("User delegation SAS works 1",
{
contname <- make_name()
bl0 <- stor$get_blob_endpoint(key=NULL, token=token)
expect_silent(create_storage_container(bl0, contname))
ukey <- get_user_delegation_key(bl0, key_start=dates[1], key_expiry=dates[2])
usas <- get_user_delegation_sas(bl0, ukey, resource=contname, start=dates[1], expiry=dates[2], permissions="rcwl")
expect_type(usas, "character")
Sys.sleep(30)
bl <- stor$get_blob_endpoint(key=NULL, sas=usas)
cont <- storage_container(bl, contname)
expect_silent(list_storage_files(cont))
expect_silent(storage_upload(cont, "../resources/iris.csv"))
})
test_that("User delegation SAS works 2",
{
contname <- make_name()
bl0 <- stor$get_blob_endpoint(key=NULL, token=token)
expect_silent(create_storage_container(bl0, contname))
ukey <- get_user_delegation_key(bl0, key_start=dates[1], key_expiry=dates[2])
usas <- get_user_delegation_sas(storname, ukey, resource=contname, start=dates[1], expiry=dates[2],
permissions="rcwl")
expect_type(usas, "character")
Sys.sleep(30)
bl <- stor$get_blob_endpoint(key=NULL, sas=usas)
cont <- storage_container(bl, contname)
expect_silent(list_storage_files(cont))
expect_silent(storage_upload(cont, "../resources/iris.csv"))
})
teardown({
stor$revoke_user_delegation_keys()
bl <- stor$get_blob_endpoint()
blconts <- list_storage_containers(bl)
lapply(blconts, delete_storage_container, confirm=FALSE)
})