From 7df2cec656c870e5d449aae8e8708e3a8d6981f0 Mon Sep 17 00:00:00 2001 From: Mikhail Shilkov Date: Fri, 24 May 2019 10:26:39 +0200 Subject: [PATCH 1/4] Re-implement signedBlobReadUrl with a getAccountSAS call --- sdk/nodejs/appservice/zMixins.ts | 33 +-------------------- sdk/nodejs/package.json | 3 +- sdk/nodejs/storage/zMixins.ts | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/sdk/nodejs/appservice/zMixins.ts b/sdk/nodejs/appservice/zMixins.ts index f69cbae516..54672b5c13 100644 --- a/sdk/nodejs/appservice/zMixins.ts +++ b/sdk/nodejs/appservice/zMixins.ts @@ -15,7 +15,6 @@ import * as pulumi from "@pulumi/pulumi"; import * as azurefunctions from "@azure/functions"; -import * as azurestorage from "azure-storage"; import { FunctionAppArgs, FunctionApp } from "./functionApp"; @@ -393,7 +392,7 @@ export class CallbackFunctionApp, E, R extends Result> exte content: assetMap.apply(m => new pulumi.asset.AssetArchive(m)), }, opts); - const codeBlobUrl = signedBlobReadUrl(zipBlob, account, container); + const codeBlobUrl = storageMod.signedBlobReadUrl(zipBlob, account); super(name, { ...args, ...resourceGroupArgs, @@ -449,36 +448,6 @@ function makeSafeStorageContainerName(prefix: string) { return prefix.replace(/[^a-zA-Z0-9-]/g, "").toLowerCase().substr(0, 63 - 8); } -/** @internal */ -export function signedBlobReadUrl( - blob: storageForTypesOnly.Blob | storageForTypesOnly.ZipBlob, - account: storageForTypesOnly.Account, - container: storageForTypesOnly.Container): pulumi.Output { - - // Choose a fixed, far-future expiration date for signed blob URLs. The shared access signature - // (SAS) we generate for the Azure storage blob must remain valid for as long as the Function - // App is deployed, since new instances will download the code on startup. By using a fixed - // date, rather than (e.g.) "today plus ten years", the signing operation is idempotent. - const signatureExpiration = new Date(2100, 1); - - return pulumi.all([account.primaryConnectionString, container.name, blob.name]).apply( - ([connectionString, containerName, blobName]) => { - const blobService = new azurestorage.BlobService(connectionString); - const signature = blobService.generateSharedAccessSignature( - containerName, - blobName, - { - AccessPolicy: { - Expiry: signatureExpiration, - Permissions: azurestorage.BlobUtilities.SharedAccessPermissions.READ, - }, - }, - ); - - return blobService.getUrl(containerName, blobName, signature); - }); -} - interface BaseSubscriptionArgs { resourceGroup?: core.ResourceGroup; resourceGroupName?: pulumi.Input; diff --git a/sdk/nodejs/package.json b/sdk/nodejs/package.json index 1c362a4709..601f00ec3d 100644 --- a/sdk/nodejs/package.json +++ b/sdk/nodejs/package.json @@ -15,8 +15,7 @@ "dependencies": { "@azure/functions": "^1.0.3", "@pulumi/pulumi": "^0.17.12", - "azure-functions-ts-essentials": "^1.3.2", - "azure-storage": "^2.10.3" + "azure-functions-ts-essentials": "^1.3.2" }, "devDependencies": { "@types/node": "^10.0.0", diff --git a/sdk/nodejs/storage/zMixins.ts b/sdk/nodejs/storage/zMixins.ts index a85ca91a18..42f54a1277 100644 --- a/sdk/nodejs/storage/zMixins.ts +++ b/sdk/nodejs/storage/zMixins.ts @@ -14,14 +14,64 @@ import * as pulumi from "@pulumi/pulumi"; +import { Account } from "./account"; +import { Blob } from "./blob"; import { Container } from "./container"; +import { getAccountSAS } from "./getAccountSAS"; import { Queue } from "./queue"; +import { ZipBlob } from "./zipBlob"; import * as appservice from "../appservice"; import * as core from "../core"; import * as storage from "../storage"; import * as util from "../util"; +/** + * Produce a URL with read-only access to a Storage Blob with a Shared Access Signature (SAS). + * @param blob Blob to construct URL for. + * @param account Storage account. + */ +export function signedBlobReadUrl(blob: Blob | ZipBlob, account: Account): pulumi.Output { + + // Choose a fixed, far-future expiration date for signed blob URLs. The shared access signature + // (SAS) we generate for the Azure storage blob must remain valid for as long as the Function + // App is deployed, since new instances will download the code on startup. By using a fixed + // date, rather than (e.g.) "today plus ten years", the signing operation is idempotent. + const signatureExpiration = "2100-01-01"; + + return pulumi.all([account.name, account.primaryConnectionString, blob.storageContainerName, blob.name]).apply( + ([accountName, connectionString, containerName, blobName]) => { + const accountSAS = getAccountSAS({ + connectionString, + start: "2019-01-01", + expiry: signatureExpiration, + services: { + blob: true, + queue: false, + table: false, + file: false, + }, + resourceTypes: { + service: false, + container: false, + object: true, + }, + permissions: { + read: true, + write: false, + delete: false, + list: false, + add: false, + create: false, + update: false, + process: false, + } + }); + + return pulumi.output(accountSAS).apply(sas => `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}${sas.sas}`); + }); +} + interface BlobBindingDefinition extends appservice.BindingDefinition { /** * The name of the property in the context object to bind the actual blob value to. Not really From 89a026762c1d0167fe675ed135a5b659fa6a091e Mon Sep 17 00:00:00 2001 From: Mikhail Shilkov Date: Fri, 24 May 2019 10:56:25 +0200 Subject: [PATCH 2/4] Change log and resources.go --- CHANGELOG.md | 2 ++ resources.go | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e1bfd961..94ad8b8e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 0.18.5 (Unreleased) +- Expose signedBlobReadUrl to produce SAS URLs for Blobs (#258) + ## 0.18.4 (Released 22nd May, 2019) ### Bug Fixes diff --git a/resources.go b/resources.go index 8c05c56b21..640fc0fa2d 100644 --- a/resources.go +++ b/resources.go @@ -998,7 +998,6 @@ func Provider() tfbridge.ProviderInfo { Dependencies: map[string]string{ "@pulumi/pulumi": "^0.17.12", "@azure/functions": "^1.0.3", - "azure-storage": "^2.10.3", "azure-functions-ts-essentials": "^1.3.2", }, Overlay: &tfbridge.OverlayInfo{ From 05b26cc56e7c3a99870af8630e8afb3da71fd614 Mon Sep 17 00:00:00 2001 From: Mikhail Shilkov Date: Fri, 24 May 2019 23:32:17 +0200 Subject: [PATCH 3/4] Use promise directly --- sdk/nodejs/storage/zMixins.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sdk/nodejs/storage/zMixins.ts b/sdk/nodejs/storage/zMixins.ts index 42f54a1277..a13275e0be 100644 --- a/sdk/nodejs/storage/zMixins.ts +++ b/sdk/nodejs/storage/zMixins.ts @@ -40,8 +40,8 @@ export function signedBlobReadUrl(blob: Blob | ZipBlob, account: Account): pulum const signatureExpiration = "2100-01-01"; return pulumi.all([account.name, account.primaryConnectionString, blob.storageContainerName, blob.name]).apply( - ([accountName, connectionString, containerName, blobName]) => { - const accountSAS = getAccountSAS({ + ([accountName, connectionString, containerName, blobName]) => + getAccountSAS({ connectionString, start: "2019-01-01", expiry: signatureExpiration, @@ -66,10 +66,8 @@ export function signedBlobReadUrl(blob: Blob | ZipBlob, account: Account): pulum update: false, process: false, } - }); - - return pulumi.output(accountSAS).apply(sas => `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}${sas.sas}`); - }); + }).then(sas => `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}${sas.sas}`) + ); } interface BlobBindingDefinition extends appservice.BindingDefinition { From 50366f6265600d84d70f77c9a21c6833299c3447 Mon Sep 17 00:00:00 2001 From: Mikhail Shilkov Date: Sun, 26 May 2019 10:03:36 +0200 Subject: [PATCH 4/4] Change to async-await --- sdk/nodejs/storage/zMixins.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/nodejs/storage/zMixins.ts b/sdk/nodejs/storage/zMixins.ts index a13275e0be..76e992886e 100644 --- a/sdk/nodejs/storage/zMixins.ts +++ b/sdk/nodejs/storage/zMixins.ts @@ -40,8 +40,8 @@ export function signedBlobReadUrl(blob: Blob | ZipBlob, account: Account): pulum const signatureExpiration = "2100-01-01"; return pulumi.all([account.name, account.primaryConnectionString, blob.storageContainerName, blob.name]).apply( - ([accountName, connectionString, containerName, blobName]) => - getAccountSAS({ + async ([accountName, connectionString, containerName, blobName]) => { + const sas = await getAccountSAS({ connectionString, start: "2019-01-01", expiry: signatureExpiration, @@ -66,8 +66,10 @@ export function signedBlobReadUrl(blob: Blob | ZipBlob, account: Account): pulum update: false, process: false, } - }).then(sas => `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}${sas.sas}`) - ); + }); + + return `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}${sas.sas}`; + }); } interface BlobBindingDefinition extends appservice.BindingDefinition {