Azure blob access #94
Changes from 10 commits
bee99cc
619a8c1
10c4478
931d0a5
5a2a074
93921d2
b52858d
356eb99
3c83615
9a598b1
91811dc
e647e30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
$schema: http://json-schema.org/draft-04/schema# | ||
title: "Azure Blob Shared-Access-Signature Response" | ||
description: | | ||
Response to a request for an Shared-Access-Signature to access an Azure | ||
Blob Storage container. | ||
type: object | ||
properties: | ||
sas: | ||
description: | | ||
Shared-Access-Signature string. This is the querystring parameters to | ||
be appened after `?` or `&` depending on whether or not a querystring is | ||
already present in the URL. | ||
type: string | ||
expiry: | ||
description: | | ||
Date and time of when the Shared-Access-Signature expires. | ||
type: string | ||
format: date-time | ||
additionalProperties: false | ||
required: | ||
- sas | ||
- expiry |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,9 +88,8 @@ api.declare({ | |
|
||
// Check that the account exists | ||
if (!this.azureAccounts[account]) { | ||
return res.status(404).json({ | ||
message: "Account '" + account + "' not found, can't delegate access" | ||
}); | ||
return res.reportError('InvalidRequestArguments', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ResourceNotFound produces 404 |
||
`Account '${account}' not found, can't delegate access`); | ||
} | ||
|
||
// Construct client | ||
|
@@ -131,3 +130,84 @@ api.declare({ | |
expiry: expiry.toJSON() | ||
}); | ||
}); | ||
|
||
api.declare({ | ||
method: 'get', | ||
route: '/azure/:account/containers/:container/:level', | ||
name: 'azureBlobSAS', | ||
input: undefined, | ||
output: 'azure-blob-response.json#', | ||
deferAuth: true, | ||
stability: 'stable', | ||
scopes: [['auth:azure-blob:<level>:<account>/<container>']], | ||
title: "Get Shared-Access-Signature for Azure Blob", | ||
description: [ | ||
"Get a shared access signature (SAS) string for use with a specific Azure", | ||
"Blob Storage container. If the level is read-write, the container will be created, " + | ||
"if it doesn't already exists." | ||
].join('\n') | ||
}, async function(req, res){ | ||
// Get parameters | ||
let account = req.params.account; | ||
let container = req.params.container; | ||
let level = req.params.level; | ||
|
||
if (['read-write', 'read-only'].indexOf(level) < 0) { | ||
return res.reportError('InvalidRequestArguments', | ||
`Level '${level}' is not valid. Must be one of ['read-write', 'read-only'].`); | ||
} | ||
|
||
// Check that the client is authorized to access given account and container | ||
if (!(level === 'read-only' && | ||
req.satisfies({account, container, level: 'read-write'}, true)) && | ||
!req.satisfies({account, container, level})) { | ||
return; | ||
} | ||
|
||
// Check that the account exists | ||
if (!this.azureAccounts[account]) { | ||
return res.reportError('InvalidRequestArguments', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again this is probably a not found error |
||
`Account '${level}' not found, can't delegate access.`); | ||
} | ||
|
||
// Construct client | ||
let blob = new azure.Blob({ | ||
accountId: account, | ||
accessKey: this.azureAccounts[account] | ||
}); | ||
|
||
// Create container ignore error, if it already exists | ||
if (level === 'read-write') { | ||
try { | ||
await blob.createContainer(container); | ||
} catch (err) { | ||
if (err.code !== 'ContainerAlreadyExists') { | ||
throw err; | ||
} | ||
} | ||
} | ||
|
||
let perm = level === 'read-write'; | ||
|
||
// Construct SAS | ||
let expiry = new Date(Date.now() + 25 * 60 * 1000); | ||
let sas = blob.sas(container, null, { | ||
start: new Date(Date.now() - 15 * 60 * 1000), | ||
expiry: expiry, | ||
resourceType: 'container', | ||
permissions: { | ||
read: true, | ||
add: perm, | ||
create: perm, | ||
write: perm, | ||
delete: perm, | ||
list: true, | ||
} | ||
}); | ||
|
||
// Return the generated SAS | ||
return res.reply({ | ||
sas: sas, | ||
expiry: expiry.toJSON() | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,4 @@ | ||||||||||||||||||||||||||||||
suite('azure table (sas)', function() { | ||||||||||||||||||||||||||||||
suite.only('azure table (sas)', function() { | ||||||||||||||||||||||||||||||
var Promise = require('promise'); | ||||||||||||||||||||||||||||||
var assert = require('assert'); | ||||||||||||||||||||||||||||||
var debug = require('debug')('auth:test:azure'); | ||||||||||||||||||||||||||||||
|
@@ -101,8 +101,7 @@ suite('azure table (sas)', function() { | |||||||||||||||||||||||||||||
accessToken: helper.rootAccessToken | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureTableSAS (allowed table)', function() { | ||||||||||||||||||||||||||||||
test('azureTableSAS (allowed table)', () => { | ||||||||||||||||||||||||||||||
// Restrict access a bit | ||||||||||||||||||||||||||||||
var auth = new helper.Auth({ | ||||||||||||||||||||||||||||||
baseUrl: helper.baseUrl, | ||||||||||||||||||||||||||||||
|
@@ -181,4 +180,133 @@ suite('azure table (sas)', function() { | |||||||||||||||||||||||||||||
assert(err.statusCode == 403, "Expected authorization error!"); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS', async () => { | ||||||||||||||||||||||||||||||
let result = await helper.auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'container-test', | ||||||||||||||||||||||||||||||
'read-write' | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
assert(typeof(result.sas) === 'string', "Expected some form of string"); | ||||||||||||||||||||||||||||||
assert(new Date(result.expiry).getTime() > new Date().getTime(), | ||||||||||||||||||||||||||||||
"Expected expiry to be in the future"); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS (read-write)', async () => { | ||||||||||||||||||||||||||||||
let result = await helper.auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'container-test', | ||||||||||||||||||||||||||||||
'read-write', | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
assert(typeof(result.sas) === 'string', "Expected some form of string"); | ||||||||||||||||||||||||||||||
assert(new Date(result.expiry).getTime() > new Date().getTime(), | ||||||||||||||||||||||||||||||
"Expected expiry to be in the future"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let blob = new azure.Blob({ | ||||||||||||||||||||||||||||||
accountId: helper.testaccount, | ||||||||||||||||||||||||||||||
sas: result.sas | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
result = await blob.putBlob('container-test', 'blobTest', {type: 'BlockBlob'}); | ||||||||||||||||||||||||||||||
assert(result); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS (read-only)', async () => { | ||||||||||||||||||||||||||||||
let result = await helper.auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'container-test', | ||||||||||||||||||||||||||||||
'read-only', | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
assert(typeof(result.sas) === 'string', "Expected some form of string"); | ||||||||||||||||||||||||||||||
assert(new Date(result.expiry).getTime() > new Date().getTime(), | ||||||||||||||||||||||||||||||
"Expected expiry to be in the future"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let blob = new azure.Blob({ | ||||||||||||||||||||||||||||||
accountId: helper.testaccount, | ||||||||||||||||||||||||||||||
sas: result.sas | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||
await blob.putBlob('container-test', 'blobTest', {type: 'BlockBlob'}); | ||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||
assert.equal(error.code, 'AuthorizationPermissionMismatch'); | ||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
assert(false, 'This should have thrown an error because the write is not allowed.'); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS (invalid level)', async () => { | ||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||
await helper.auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'container-test', | ||||||||||||||||||||||||||||||
'foo-bar-baz', | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||
assert.equal(error.code, 'InvalidRequestArguments'); | ||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
assert(false, "This should have thrown an error"); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS (allowed container)', async () => { | ||||||||||||||||||||||||||||||
let auth = new helper.Auth({ | ||||||||||||||||||||||||||||||
baseUrl: helper.baseUrl, | ||||||||||||||||||||||||||||||
credentials: rootCredentials, | ||||||||||||||||||||||||||||||
authorizedScopes: [ | ||||||||||||||||||||||||||||||
'auth:azure-blob:read-write:' + helper.testaccount + '/allowed-container' | ||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let result = await auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'allowed-container', | ||||||||||||||||||||||||||||||
'read-write' | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
assert(typeof(result.sas) === 'string', "Expected some form of string"); | ||||||||||||||||||||||||||||||
assert(new Date(result.expiry).getTime() > new Date().getTime(), | ||||||||||||||||||||||||||||||
"Expected expiry to be in the future"); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS (allowed read-write -> read-only)', async () => { | ||||||||||||||||||||||||||||||
var auth = new helper.Auth({ | ||||||||||||||||||||||||||||||
baseUrl: helper.baseUrl, | ||||||||||||||||||||||||||||||
credentials: rootCredentials, | ||||||||||||||||||||||||||||||
authorizedScopes: [ | ||||||||||||||||||||||||||||||
'auth:azure-blob:read-write:' + helper.testaccount + '/allowed-container' | ||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let result = await auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'allowed-container', | ||||||||||||||||||||||||||||||
'read-only', | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
assert(typeof(result.sas) === 'string', "Expected some form of string"); | ||||||||||||||||||||||||||||||
assert(new Date(result.expiry).getTime() > new Date().getTime(), | ||||||||||||||||||||||||||||||
"Expected expiry to be in the future"); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('azureBlobSAS (unauthorized container)', async () => { | ||||||||||||||||||||||||||||||
let auth = new helper.Auth({ | ||||||||||||||||||||||||||||||
baseUrl: helper.baseUrl, | ||||||||||||||||||||||||||||||
credentials: rootCredentials, | ||||||||||||||||||||||||||||||
authorizedScopes: [ | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helper has a quick method to set authorised scopes... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which method? Sorry, but I do not find a method that can set authorizedScopes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, we don't have it here... taskcluster-auth/test/helper.js Lines 76 to 82 in ce1f509
We could do the same here: taskcluster-auth/test/helper.js Lines 76 to 82 in ce1f509
Ie. basically wrap with a |
||||||||||||||||||||||||||||||
'auth:azure-blob:read-write:' + helper.testaccount + '/allowed-container' | ||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||
await auth.azureBlobSAS( | ||||||||||||||||||||||||||||||
helper.testaccount, | ||||||||||||||||||||||||||||||
'unauthorized-container', | ||||||||||||||||||||||||||||||
'read-write' | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||
assert(error.statusCode == 403, "Expected authorization error!"); | ||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
assert(false, "Expected an authentication error!"); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks :)