Error when calling any method on Service Management API - c#

I'm looking to start an Azure runbook from a c# application which will be hosted on an Azure web app.
I'm using certificate authentication (in an attempt just to test that I can connect and retrieve some data)
Here's my code so far:
var cert = ConfigurationManager.AppSettings["mgmtCertificate"];
var creds = new Microsoft.Azure.CertificateCloudCredentials("<my-sub-id>",
new X509Certificate2(Convert.FromBase64String(cert)));
var client = new Microsoft.Azure.Management.Automation.AutomationManagementClient(creds, new Uri("https://management.core.windows.net/"));
var content = client.Runbooks.List("<resource-group-id>", "<automation-account-name>");
Every time I run this, no matter what certificate I use I get the same error:
An unhandled exception of type 'Hyak.Common.CloudException' occurred in Microsoft.Threading.Tasks.dll
Additional information: ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I've tried downloading the settings file which contains the automatically generated management certificate you get when you spin up the Azure account... nothing I do will let me talk to any of the Azure subscription
Am I missing something fundamental here?
Edit: some additional info...
So I decided to create an application and use the JWT authentication method.
I've added an application, given the application permissions to the Azure Service Management API and ensured the user is a co-administrator and I still get the same error, even with the token...
const string tenantId = "xx";
const string clientId = "xx";
var context = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantId));
var user = "<user>";
var pwd = "<pass>";
var userCred = new UserCredential(user, pwd);
var result = context.AcquireToken("https://management.core.windows.net/", clientId, userCred);
var token = result.CreateAuthorizationHeader().Substring("Bearer ".Length); // Token comes back fine and I can inspect and see that it's valid for 1 hour - all looks ok...
var sub = "<subscription-id>";
var creds = new TokenCloudCredentials(sub, token);
var client = new AutomationManagementClient(creds, new Uri("https://management.core.windows.net/"));
var content = client.Runbooks.List("<resource-group>", "<automation-id>");
I've also tried using other Azure libs (like auth, datacentre etc) and I get the same error:
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I'm sure it's just 1 tickbox I need to tick buried somewhere in that monolithic Management Portal but I've followed a few tutorials on how to do this and they all end up with this error...

