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:
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);
I need to get access to Key Vault and Service Bus from code, using a Service Principle for authentication.
I can use the following code to access Service Bus, which works as expected - when I enable to Service Principle in the Access Policies I can pull the list of topics:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(APPID, APPSECRET, TENANTID, AzureEnvironment.AzureGlobalCloud);
var serviceBusManager = ServiceBusManager.Authenticate(credentials, SUBSCRIPTIONID);
var serviceBusNamespace = serviceBusManager.Namespaces.List().SingleOrDefault(n => n.Name == "SERVICEBUSNAMESPACE");
var topics = serviceBusNamespace.Topics.ListAsync().GetAwaiter().GetResult();
However, I also need to get some information from Key Vault and I was trying to establish a common way to authenticate.
METHOD 1
Similar to the above, I tried this code to access KeyVault:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(APPID, APPSECRET, TENANTID, AzureEnvironment.AzureGlobalCloud);
var kvManager = new KeyVaultClient(credentials);
var secret = kvManager.GetSecretAsync("https://VAULTNAMESPACE.vault.azure.net", "SECRETNAME").GetAwaiter().GetResult().Value;
I get the the following error:
Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: 'Operation
returned an invalid status code 'Unauthorized''
METHOD 2
This code does work for Key Vault however (showing I have correct permissions):
string GetSecret()
{
var client = new KeyVaultClient(GetAccessToken);
var secret = client.GetSecretAsync("https://VAULTNAMESPACE.vault.azure.net", "SECRETNAME").GetAwaiter().GetResult();
return secret;
}
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
var credential = new ClientCredential(appId, appSecret);
var tokenResult = await context.AcquireTokenAsync("https://vault.azure.net", credential);
return tokenResult.AccessToken;
}
But, again, it's a very KeyVault specific way to Authenticate and I was hoping to establish a common mechanism using SdkContext.AzureCredentialsFactory. Any reason why I'd be getting an Unauthorized exception with the code above connecting to Key Vault? (all is set up correctly in Azure).
Thanks for any tips!
When you use SdkContext.AzureCredentialsFactory.FromServicePrincipal to authenticate, it will use https://management.azure.com/ as its Resource Uri.
While Azure Key Vault has its own authorization system and its Resource Uri is https://vault.azure.net, so you may get the Unauthorized error message.
So, you could use Method2 to get access to Azure Key Vault with right Resource Uri.
For more details, you could refer to this article.
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);
I have a requirement to create user or group in azure active directory programmatically. I searched on google and I found multiple solutions like using Graph APIs, C# Code etc.. but I am bit confused with the approach.
Can any one help me out with the difference between these approaches and suggest me the best approach? Please let me know if there are any code samples available.
Thanks in advance !!
Azure ad support multiple protocols. To acquire the token for the Azure AD Graph we need to choose the suitable flow in OAuth 2.0/OpenId connect to interact with Azure AD.
For example, if you were developing a web app the OAuth code grant flow maybe is a good choice. And if the app is daemon app or service application, the client credentials flow the better one. More about the scenarios you can refer this document.
And to acquire the token for Azure AD Graph in a web app, you can refer this code sample. At the line of 104 of this code sample, it acquire the access token for Azure AD Graph. And then in the controller, you can use the code below to acquire the token from cache and create the user using Azure AD Graph:
string graphResourceId = "https://graph.windows.net";
string tenantId = "xxx.onmicrosoft.com";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/xxx.onmicrosoft.com");
ClientCredential credential = new ClientCredential("{clientId}", "{secret}");
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationResult result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
var accessToken = result.AccessToken;
Uri servicePointUri = new Uri(graphResourceId);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
ActiveDirectoryClient graphClient = new ActiveDirectoryClient(serviceRoot, async () => await Task.FromResult(accessToken));
var user = new User();
user.AccountEnabled = true;
user.DisplayName = "testName";
user.UserPrincipalName = "testName#xxx.onmicrosoft.com";
user.MailNickname = "testName";
user.UsageLocation = "US";
user.PasswordProfile = new PasswordProfile
{
Password = "xxxxxx",
ForceChangePasswordNextLogin = true
};
await graphClient.Users.AddUserAsync(user);
And the application requires Directory.ReadWrite.All to create user and group. More detail about the permission you can refer here.