Scope for Accessing Storage Account using Managed Identity - azure

I'm using managed identity to access azure database in this manner.The Azure App Registration is used for getting the token and the token is passed to the connection.In the same manner,how do i connect to a storage account and write to a container? What will be the scope in this case?
AuthenticationResult authenticationResult = null;
var _app = ConfidentialClientApplicationBuilder.Create(Environment.GetEnvironmentVariable("ClientId"))
.WithAuthority(string.Format(Environment.GetEnvironmentVariable("AADInstance"), Environment.GetEnvironmentVariable("Tenant")))
.WithClientSecret(Environment.GetEnvironmentVariable("ClientSecret")).Build();
authenticationResult = _app.AcquireTokenForClient(new string[] { "https://database.windows.net/.default" }).ExecuteAsync().Result;
using (SqlConnection conn = new SqlConnection(Environment.GetEnvironmentVariable("DBConnection")))
{
conn.AccessToken = authenticationResult.AccessToken;
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT * FROM mytable", conn))
{
var result = cmd.ExecuteScalar();
Console.WriteLine(result);
}
}

Azure Storage uses this scope:
https://storage.azure.com/.default
That said, with the new Azure Storage SDK and Azure.Identity, you don't actually need to know this.
You can use them like this:
var credential = new ClientSecretCredential(tenantId: "", clientId: "", clientSecret: "");
var blobUrl = "https://accountname.blob.core.windows.net";
var service = new BlobServiceClient(new Uri(blobUrl), credential);
var container = service.GetBlobContainerClient("container");
var blob = container.GetBlobClient("file.txt");
// TODO: Write the file

For Azure Storage, the scope will be https://storage.azure.com/.default.
Please see this link for more details: https://learn.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app?tabs=dotnet#azure-storage-resource-id.

Related

How to create Azure Storage SAS token using DefaultAzureCredential class

I want to create SAS token to download a blob stored in container in azure storage. I can easily generate SAS token using shared credential but this requires storage access key. How can I generate sas token using managed Identity.
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
sasQueryParams, err := azblob.BlobSASSignatureValues{
Protocol: azblob.SASProtocolHTTPS,
ExpiryTime: time.Now().UTC().Add(4 * time.Hour),
ContainerName: containerName,
BlobName: blobName,
Permissions: azblob.BlobSASPermissions{Add: false,
Read: true, Write: false}.String(),
}.NewSASQueryParameters(credential)
How can I generate sas token using managed Identity?
You can generate it by using DefaultAzureCredential and the proper access to that blob in the storage container.
Connect to the storage account by using the Azure AD credentials of Default Azure Credential class.
Sample Code:
var strgAccName = _configuration.GetValue<string>("YourStorageAccountName");
var saUri = $"https://{strgAccName}.blob.core.windows.net";
var blobServiceClient = new BlobServiceClient(new Uri(saUri), new DefaultAzureCredential());
var blobContainerClient = blobServiceClient.GetBlobContainerClient(_configuration.GetValue<string>("YourContainerName"));
var blobClient = blobContainerClient.GetBlobClient("YourImage.jpg");
// We can issue the SAS token till a maximum of 7 days.
var userDelegationKey = blobServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddHours(4));
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = blobClient.BlobContainerName,
BlobName = blobClient.Name,
Resource = "b", // b: blob, c: container
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(4),
};
sasBuilder.SetPermissions(BlobSasPermissions.Read);
var blobUriBuilder = new BlobUriBuilder(blobClient.Uri)
{
Sas = sasBuilder.ToSasQueryParameters(userDelegationKey,blobServiceClient.AccountName)
};
// Read this in any view like `blobUriBuilder.ToUri().ToString();`
}
And re-check the delegated access is there or not for that blob.
So, we don't use any access key and connection string for this.
Thanks to #Anupam Maiti for this Article, please refer this for step-by-step procedure.

Programmatically authenticate AKS with Azure AD and Managed Identity