public async Task StartAzureRunbook()
{
try
{
var subscriptionId = "azure subscription Id";
string base64cer = "****long string here****"; //taken from http://stackoverflow.com/questions/24999518/azure-api-the-server-failed-to-authenticate-the-request
var cert = new X509Certificate2(Convert.FromBase64String(base64cer));
var client = new Microsoft.Azure.Management.Automation.AutomationManagementClient(new CertificateCloudCredentials(subscriptionId, cert));
var ct = new CancellationToken();
var content = await client.Runbooks.ListByNameAsync("MyAutomationAccountName", "MyRunbookName", ct);
var firstOrDefault = content?.Runbooks.FirstOrDefault();
if (firstOrDefault != null)
{
var operation = client.Runbooks.Start("MyAutomationAccountName", new RunbookStartParameters(firstOrDefault.Id));
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Also in portal:
1) Application is multitenant
2) Permissions to other applications section - Windows Azure Service Manager - Delegated permissions "Access Azure Service Management(preview)"

Ensure that your Management certificate has private key and was not made from the .CER file. The fact that you're not supplying a password when generating the X509Certificate object makes me think you're using public key only
Ensure that your Managemnet's certificate public key (.CER file) has been uploaded to the Azure management portal (legacy version, Management Certificate area)
Use CertificateCloudCredentials and not any other credential type of an object

Ok, stupid really but one of the tutorials I followed suggested installing the prerelease version of the libs.
Installing the preview (0.15.2-preview) has fixed the issue!

Related

Unable to fetch secret value from Key Vault using system managed identity in C# Azure Function

I had enabled system assigned managed identity in azure function(Service bus topic trigger) and added the identity(Object (principal) ID
) in key vault access policy with "Get,List" permissions of secrets, keys. I added the reference of the Key Vault into Azure function Application settings and able to receive at runtime after azure function deployment.
#Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931)
Code:
private async Task<string> FetchSecretValueFromKeyvault(string secretName)
{
_logger.LogInformation($"FetchSecretValueFromKeyvault: SecretName {secretName}");
string actualSecret = string.Empty;
try
{
string systemAssignedClientId = GetEnvironmentVariable("AzureADManagedIdentityClientId");
string azureKeyVaultUrl = GetEnvironmentVariable("AzureKeyVaultUrl");
var defaultAzureCredentialOptions = new DefaultAzureCredentialOptions();
defaultAzureCredentialOptions.ExcludeAzureCliCredential = true;
defaultAzureCredentialOptions.ExcludeEnvironmentCredential = true;
defaultAzureCredentialOptions.ExcludeAzurePowerShellCredential = true;
defaultAzureCredentialOptions.ExcludeInteractiveBrowserCredential = true;
defaultAzureCredentialOptions.ExcludeManagedIdentityCredential = false;
defaultAzureCredentialOptions.ExcludeSharedTokenCacheCredential = true;
defaultAzureCredentialOptions.ExcludeVisualStudioCodeCredential = true;
defaultAzureCredentialOptions.ExcludeVisualStudioCredential = true;
defaultAzureCredentialOptions.ManagedIdentityClientId = systemAssignedClientId;
var credential = new DefaultAzureCredential(defaultAzureCredentialOptions);
var client = new SecretClient(new Uri(AzureKeyVaultUrl)), credential);
var secret = await client.GetSecretAsync(secretName).ConfigureAwait(false);
actualSecret = secret.Value.Value;
_logger.LogInformation($"FetchSecretValueFromKeyvault: Received secretValue for {secretName}");
}
catch (RequestFailedException ex)
{
actualSecret = string.Empty;
_logger.LogError($"Message: {ex.Message}. \nInnerException:{ex.InnerException}. \nStackTrace: {ex.StackTrace}. \nInnerExceptionMessage:{ex.InnerException?.Message}.");
}
catch (Exception ex)
{
actualSecret = string.Empty;
_logger.LogError($"Message: {ex.Message}. \nInnerException:{ex.InnerException}. \nStackTrace: {ex.StackTrace}. \nInnerExceptionMessage:{ex.InnerException?.Message}.");
}
return actualSecret;
}
local settings & Azure Function App Settings:
"AzureADManagedIdentityClientId": "xxx-123-abc-xyz-567890"
"AzureKeyVaultUrl": "https://keyvaulturl.azurewebsites.net",
Nuget package and its versions:
Azure.Security.KeyVault.Secrets -- 4.3.0
Azure.Extensions.AspNetCore.Configuration.Secrets -- 1.2.2
Azure.Identity -- 1.6.1
Function Runtime Version: .NET Core V3.1
I am trying to read same secret value through code with help of same managed identity, I am getting error ManagedIdentityCredential authentication unavailable. Multiple attempts failed to obtain a token from the managed identity endpoint.' while debugging in local machine. I deployed azure function and in application insights, I am getting No Managed Identity found for specified ClientId/ResourceId/PrincipalId. Status: 400 (Bad Request)
I double cross checked PrincipalId, Its existed in both local seetings , azure function app settings and value is correct.
what am i doing wrong?
Please check :
When deployed to Azure resource that actually supports managed identity, the library automatically uses managed identities for Azure resources
Local machines does not support managed identities for Azure resources.
So in local environment Microsoft.Azure.Services.AppAuthentication library uses the developer credentials.
For local development, AzureServiceTokenProvider tries to fetch tokens first using Visual Studio, and then using Azure command-line interface (CLI), or Azure AD Integrated Authentication.
You can try to use connection string specified in the AzureServicesAuthConnectionString environment variable that can be passed to the AzureServiceTokenProvider .
You can use visual studio or azure cli method to pass to azure service authentication.
Using azure cli , you can create service principal rbac for local testing something like
az ad sp create-for-rbac --name local-sp --skip-assignment.
To use a connection string passed to the AzureServiceTokenProvider in the AzureServicesAuthConnectionString environment variable for Local development which uses AzureCli to get token.
`RunAs=Developer; DeveloperTool=AzureCli`
You may then have to add the service principal localtest-sp in the access control (IAM ) for the required Azure services.
Using the DefaultAzureCredential in Azure.Identity will provide some sort of similar type functionality to AzureServiceTokenProvider in AppAuthentication, where current environment can be changed.
Please check this App Authentication client library for .NET | Microsoft Docs to try for other options using secret or certificate.
Snippets from AppAuthentication
Using AppAuthentication library :
AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider();
var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
...
Using Azure.Identity library :
var client = new SecretClient(new Uri("https://keyvaultname.vault.azure.net"), new
DefaultAzureCredential());
var secret = client.GetSecret("secretName").Value;
For Access token retrival
Using AppAuthentication library:
var tokenProvider = new AzureServiceTokenProvider();
var accessToken = await tokenProvider.GetAccessTokenAsync(ResourceId);
Using Azure.Identity library:
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { ResourceId + "/.default" }) { }
);
Reference :
python - ManagedIdentityCredential authentication unavailable, no managed identity endpoint found - Stack Overflow

Creating an Azure DevOPS Personal Access Token (PAT) using C#

