How to enumerate Azure subscriptions and tenants programmatically? This is related to my previous question Login-AzureRmAccount (and related) equivalent(s) in .NET Azure SDK.
Basically I try to replicate the behavior of Login-AzureRmAccount and Get-AzureRmSubscription in desktop or a console application. Thus far I've figured out MSAL seems to always require client ID and tenant ID, so there needs to be some other library to acquire those from. After this I would like to go about creating a service principal programmatically using the most current library, but I suppose that is a subject for further investigation (and questions if needed).
Actually, the Login-AzureRmAccount and Get-AzureRmSubscription use the Microsoft Azure PowerShell app to operate the Azure resource through Resource Manager REST APIs.
To simulate the same operations using REST as PowersShell commands, we can also use this app. However since this app is register on Azure portal(not the v2.0 app) so we are not able to acquire the token using this app via MSAL. We need to use Adal instead of MSAL.
Here is a code sample to list the subscriptions using admin account via Microsoft.WindowsAzure.Management using this app for your reference:
public static void ListSubscriptions()
{
string authority = "https://login.microsoftonline.com/common";
string resource = "https://management.core.windows.net/";
string clientId = "1950a258-227b-4e31-a9cf-717495945fc2";
Uri redirectUri = new Uri("urn:ietf:wg:oauth:2.0:oob");
AuthenticationContext authContext = new AuthenticationContext(authority);
var access_token = authContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters (PromptBehavior.Auto)).Result.AccessToken;
var tokenCred = new Microsoft.Azure.TokenCloudCredentials(access_token);
var subscriptionClient = new SubscriptionClient(tokenCred);
foreach (var subscription in subscriptionClient.Subscriptions.List())
{
Console.WriteLine(subscription.SubscriptionName);
}
}
Update:
string resource = "https://management.core.windows.net/";
string clientId = "1950a258-227b-4e31-a9cf-717495945fc2";
string userName = "";
string password = "";
HttpClient client = new HttpClient();
string tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/token";
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={userName}&password={password}";
var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var result = client.PostAsync(tokenEndpoint, stringContent).ContinueWith<string>((response) =>
{
return response.Result.Content.ReadAsStringAsync().Result;
}).Result;
JObject jobject = JObject.Parse(result);
var token = jobject["access_token"].Value<string>();
client.DefaultRequestHeaders.Add("Authorization", $"bearer {token}");
var subcriptions = client.GetStringAsync("https://management.azure.com/subscriptions?api-version=2014-04-01-preview").Result;
Console.WriteLine(subcriptions);
Related
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
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);
So we have built a set of Azure Functions that are secured by ADFS (MSAL)
We have configured an App in ADFS and got it all working well with our Android client.
We now want to do some API testing so we want to programmatically generate Auth tokens to test the API's
I can't get the following code to work at all, maybe I have the tenant ID wrong, in the App config, its a GUID (42b03d0b-d7f2-403e-b764-0dbdcf0505f6), but examples say it's our domain
string userName = "-";
string password = "-";
string clientId = "ee13c922-bf4b-4f0a-ba39-ea74e1203c6e";
var credentials = new UserPasswordCredential(userName, password);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/acostaonline.onmicrosoft.com");
var result = await authenticationContext.AcquireTokenAsync("https://graph.windows.net", clientId, credentials);
UPDATE
So changed the code to be MSAL and still trying to login via username and password. Now it just times out
string authority = "https://login.microsoftonline.com/42b03d0b-d7f2-403e-b764-0dbdcf0505f6/";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication("ee13c922-bf4b-4f0a-ba39-ea74e1203c6e", authority);
var accounts = await app.GetAccountsAsync();
Microsoft.Identity.Client.AuthenticationResult result = null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
try
{
var securePassword = new SecureString();
foreach (char c in "PASSWORD") // you should fetch the password keystroke
securePassword.AppendChar(c); // by keystroke
result = await app.AcquireTokenByUsernamePasswordAsync(scopes, "AUSER#acosta.com",
securePassword);
}
}
Error
SocketException: A connection attempt failed because the connected
party did not properly respond after a period of time, or established
connection failed because connected host has failed to respond
172.26.200.77:443
It seems that the code you provided is using ADAL instead of MSAL.
The main difference is that with ADAL you would use an AuthenticationContext to acquire tokens, whereas in MSAL you use ConfidentialClientApplication or PublicClientApplication, depending on if the application is running in a back-end or on the user's device.
Here is the article about Differences between ADAL.NET and MSAL.NET applications.
When you use MSAL.Net to get a token for the Microsoft Graph API, you could use the following code:
public static PublicClientApplication PublicClientApp = new
PublicClientApplication(ClientId);
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
authResult = await app.AcquireTokenSilentAsync(_scopes, accounts.FirstOrDefault());
For more details, you could refer to this article, in left menu also includes Android and iOS.
From an Azure Function (FuncA), I want to call another Azure Function (FuncB) in a different function app on behalf of the current user.
I'm using AAD as the authentication provider in both apps. FuncA's app and FuncB's app are using separate App Registrations, and I've added FuncB as a 'Required Permisson' in FuncA's App registration.
I'm attempting to get a bearer token that I can pass to Func B but AcquireTokenAsync fails with 'AADSTS50000: There was an error issuing a token'
Here's the code I'm using:
var oldAuthToken = req.Headers.SingleOrDefault(_ => _.Key == "X-MS-TOKEN-AAD-ID-TOKEN").Value?.FirstOrDefault();
var userAssertion = new UserAssertion(oldAuthToken,
"urn:ietf:params:oauth:grant-type:jwt-bearer",
userName);
var clientId = "<application id of FuncA's App>";
var clientKey = "<Key of FuncA's App>";
var credential = new ClientCredential(clientId, clientKey);
string tenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var authority = $"https://login.microsoftonline.com/{tenantId}/";
var apiIdentifier = "<Func B's application id>";
var authContext = new AuthenticationContext(authority);
var result = await authContext.AcquireTokenAsync(apiIdentifier, credential, userAssertion);
Edit: It works if I use client credential flow (i.e. don't pass userAssertion on the last line of code), but I actually don't want that flow to succeed - I want to control access using the user principal.
The below code works where the authentication works. But when I try to use Service Principle as authentication the authentication fails.
Working Script:
var context = new AuthenticationContext(azureAdUrl + azureADTenant);
var credential = new UserPasswordCredential(azureUsername, azurePassword);
var authParam = new PlatformParameters(PromptBehavior.RefreshSession, null);
var tokenInfo = context.AcquireTokenAsync("https://management.core.windows.net/", azureADClientId, credential);
TokenCloudCredentials tokencreds = new TokenCloudCredentials(subscriptionId, tokenInfo.Result.AccessToken);
ComputeManagementClient computeClient = new ComputeManagementClient(tokencreds);
string deploymentName = computeClient.Deployments.GetBySlot(serviceName, DeploymentSlot.Production).Name;
string label = computeClient.Deployments.GetBySlot(serviceName, DeploymentSlot.Production).Label;
Not Working:
AuthenticationFailed: The JWT token does not contain expected audience
uri 'https://management.core.windows.net/'.
ClientCredential cc = new ClientCredential(applicationClientID, accessKey);
var context = new AuthenticationContext("https://login.windows.net/" + AzureTenantId);
var tokenInfo = context.AcquireTokenAsync("https://management.azure.com/", cc);
tokenInfo.Wait();
if (tokenInfo == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
TokenCloudCredentials tokencreds = new TokenCloudCredentials(subscriptionId, tokenInfo.Result.AccessToken);
ComputeManagementClient computeClient = new ComputeManagementClient(tokencreds);
string deploymentName = computeClient.Deployments.GetBySlot(serviceName, DeploymentSlot.Production).Name;
I don't think it is possible to access classic Azure resources using a Service Principal.
Classic Azure resources are managed via Service Management API that does not have any notion of Service Principal. It only supports tokens when the token is obtained for an Administrator or Co-Administrator.
You would need to use username/password of an actual user to work with Service Management API.
According to your code, I tested it on my side and could encounter the same issue as you provided. And Gaurav Mantri has provided the reasonable answer. AFAIK, for classic Azure Services (ASM), you could refer to Authenticate using a management certificate and upload a management API certificate.
Here is my code snippet, you could refer to it:
CertificateCloudCredentials credential = new CertificateCloudCredentials("<subscriptionId>",GetStoreCertificate("<thumbprint>"));
ComputeManagementClient computeClient = new ComputeManagementClient(credential);
string deploymentName = computeClient.Deployments.GetBySlot("<serviceName>", DeploymentSlot.Production).Name;
Result: