Skip to content

Commit 5bf8ca9

Browse files
committed
Code without tests.
Code without tests.
1 parent 6c4f5ff commit 5bf8ca9

File tree

8 files changed

+209
-7
lines changed

8 files changed

+209
-7
lines changed

src/ResourceManager/Sql/Commands.Sql/Auditing/AuditingHelpMessages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This parameter is not required.
1010
If you do not specify this parameter, the cmdlet uses the storage account that was defined previously as part of the auditing policy.
1111
If this is the first time an auditing policy is defined and you do not specify this parameter, the cmdlet fails.";
1212

13+
public const string AuditStorageAccountSubscriptionIdHelpMessage = "Specifies the id of the storage account subscription";
14+
1315
public const string StorageKeyTypeHelpMessage = "Specifies which of the storage access keys to use.";
1416

1517
public const string RetentionInDaysHelpMessage = "The number of retention days for the audit logs.";

src/ResourceManager/Sql/Commands.Sql/Auditing/Cmdlet/AuditingSettings/SetAzureSqlDatabaseAuditing.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ public class SetAzureSqlDatabaseAuditing : SqlDatabaseAuditingSettingsCmdletBase
6060
[ValidateNotNullOrEmpty]
6161
public string StorageAccountName { get; set; }
6262

63+
/// <summary>
64+
/// Gets or sets the id of the storage account subscription to use.
65+
/// </summary>
66+
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = AuditingHelpMessages.AuditStorageAccountSubscriptionIdHelpMessage)]
67+
[ValidateNotNullOrEmpty]
68+
public Guid StorageAccountSubscriptionId { get; set; }
69+
6370
/// <summary>
6471
/// Gets or sets the type of the storage key.
6572
/// </summary>
@@ -118,6 +125,11 @@ protected override DatabaseBlobAuditingSettingsModel ApplyUserInputToModel(Datab
118125
model.AuditAction = AuditAction;
119126
}
120127

128+
if (!StorageAccountSubscriptionId.Equals(Guid.Empty))
129+
{
130+
model.StorageAccountSubscriptionId = StorageAccountSubscriptionId;
131+
}
132+
121133
return model;
122134
}
123135
}

src/ResourceManager/Sql/Commands.Sql/Auditing/Cmdlet/AuditingSettings/SetAzureSqlServerAuditing.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ public class SetAzureSqlServerAuditing : SqlServerAuditingSettingsCmdletBase
5454
[ValidateNotNullOrEmpty]
5555
public string StorageAccountName { get; set; }
5656

57+
/// <summary>
58+
/// Gets or sets the id of the storage account subscription to use.
59+
/// </summary>
60+
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = AuditingHelpMessages.AuditStorageAccountSubscriptionIdHelpMessage)]
61+
[ValidateNotNullOrEmpty]
62+
public Guid StorageAccountSubscriptionId { get; set; }
63+
5764
/// <summary>
5865
/// Gets or sets the name of the storage account to use.
5966
/// </summary>
@@ -103,6 +110,11 @@ protected override ServerBlobAuditingSettingsModel ApplyUserInputToModel(ServerB
103110
model.AuditActionGroup = AuditActionGroup;
104111
}
105112

113+
if (!StorageAccountSubscriptionId.Equals(Guid.Empty))
114+
{
115+
model.StorageAccountSubscriptionId = StorageAccountSubscriptionId;
116+
}
117+
106118
return model;
107119
}
108120
}