I'm new to AKS and the Azure Identity platform. I have an AKS cluster that is using the Azure AD integration. From an Azure VM that has a user assigned managed identity, I'm trying to run a C# console app to authenticate against Azure AD, get the kubeconfig contents and then work with the kubernetes client to perform some list operations. When the code below is run I get an Unauthorized error when attempting to perform the List operation. I've made sure that in the cluster access roles, the user assigned managed identity has the Owner role.
The code does the following:
Creates an instance of DefaultAzureCredential with the user managed identity ID
Converts the token from DefaultAzureCredential to an instance of Microsoft.Azure.Management.ResourceManager.Fluent.Authentication.AzureCredentials and authenticates
Gets the contents of the kubeconfig for the authenticated user
Gets the access token from http://169.254.169.254/metadata/identity/oauth2/token
Sets the access token on the kubeconfig and creates a new instance of the Kubernetes client
Attempt to list the namespaces in the cluster
I've pulled information from this POST as well from this POST.
I'm not sure if the scopes of TokenRequestContext is correct and if the resource parameter of the oauth token request is correct.
string userAssignedClientId = "0f2a4a25-e37f-4aba-942a-5c58f39eb136";
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId });
var defaultToken = credential.GetToken(new TokenRequestContext(new[] { "https://management.azure.com/.default" })).Token;
var defaultTokenCredentials = new Microsoft.Rest.TokenCredentials(defaultToken);
var azureCredentials = new Microsoft.Azure.Management.ResourceManager.Fluent.Authentication.AzureCredentials(defaultTokenCredentials, defaultTokenCredentials, null, AzureEnvironment.AzureGlobalCloud);
var azure = Microsoft.Azure.Management.Fluent.Azure.Authenticate(azureCredentials).WithSubscription("XXX");
var kubeConfigBytes = azure.KubernetesClusters.GetUserKubeConfigContents(
"XXX",
"XXX"
);
var kubeConfigRaw = KubernetesClientConfiguration.LoadKubeConfig(new MemoryStream(kubeConfigBytes));
var authProvider = kubeConfigRaw.Users.Single().UserCredentials.AuthProvider;
if (!authProvider.Name.Equals("azure", StringComparison.OrdinalIgnoreCase))
throw new Exception("Invalid k8s auth provider!");
var httpClient = new HttpClient();
var token = string.Empty;
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Get, $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={Uri.EscapeUriString("6dae42f8-4368-4678-94ff-3960e28e3630/.default")}&client_id={userAssignedClientId}"))
{
requestMessage.Headers.Add("Metadata", "true");
var response = await httpClient.SendAsync(requestMessage);
token = await response.Content.ReadAsStringAsync();
Console.WriteLine(token);
}
var tokenNode = JsonNode.Parse(token);
authProvider.Config["access-token"] = tokenNode["access_token"].GetValue<string>();
authProvider.Config["expires-on"] = DateTimeOffset.UtcNow.AddSeconds(double.Parse(tokenNode["expires_in"].GetValue<string>())).ToUnixTimeSeconds().ToString();
var kubeConfig = KubernetesClientConfiguration.BuildConfigFromConfigObject(kubeConfigRaw);
var kubernetes = new Kubernetes(kubeConfig);
var namespaces = kubernetes.CoreV1.ListNamespace();
foreach (var ns in namespaces.Items)
{
Console.WriteLine(ns.Metadata.Name);
var list = kubernetes.CoreV1.ListNamespacedPod(ns.Metadata.Name);
foreach (var item in list.Items)
{
Console.WriteLine(item.Metadata.Name);
}
}
Any help is appreciated!
Try using the resource in the token request without /.default.
So it should be:
resource=6dae42f8-4368-4678-94ff-3960e28e3630

Resource Graph query using Azure Function .NET and User managed Identity?