I am trying to create a PAT using the new capabilities in the TokensHttpClient. However I keep getting authorisation exception. I am using my Microsoft account which is an organization administrator.
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
// Connect to Azure DevOps Services
VssConnection connection = new VssConnection(_uri, creds);
connection.ConnectAsync().SyncResult();
var t = connection.GetClient<TokenAdminHttpClient>();
//next line works as expected
var tokens = t.ListPersonalAccessTokensAsync(connection.AuthorizedIdentity.SubjectDescriptor).Result;
var tokenAdmin = connection.GetClient<TokensHttpClient>();
PatTokenCreateRequest createRequest = new PatTokenCreateRequest();
createRequest.DisplayName = "Niks_Api_Token";
createRequest.Scope = "vso.work_full";
createRequest.ValidTo = DateTime.Now.AddYears(1);
//this is where authorization exception occurs
var result = tokenAdmin.CreatePatAsync(createRequest).Result;
To manage personal access tokens with APIs, you must authenticate with an Azure AD token. Azure AD tokens are a safer authentication mechanism than using PATs. Given this API’s ability to create and revoke PATs, we want to ensure that such powerful functionality is given to allowed users only.
Please check the Prerequisites here.
Make sure your org has been connect to AAD, see here.
Please register an application in Azure AD, make sure the client secret has been created. You can refer to this doc. And add the permission of Azure DevOps.
The sample code to get Azure AD access token.
public static async Task<string> GetAccessTokenAsyncByClientCredential()
{
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(<appId/clientId>)
.WithTenantId(<tenantId>)
.WithClientSecret(<clientSecret>)
.Build();
string[] scopes = new string[] { "499b84ac-1321-427f-aa17-267ca6975798/.default" };
var result = await cca.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}

azure deploy multiple site's instance on demand

I have a website called www.Request.com, when users access this site they will be able to request the creation of a new instance of another website that is already deployed in AZURE with the name www.MyTechnicalApp.com
for example when I access to www.Request.com I will request the creation of MyTechnicalApp for my company called "MyCompany", it's supposed that there is a script that will be executed by request.com website to create automatically www.MyCompany.MyTechnicalApp.com website.
would you please let me know how could I do that?
According to your description, to create a web app on Azure automatically, there are two ways to achieve this.
One: using "Windows Azure Management Libraries", this SDK is a wrapper around "Azure Service Management" API.
First, we need to do authentication with ASM API and we can refer to: Windows Azure Management Librairies : Introduction et authentification, then we will be able to create a website with something like this:
using (var AwsManagement = new Microsoft.WindowsAzure.Management.WebSites.WebSiteManagementClient(azureCredentials))
{
WebSiteCreateParameters parameters = new WebSiteCreateParameters()
{
Name = "myAws",
// this Service Plan must be created before
ServerFarm = "myServiceplan",
};
await AwsManagement.WebSites.CreateAsync("myWebSpace", parameters, CancellationToken.None);
}
Two: We can create a web site by using a POST request that includes the name of the web site and other information in the request body. We can check the code example for azure-sdk-for-net
use this link to get the credentials Authentication in Azure Management Libraries for Java.
https://github.com/Azure/azure-libraries-for-java/blob/master/AUTH.md
The below link helped me to find the answer.
static void Main(string[] args)
{
try
{
var resourceGroupName = "your ressource group name";
var subId = "64da6c..-.......................88d";
var appId = "eafeb071-3a70-40f6-9e7c-fb96a6c4eabc";
var appSecret = "YNlNU...........................=";
var tenantId = "c5935337-......................19";
var environment = AzureEnvironment.AzureGlobalCloud;
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(appId, appSecret, tenantId, AzureEnvironment.AzureGlobalCloud);
var azure = Microsoft.Azure.Management.Fluent.Azure
.Configure()
.Authenticate(credentials)
.WithSubscription(subId);
azure.AppServices.WebApps.Inner.CreateOrUpdateHostNameBindingWithHttpMessagesAsync(resourceGroupName, "WebSiteName", "SubDomainName",
new HostNameBindingInner(
azureResourceType: AzureResourceType.Website,
hostNameType: HostNameType.Verified,
customHostNameDnsRecordType: CustomHostNameDnsRecordType.CName)).Wait();
}
catch (Exception ex)
{
}
}

How to find the SharePoint Path from Office 365 using OAuth2 Client Credential Flow