src/ResourceManager/Sql/Commands.Sql/Auditing/Model/AuditingPolicyModel.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using System;
16+
1517
namespace Microsoft.Azure.Commands.Sql.Auditing.Model
1618
{
1719
/// <summary>
@@ -55,6 +57,11 @@ public abstract class AuditingPolicyModel
5557
/// </summary>
5658
public string StorageAccountName { get; set; }
5759

60+
/// <summary>
61+
/// Gets or sets the id of the storage account subscription.
62+
/// </summary>
63+
public Guid StorageAccountSubscriptionId { get; set; }
64+
5865
/// <summary>
5966
/// Gets or sets the storage key type
6067
/// </summary>

src/ResourceManager/Sql/Commands.Sql/Auditing/Services/SqlAuditAdapter.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public class SqlAuditAdapter
6666
/// Caching the fetched storage account table name to prevent costly network interaction in cases it is not needed
6767
/// </summary>
6868
private string FetchedStorageAccountTableEndpoint { get; set; }
69-
69+
7070

7171
/// <summary>
7272
/// In cases when storage is not needed and not provided, there's no need to perform storage related network interaction that may fail
@@ -217,7 +217,7 @@ private void ModelizeDatabaseAuditPolicy(BlobAuditingPolicy policy, DatabaseBlob
217217
{
218218
var properties = policy.Properties;
219219
dbPolicyModel.AuditState = ModelizeAuditState(properties.State);
220-
ModelizeStorageInfo(dbPolicyModel, properties.StorageEndpoint, properties.IsStorageSecondaryKeyInUse);
220+
ModelizeStorageInfo(dbPolicyModel, properties.StorageEndpoint, properties.IsStorageSecondaryKeyInUse, properties.StorageAccountSubscriptionId);
221221
ModelizeAuditActionGroups(dbPolicyModel, properties.AuditActionsAndGroups);
222222
ModelizeAuditActions(dbPolicyModel, properties.AuditActionsAndGroups);
223223
ModelizeRetentionInfo(dbPolicyModel, properties.RetentionDays);
@@ -256,7 +256,7 @@ private void ModelizeRetentionInfo(BaseBlobAuditingPolicyModel model, int retent
256256
model.RetentionInDays = Convert.ToUInt32(retentionDays);
257257
}
258258

259-
private static void ModelizeStorageInfo(BaseBlobAuditingPolicyModel model, string storageEndpoint, bool isSecondary)
259+
private static void ModelizeStorageInfo(BaseBlobAuditingPolicyModel model, string storageEndpoint, bool isSecondary, string storageAccountSubscriptionId)
260260
{
261261
if (string.IsNullOrEmpty(storageEndpoint))
262262
{
@@ -266,6 +266,7 @@ private static void ModelizeStorageInfo(BaseBlobAuditingPolicyModel model, strin
266266
var accountNameEndIndex = storageEndpoint.IndexOf(".blob", StringComparison.InvariantCultureIgnoreCase);
267267
model.StorageAccountName = storageEndpoint.Substring(accountNameStartIndex, accountNameEndIndex- accountNameStartIndex);
268268
model.StorageKeyType = (isSecondary) ? StorageKeyKind.Secondary : StorageKeyKind.Primary;
269+
model.StorageAccountSubscriptionId = Guid.Parse(storageAccountSubscriptionId);
269270
}
270271

271272
/// <summary>
@@ -289,7 +290,7 @@ private void ModelizeServerAuditPolicy(BlobAuditingPolicy policy, ServerBlobAudi
289290
{
290291
var properties = policy.Properties;
291292
serverPolicyModel.AuditState = ModelizeAuditState(properties.State);
292-
ModelizeStorageInfo(serverPolicyModel, properties.StorageEndpoint, properties.IsStorageSecondaryKeyInUse);
293+
ModelizeStorageInfo(serverPolicyModel, properties.StorageEndpoint, properties.IsStorageSecondaryKeyInUse, properties.StorageAccountSubscriptionId);
293294
ModelizeAuditActionGroups(serverPolicyModel, properties.AuditActionsAndGroups);
294295
ModelizeRetentionInfo(serverPolicyModel, properties.RetentionDays);
295296
}
@@ -470,9 +471,12 @@ private BlobAuditingCreateOrUpdateParameters PolicizeBlobAuditingModel(BaseBlobA
470471
if (!IgnoreStorage && (model.AuditState == AuditStateType.Enabled))
471472
{
472473
properties.StorageEndpoint = ExtractStorageAccountName(model, storageEndpointSuffix);
473-
properties.StorageAccountAccessKey = ExtractStorageAccountKey(model.StorageAccountName, model.StorageKeyType);
474+
properties.StorageAccountAccessKey = Guid.Empty.Equals(model.StorageAccountSubscriptionId) || Context.Subscription.GetId().Equals(model.StorageAccountSubscriptionId) ?
475+
ExtractStorageAccountKey(model.StorageAccountName, model.StorageKeyType) :
476+
ExtractStorageAccountKey(model.StorageAccountSubscriptionId, model.StorageAccountName, model.StorageKeyType);
474477
properties.IsStorageSecondaryKeyInUse = model.StorageKeyType == StorageKeyKind.Secondary;
475-
properties.StorageAccountSubscriptionId = ExtractStorageAccountSubscriptionId(model.StorageAccountName);
478+
properties.StorageAccountSubscriptionId = Guid.Empty.Equals(model.StorageAccountSubscriptionId) ?
479+
ExtractStorageAccountSubscriptionId(model.StorageAccountName) : model.StorageAccountSubscriptionId.ToString();
476480
}
477481
properties.AuditActionsAndGroups = ExtractAuditActionsAndGroups(model);
478482
if (model.RetentionInDays != null)
@@ -620,6 +624,11 @@ private string ExtractStorageAccountResourceGroup(string storageName)
620624
return AzureCommunicator.GetStorageResourceGroup(storageName);
621625
}
622626

627+
private string ExtractStorageAccountKey(Guid storageAccountSubscriptionId, string storageAccountName, StorageKeyKind storageKeyKind)
628+
{
629+
return AzureCommunicator.RetrieveStorageKeys(storageAccountSubscriptionId, storageAccountName)[storageKeyKind];
630+
}
631+
623632
/// <summary>
624633
/// Extracts the storage account requested key
625634
/// </summary>

src/ResourceManager/Sql/Commands.Sql/Common/AzureEndpointsCommunicator.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using System.Collections.Generic;
2626
using System.Linq;
2727
using System.Net.Http;
28+
using System.Text;
2829
using System.Threading;
2930
using System.Threading.Tasks;
3031

@@ -234,5 +235,140 @@ private ResourceManagementClient GetCurrentResourcesClient(IAzureContext context
234235
}
235236
return ResourcesClient;
236237
}
238+
239+
/// <summary>
240+
/// Retrieves storage keys.
241+
/// </summary>
242+
/// <param name="storageAccountSubscriptionId">Storage account subscription id</param>
243+
/// <param name="storageAccountName">Storage account name</param>
244+
/// <returns>Dictionary containing storage keys</returns>
245+
internal Dictionary<StorageKeyKind, string> RetrieveStorageKeys(Guid storageAccountSubscriptionId, string storageAccountName)
246+
{
247+
// Retrieve the id of the storage account.
248+
//
249+
string storageAccountId = RetrieveStorageAccountIdAsync(storageAccountSubscriptionId, storageAccountName).GetAwaiter().GetResult();
250+
251+
// Extract storage account keys.
252+
//
253+
return RetrieveStorageKeysAsync(storageAccountId).GetAwaiter().GetResult();
254+
}
255+
256+
/// <summary>
257+
/// Retrieves storage account keys.
258+
/// </summary>
259+
/// <param name="storageAccountId">Storage account id</param>
260+
/// <returns>Dictionary containing storage keys</returns>
261+
private async Task<Dictionary<StorageKeyKind, string>> RetrieveStorageKeysAsync(string storageAccountId)
262+
{
263+
bool isClassicStorage = storageAccountId.Contains("Microsoft.ClassicStorage/storageAccounts");
264+
265+
// Build a URI for calling corresponding REST-API
266+
//
267+
StringBuilder uriBuilder = new StringBuilder(Context.Environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager).ToString());
268+
uriBuilder.AppendFormat("{0}/listKeys?api-version={1}",
269+
storageAccountId,
270+
isClassicStorage ? "2016-11-01" : "2017-06-01");
271+
272+
// Define an exception to be thrown on failure.
273+
//
274+
Exception exception = new Exception(string.Format(Properties.Resources.RetrievingStorageAccountKeysFailed, storageAccountId));
275+
276+
// Call the URI and get storage account keys.
277+
//
278+
JToken storageAccountKeysResponse = await SendAsync(uriBuilder.ToString(), HttpMethod.Post, exception);
279+
280+
// Extract keys out of response.
281+
//
282+
Dictionary<StorageKeyKind, string> storageAccountKeys = new Dictionary<StorageKeyKind, string>();
283+
string primaryKey = null;
284+
string secondaryKey = null;
285+
if (isClassicStorage)
286+
{
287+
primaryKey = (string)storageAccountKeysResponse["primaryKey"];
288+
secondaryKey = (string)storageAccountKeysResponse["secondaryKey"];
289+
}
290+
else
291+
{
292+
JArray storageAccountKeysArray = (JArray)storageAccountKeysResponse["keys"];
293+
if (storageAccountKeysArray == null)
294+
{
295+
throw exception;
296+
}
297+
298+
primaryKey = (string)storageAccountKeysArray[0]["value"];
299+
secondaryKey = (string)storageAccountKeysArray[1]["value"];
300+
}
301+
302+
if (string.IsNullOrEmpty(primaryKey) || string.IsNullOrEmpty(secondaryKey))
303+
{
304+
throw exception;
305+
}
306+
307+
storageAccountKeys.Add(StorageKeyKind.Primary, primaryKey);
308+
storageAccountKeys.Add(StorageKeyKind.Secondary, secondaryKey);
309+
return storageAccountKeys;
310+
}
311+
312+
/// <summary>
313+
/// Retrieves id of a storage account
314+
/// </summary>
315+
/// <param name="storageAccountSubscriptionId">Storage account subscription id</param>
316+
/// <param name="storageAccountName">Storage account name</param>
317+
/// <returns>Id of the storage account</returns>
318+
private async Task<string> RetrieveStorageAccountIdAsync(Guid storageAccountSubscriptionId, string storageAccountName)
319+
{
320+
// Build a URI for calling corresponding REST-API.
321+
//
322+
StringBuilder uriBuilder = new StringBuilder(Context.Environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager).ToString());
323+
uriBuilder.AppendFormat("/resources?api-version=2018-05-01&$filter=(subscriptionId%20eq%20'{0}')%20and%20((resourceType%20eq%20'microsoft.storage/storageaccounts')%20or%20(resourceType%20eq%20'microsoft.classicstorage/storageaccounts'))%20and%20(name%20eq%20'{1}')",
324+
storageAccountSubscriptionId,
325+
storageAccountName);
326+
string nextLink = uriBuilder.ToString();
327+
JToken response = null;
328+
329+
while (!string.IsNullOrEmpty(nextLink))
330+
{
331+
response = await SendAsync(nextLink, HttpMethod.Get, new Exception(string.Format(Properties.Resources.RetrievingStorageAccountIdUnderSubscriptionFailed, storageAccountName, storageAccountSubscriptionId)));
332+
nextLink = (string)response["nextLink"];
333+
}
334+
335+
JArray valuesArray = (JArray)response["value"];
336+
if (!valuesArray.HasValues)
337+
{
338+
throw new Exception(string.Format(Properties.Resources.StorageAccountNotFound, storageAccountName));
339+
}
340+
341+
JToken idValueToken = valuesArray[0];
342+
string id = (string)idValueToken["id"];
343+
if (string.IsNullOrEmpty(id))
344+
{
345+
throw new Exception(string.Format(Properties.Resources.RetrievingStorageAccountIdUnderSubscriptionFailed, storageAccountName, storageAccountSubscriptionId));
346+
}
347+
348+
return id;
349+
}
350+
351+
/// <summary>
352+
/// Sends an async HTTP request.
353+
/// </summary>
354+
/// <param name="url">URL of the request.</param>
355+
/// <param name="method">Http method.</param>
356+
/// <param name="exceptionToThrowOnFailure">Exception to be thrown if request did not succeed.</param>
357+
/// <returns>Response of the request.</returns>
358+
private async Task<JToken> SendAsync(string url, HttpMethod method, Exception exceptionToThrowOnFailure)
359+
{
360+
ResourceManagementClient client = GetCurrentResourcesClient(Context);
361+
HttpRequestMessage httpRequest = new HttpRequestMessage();
362+
httpRequest.Method = method;
363+
httpRequest.RequestUri = new Uri(url);
364+
await client.Credentials.ProcessHttpRequestAsync(httpRequest, CancellationToken.None).ConfigureAwait(false);
365+
HttpResponseMessage httpResponse = await client.HttpClient.SendAsync(httpRequest, CancellationToken.None).ConfigureAwait(false);
366+
if (!httpResponse.IsSuccessStatusCode)
367+
{
368+
throw exceptionToThrowOnFailure;
369+
}
370+
371+
return JToken.Parse(await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false));
372+
}
237373
}
238374
}

src/ResourceManager/Sql/Commands.Sql/Properties/Resources.Designer.cs

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ResourceManager/Sql/Commands.Sql/Properties/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,10 @@
408408
<data name="RemoveAzureSqlDatabaseLongTermRetentionBackupWarning" xml:space="preserve">
409409
<value>Are you sure you want to remove the Long Term Retention backup '{0}' on database '{1}' on server '{2}' in location '{3}'?</value>
410410
</data>
411+
<data name="RetrievingStorageAccountIdUnderSubscriptionFailed" xml:space="preserve">
412+
<value>Failed retrieving id of storage account '{0}' under subscription '{1}'.</value>
413+
</data>
414+
<data name="RetrievingStorageAccountKeysFailed" xml:space="preserve">
415+
<value>Failed retrieving keys of storage '{0}'.</value>
416+
</data>
411417
</root>

0 commit comments

Comments
 (0)