In the example the DotNet-ResourceGraphClient requires ServiceClientCredentials. I do not know how to use a user-assigned-managed-identity directly.
For instance:
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = umiClientId });
ResourceGraphClient argClient = new ResourceGraphClient(serviceClientCreds);
results in: Argument 1: cannot convert from 'Azure.Identity.DefaultAzureCredential' to 'Microsoft.Rest.ServiceClientCredentials'.
I found a PHP-example with credentials = MSIAuthentication(). Can anyone provide a similar example for dotnet-azure-resource-graph-sdk?
Thanks
To acquire a token credential for your code to approve calls to Microsoft Graph, one workaround is to utilize the ChainedTokenCredential, ManagedIdentityCredential and EnvironmentCredential classes.
The following snippet generates the authenticated token credential and implements those to the creation of a service client object.
var credential = new ChainedTokenCredential(
new ManagedIdentityCredential(),
new EnvironmentCredential());
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }));
var accessToken = token.Token;
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
REFERENCES:
Access Microsoft Graph from a secured .NET app as the app
Tutorial: Access Microsoft Graph from a secured .NET app as the app
thanks for the input.
Authentication with user managed identity.
https://learn.microsoft.com/en-us/dotnet/api/overview/azure/service-to-service-authentication#connection-string-support
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
// Connect client with user assigned managed identity.
string umiClientId = "<your-user-assigned-managed-identity-client-id>";
string conStrOpts = string.Format("RunAs=App;AppId={0}", umiClientId);
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider(
conStrOpts
);
var tokenCredentials = new TokenCredentials(
await azureServiceTokenProvider
.GetAccessTokenAsync("https://management.azure.com/")
.ConfigureAwait(false)
);
ResourceGraphClient argClient = new ResourceGraphClient(tokenCredentials);

Unable to Create Pool using Azure Batch Management Library with ServiceClientCrenetials generated via AzureCredentialsFactory