Problem
I have been trying to figure out how to find a SharePoint path for a user when using OAuth2 Client Credential Flow (where an application has permission to read all users' SharePoint files using an Office 365 administrator's one-time acceptance)
I have my client application setup in Azure and am able to read files if I hard-code the SharePoint URL - so I know it is setup correctly.
But I need to "discover" the SharePoint URL so it will be change-tolerant and reusable across customers.
Related Articles:
Different OAuth2 Flows
Using OAuth2 Flow for Exchange
Code
var azureAdAuthority = "https://login.windows.net/{tenant-id}/oauth2/authorize".Replace("{tenant-id}", tenantId);
var discoveryUri = "https://api.office.com/discovery/v1.0/me/";
var discoveryResourceUri = "https://api.office.com/discovery/";
// discover contact endpoint
var cert = new X509Certificate2(certFilePath, certFilePassword, X509KeyStorageFlags.MachineKeySet);
var clientAssertion = new ClientAssertionCertificate(clientId, cert);
var userIdentifier = new UserIdentifier(userObjectId, UserIdentifierType.UniqueId);
var userAssertion = new UserAssertion(userObjectId);
// create auth context
var authContext = new AuthenticationContext(azureAdAuthority, false);
// create O365 discovery client
var discovery = new DiscoveryClient(new Uri(discoveryUri),
() => authContext.AcquireTokenSilent(discoveryResourceUri, clientAssertion, userIdentifier).AccessToken);
// query discovery service for endpoint for 'calendar' endpoint
var dcr = await discovery.DiscoverCapabilityAsync("MyFiles");
This and many other variations throw exceptions from the AcquireTokenSilent function.
If I don't use a "userIdentifier" and call the AcquireToken function it succeeds, but the DiscoverCapabilityAsync function fails.

Google Analytics Api on Azure

I want to use the google analytics api in my MVC website, im authenticating using the api service account and oauth2 with have no issues on my localhost but as soon as I deploy to Azure i get a 502 error:
"502 - Web server received an invalid response while acting as a
gateway or proxy server. There is a problem with the page you are
looking for, and it cannot be displayed. When the Web server (while
acting as a gateway or proxy) contacted the upstream content server,
it received an invalid response from the content server."
heres my code:
const string ServiceAccountUser = "xxxxxxxxxx-cpla4j8focrebami0l87mbcto09j9j6k#developer.gserviceaccount.com";
AssertionFlowClient client = new AssertionFlowClient(
GoogleAuthenticationServer.Description,
new X509Certificate2(System.Web.Hosting.HostingEnvironment.MapPath("/Areas/Admin/xxxxxxxxxxxxxxxxxx-privatekey.p12"),
"notasecret", X509KeyStorageFlags.Exportable))
{
Scope = AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue(),
ServiceAccountId = ServiceAccountUser //Bug, why does ServiceAccountUser have to be assigned to ServiceAccountId
//,ServiceAccountUser = ServiceAccountUser
};
OAuth2Authenticator<AssertionFlowClient> authenticator = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
I cant figure out whats causing it? Am im missing something within Azure?
Thanks for any help.
I also ran into the same issue but passing X509KeyStorageFlags.MachineKeySet into the constructor as well fixed the issue for me.
X509Certificate2 certificate = new X509Certificate2(file, "key", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
After hours of pain on this exact same problem, I found a work around by piecing together various sources of info.
The problem arises from trying to read the p12 file from the Azure web site, i.e. this line in my code fails
var key = new X509Certificate2(keyFile, keyPassword, X509KeyStorageFlags.Exportable);
No idea why, but it works if you split the file into a cer and key.xml file?
Firstly, extract these files, (I just used a console app)
// load pfx/p12 as "exportable"
var p12Cert = new X509Certificate2(#"c:\Temp\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
// export .cer from .pfx/.p12
File.WriteAllBytes(#"C:\Temp\MyCert.cer", p12Cert.Export(X509ContentType.Cert));
// export private key XML
string privateKeyXml = p12Cert.PrivateKey.ToXmlString(true);
File.WriteAllText(#"C:\Temp\PrivateKey.xml", privateKeyXml);
Then copy them to your website then load them in like so
//Store the authentication description
AuthorizationServerDescription desc = GoogleAuthenticationServer.Description;
//Create a certificate object to use when authenticating
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
rsaCryptoServiceProvider.FromXmlString(File.ReadAllText(keyFile));
var key = new X509Certificate2(certFile) {PrivateKey = rsaCryptoServiceProvider};
//Now, we will log in and authenticate, passing in the description
//and key from above, then setting the accountId and scope
var client = new AssertionFlowClient(desc, key)
{
//cliendId is your SERVICE ACCOUNT Email Address from Google APIs Console
//looks something like 12345-randomstring#developer.gserviceaccount.com
//~IMPORTANT~: this email address has to be added to your Google Analytics profile
// and given Read & Analyze permissions
ServiceAccountId = clientId,
Scope = "https://www.googleapis.com/auth/analytics.readonly"
};
//Finally, complete the authentication process
//NOTE: This is the first change from the update above
var auth = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
//First, create a new service object
//NOTE: this is the second change from the update
//above. Thanks to James for pointing this out
var gas = new AnalyticsService(new BaseClientService.Initializer { Authenticator = auth });
This now works for me and I hope it helps you.

Categories

Resources