I am using this documentation to assign a managed identity to my Batch Pool. For simplicity I do not include that assignment in the examples below as the issue is not tied to that but rather to accessing the management library with my credentials and just being able to generate a new pool on my existing batch account.
I'm using a Service Principal to generate ServiceClientCredntials via AzureCredentialsFactory to use with the Microsoft.Azure.Management.Batch library.
The Service Principal has Azure Service Management permissions enabled with user_impersonation set as Delegated. It is also assigned as a Contributor role on the subscription.
I am able to create the credentials with the following code
using Microsoft.Azure.Management.Batch.Models;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
string subscriptionId = "<subscriptionId>";
string tenantId = "<tenantId>";
string servicePrincipalId = "<servicePrincipalId>";
string servicePrincipalKey = "<servicePrincipalKey>";
var creds = new AzureCredentialsFactory().FromServicePrincipal(servicePrincipalId, servicePrincipalKey, tenantId, AzureEnvironment.AzureGlobalCloud).WithDefaultSubscription(subscriptionId);
var managementClient = new Microsoft.Azure.Management.Batch.BatchManagementClient(creds);
However when I attempt to use the credentials to create a pool in my Azure Batch account I get the following exception:
Microsoft.Rest.ValidationException: ''this.Client.SubscriptionId' cannot be null.'
I'm using .WithDefaultSubscription(subscriptionId) and have verified that the default subscription is set on the credentials prior to creating the BatchManagementClient.
Here is the code I am using to create the pool
var poolId = "test-pool";
var batchResourceGroupName = "<resourceGroupName>";
var batchAccountName = "<batchAccountName>";
var poolParameters = new Pool(name: poolId)
{
VmSize = "STANDARD_D8S_V3",
DeploymentConfiguration = new DeploymentConfiguration
{
VirtualMachineConfiguration = new VirtualMachineConfiguration(
new ImageReference(
"Canonical",
"UbuntuServer",
"18.04-LTS",
"latest"),
"batch.node.ubuntu 18.04")
}
};
var pool = await managementClient.Pool.CreateWithHttpMessagesAsync(
poolName: poolId,
resourceGroupName: batchResourceGroupName,
accountName: batchAccountName,
parameters: poolParameters,
cancellationToken: default(CancellationToken)).ConfigureAwait(false);
I am able to list my subscriptions with the credentials using
IAzure azure = Azure.Authenticate(creds).WithDefaultSubscription();
var subscriptions = azure.Subscriptions.List().ToList();
And I see the subscriptions that this service principal has access to in that list. So I know the credentials are good.
The exception occurs on this line
var pool = await managementClient.Pool.CreateWithHttpMessagesAsync(...
And here is the FULL CODE
using Microsoft.Azure.Management.Batch.Models;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
string subscriptionId = "<subscriptionId>";
string tenantId = "<tenantId>";
string servicePrincipalId = "<servicePrincipalId>";
string servicePrincipalKey = "<servicePrincipalKey>";
var poolId = "test-pool";
var batchResourceGroupName = "<resourceGroupName>";
var batchAccountName = "<batchAccountName>";
var poolParameters = new Pool(name: poolId)
{
VmSize = "STANDARD_D8S_V3",
DeploymentConfiguration = new DeploymentConfiguration
{
VirtualMachineConfiguration = new VirtualMachineConfiguration(
new ImageReference(
"Canonical",
"UbuntuServer",
"18.04-LTS",
"latest"),
"batch.node.ubuntu 18.04")
}
};
var creds = new AzureCredentialsFactory().FromServicePrincipal(servicePrincipalId, servicePrincipalKey, tenantId, AzureEnvironment.AzureGlobalCloud).WithDefaultSubscription(subscriptionId);
var managementClient = new Microsoft.Azure.Management.Batch.BatchManagementClient(creds);
var pool = await managementClient.Pool.CreateWithHttpMessagesAsync(
poolName: poolId,
resourceGroupName: batchResourceGroupName,
accountName: batchAccountName,
parameters: poolParameters,
cancellationToken: default(CancellationToken)).ConfigureAwait(false);
---- UPDATE ----
I added the following line before calling Pool.CreateWithHttpMessageAsync()
managementClient.SubscriptionId = subscriptionId;
And am no longer getting an error regarding null subscriptionId and can now use the management client as expected.

Getting Error while Creating SAS Token for Azure Storage Blob with MSI

I'm trying to create a SAS token for a storage blob. I use a StorageCredentials which was created with MSI (Managed Service Identity) to create the CloudBlobClient. When creating the SAS I'm getting "Cannot create Shared Access Signature unless Account Key credentials are used". Is there support to SAS with MSI?
var container = blobClient.GetContainerReference(containerName);
var blockBlob = container.GetBlockBlobReference(snapname);
var sas = string.Concat(blockBlob.Uri.ToString(), blockBlob.GetSharedAccessSignature(sasConstraints));
This is how I create the StorageCredentials:
tokenCallback = CreateMsiCallback();
var initToken = await tokenCallback(audience);
return new StorageCredentials(
new TokenCredential(initToken, async (state, token) =>
{
var accessToken = await _tokenCallback(audience);
return new NewTokenAndFrequency(accessToken, TimeSpan.FromMinutes(1));
}, null, TimeSpan.FromMinutes(1))
);
To create the token callback I use HttpClient
public Func<string, Task<string>> CreateMsiCallback()
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, certChain, policyErrors) =>
{
if (policyErrors == SslPolicyErrors.None)
{
return true;
}
return 0 == string.Compare(cert.GetCertHashString(), FabricThumbprint, StringComparison.OrdinalIgnoreCase);
}
};
var client = new HttpClient(handler)
{
DefaultRequestHeaders =
{
{"secret", FabricAuthenticationCode }
}
};
return async (resource) =>
{
var requestUri = $"{FabricMsiEndpoint}?api-version={FabricApiVersion}&resource={HttpUtility.UrlEncode(resource)}";
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
var response = await client.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
var tokenResponseString = await response.Content.ReadAsStringAsync();
var tokenResponseObject =
JsonConvert.DeserializeObject<ManagedIdentityTokenResponse>(tokenResponseString);
return tokenResponseObject.AccessToken;
};
}
}
Based on this Github issue, you will need to assign Storage data roles to your MSI in order to generate SAS token. From this thread:
The error is because your oauth account don't have permission to
generateUserDelegationKey. To get SAS with Oauth storage context
(New-AzStorageContext -UseConnectedAuth), we need first generate
UserDelegationKey from server , then use the key to generate the SAS
token.
Please check have you assigned correct roles to the Oauth login user
(with Connect-AzAccount). like at least one of the following 4 roles
on the specific storage account:
Storage Blob Data Owner
Storage Blob Data Contributor
Storage Blob Data Reader
Storage Blob Delegator

Resources