I want to use the Azure API Management (management.core.windows.net) to reboot role instance (ref to Microsoft documentation: https://learn.microsoft.com/en-us/rest/api/compute/cloudservices/rest-reboot-role-instance) but I'm getting a 403 as a response.
Request:
https://management.core.windows.net/{subscription-id}/services/hostedservices/{hosted-service}/deploymentslots/staging/roleinstances/{role-instance-name}?comp=reboot`
Headers:
- Authorization: Bearer {token}
- Content-Type: application/xml
- x-ms-version: 2010-10-28
- Content-Length: 0
Body: Empty
Response Body:
<Error xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Code>ForbiddenError</Code>
<Message>The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.</Message>
</Error>
I get the Authentication - Bearer Token by calling (ref to Microsoft documentation: https://learn.microsoft.com/en-us/azure/active-directory/azuread-dev/v1-oauth2-client-creds-grant-flow#service-to-service-access-token-request):
https://login.microsoftonline.com/{tenant_id}/oauth2/token
Headers:
- Content-Type: application/x-www-form-urlencoded
Body:
- grant_type: client_credentials,
- client_id: {client_id}
- client_secret: {client_secret}
- resource: https://management.core.windows.net/
Any idea? Any missing configuration on the request or Azure Portal side? Is the management.core.windows.net deprecated since I can use the management.azure.com?
Notes:
I already configured the permissions on the Azure side: I created an app registration for this, with a secret used to give permissions as contributor;
The management.azure.com API works with the Bearer Token. I can access other resources such as https://management.azure.com/subscriptions/{subscription-id}/resourcegroups?api-version=2017-05-10 but I can't access the https://management.core.windows.net/{subscription-id}/services/hostedservices resources.
I'm testing this on Postman.
SOLUTION
The problem was related to the certificate configuration
$cert = New-SelfSignedCertificate -Subject "CN=Azure Management API" -CertStoreLocation "cert:\LocalMachine\My" -KeyLength 2048 -KeySpec "KeyExchange" -NotAfter (Get-Date).AddMonths(360)
$password = ConvertTo-SecureString -String "strong-password-here" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\azure-management-api.pfx" -Password $password
Export-Certificate -Type CERT -Cert $cert -FilePath .\azure-management-api.cer
Attention to the importance of the certificate to be a .pfx
CODE
var cert = new X509Certificate2( File.ReadAllBytes( "your-certificate-path.pfx" ), "your_password" );
var httpClientHandler = new HttpClientHandler
{
UseProxy = false,
ClientCertificateOptions = ClientCertificateOption.Manual
};
httpClientHandler.ClientCertificates.Add( cert );
var httpClient = new HttpClient( httpClientHandler );
httpClient.DefaultRequestHeaders.Add( "Accept", "application/xml" );
httpClient.DefaultRequestHeaders.Add( "Host", "management.core.windows.net" );
httpClient.DefaultRequestHeaders.Add( "x-ms-version", "2010-10-28" );
var uri = $"https://management.core.windows.net/{subscriptionId}/services/hostedservices";
Console.WriteLine( $"GET {uri} [{httpClient.DefaultRequestVersion}]" );
foreach ( var header in httpClient.DefaultRequestHeaders )
{
Console.WriteLine( $"{header.Key} {header.Value.First()}" );
}
var response = httpClient.GetAsync( uri )
.GetAwaiter()
.GetResult();
var content = response.Content.ReadAsStringAsync()
.GetAwaiter()
.GetResult();
Console.WriteLine( $"{(int)response.StatusCode} {response.StatusCode}" );
Console.WriteLine( content );
httpClient.Dispose();
httpClientHandler.Dispose();
According to your description, you want to manage Azure cloud service. Azure cloud service is Azure classic resource. So we need to use Azure service management API to manage it. If we want to call the API, we need to do X509 client certificates authentication. For more details, please refer to the document
The detailed steps are as below
Upload certificate to Azure
a. create a certificate
$cert = New-SelfSignedCertificate -DnsName yourdomain.cloudapp.net -CertStoreLocation "cert:\LocalMachine\My" -KeyLength 2048 -KeySpec "KeyExchange"
$password = ConvertTo-SecureString -String "your-password" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\my-cert-file.pfx" -Password $password
Export-Certificate -Type CERT -Cert $cert -FilePath .\my-cert-file.cer
b upload .cer file to Azure(Subscriptions -> your subscription -> Management certificates)
Code (for example, I list cloud service in my subscription )
static async Task Main(string[] args)
{
var _clientHandler = new HttpClientHandler();
_clientHandler.ClientCertificates.Add(GetStoreCertificate("the cert's thumbprint" ));
_clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
String uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices", "subscription id");
using (var _client = new HttpClient(_clientHandler))
using (var request = new HttpRequestMessage(HttpMethod.Get, uri)) {
request.Headers.Add("x-ms-version", "2014-05-01");
request.Headers.Add("Accept", "application/xml");
//request.Headers.Add("Content-Type", "application/xml");
using (HttpResponseMessage httpResponseMessage = await _client.SendAsync(request)) {
string xmlString = await httpResponseMessage.Content.ReadAsStringAsync();
Console.WriteLine(httpResponseMessage.StatusCode);
Console.WriteLine(xmlString);
}
}
}
private static X509Certificate2 GetStoreCertificate(string thumbprint)
{
X509Store store = new X509Store("My", StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certificates = store.Certificates.Find(
X509FindType.FindByThumbprint, thumbprint, false);
if (certificates.Count == 1)
{
return certificates[0];
}
}
finally
{
store.Close();
}
throw new ArgumentException(string.Format(
"A Certificate with Thumbprint '{0}' could not be located.",
thumbprint));
}
Related
I am coding by nodejs and I am referring to this doc :
https://learn.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate
As this doc said , I can get an access token by a JWT token . This doc indicated how to sign a JWT :
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials
but I can't find any demo code about it.So how can I implement it to get an access token to call microsoft graph apis by nodejs ?
Any assistance is appreciated, thanks !
To go through this whole process , we should create certs first. I use self-signed certs for demo here .
Step 1 : Create .cer and .key files, we will upload .cer to Azure AD App and use .key file to sign our JWT tokens.
1) Create a self signed cert which password is 123456 by Powershell :
$cert = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname stantest.com
$pwd = ConvertTo-SecureString -String '123456' -Force -AsPlainText
$path = 'cert:\localMachine\my\' + $cert.thumbprint
Export-PfxCertificate -cert $path -FilePath <path of your pfx file> -Password $pwd
2) Create .cer file based on .pfx file in CMD:
openssl pkcs12 -in <path of .pfx file> -clcerts -nokeys -out <path of .cer>
3) Create .key file based on .pfx file in CMD:
openssl pkcs12 -in <path of .pfx file> -nocerts -nodes -out <path of .pem file>
openssl rsa -in <path of .pem file> -out <path of .key file>
Finally , we will get files below :
STEP 2 : Upload .cer file to your Azure AD app and note its Thumbprint value:
STEP 3 : Use the nodejs code below to sign a JWT and exchange an access token for Microsoft Graph APIs :
var jwt = require("jsonwebtoken");
var fs = require("fs");
var uuidv1 = require('uuid/v1');
var fetch = require("node-fetch");
var tenant = "<your tenant ID/Name>";
var clientID = "<your Azure AD app ID>";
var certThumbprint = "<.cer Thumbprint value on Azure portal>";
var privateKey = fs.readFileSync("<path of your .key file>").toString();
var certOctets = certThumbprint.match(/.{1,2}/g)
var certBuffer = Buffer.alloc(certOctets.length)
for(var i=0; i<certOctets.length; i++){
certBuffer.writeUInt8(parseInt(certOctets[i], 16), i);
}
//Perform base64url-encoding as per RFC7515 Appendix C
var x5t = certBuffer.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
var current = Date.now().toString().substr(0,10);
var payload=
{
"aud":"https://login.microsoftonline.com/"+tenant+"/oauth2/token",
"exp": Number(current) + 3600,
"iss":clientID,
"jti":uuidv1(),
"nbf":Number(current),
"sub":clientID
}
var token = jwt.sign(payload,privateKey,{algorithm: 'RS256',header: {"x5t": x5t}})
var reqTokenBody =
"grant_type=client_credentials&"+
"client_id="+clientID + "&" +
"resource=https://graph.microsoft.com&"+
"client_assertion="+ token +"&" +
"client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
fetch("https://login.microsoftonline.com/hanxia.onmicrosoft.com/oauth2/token",
{
method: 'POST',
headers:
{
'Content-Type': 'application/x-www-form-urlencoded',
},
body:reqTokenBody,
}).then((response) => response.json()).then((data) =>
{
console.log(JSON.stringify(data, null, 2));
}).catch((error) =>
{
console.log(error);
});
Result :
Hope it helps.
I have a certificate associated with a service principal in Azure AD. How can I get the certificate name or thumbprint associated with it using powershell?
I have tried Get-AzureRmADServicePrincipalCredential, Get-AzureRmADSpCredential and Get-AzureADServicePrincipalKeyCredential commands but they return Key Identifier not thumbprint.
Basically I want to recognize which certificate is associated with the principal before revoking it.
As #Stanley Gong mentioned, you can use MS Graph to get it.
Here is another way, try the command as below, the $Thumbprint is that you want.
Note the <object-id> is the object id of your AD App(App registration), not the service principal(Enterprise application), they are different.
$CustomKeyIdentifier = (Get-AzureADApplicationKeyCredential -ObjectId "<object-id>").CustomKeyIdentifier
$Thumbprint = [System.Convert]::ToBase64String($CustomKeyIdentifier)
Try the PS command below to get cert thumbprint via Microsoft Graph API :
$clientId = "<your Azure AD App ID>"
$clientSec="<your Azure AD App Secret>"
$appObjId = "<object ID of the app that you want to query>"
$tenant = "<your tenant ID>"
$body=#{
"grant_type"="client_credentials";
"resource"="https://graph.microsoft.com/";
"client_id"= $clientId;
"client_secret" = $clientSec
}
$accessToken=(Invoke-RestMethod -Uri "https://login.windows.net/$tenant/oauth2/token" -Method POST -Body $body ).access_token
$keyCreds = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/applications/$appObjId/keyCredentials" -Method Get -Headers #{"Authorization" = "Bearer $accessToken"}
$keyCreds.value.customKeyIdentifier
Result:
my certs on portal :
query result :
Pls note that make sure your app which you used for getting token with permission below so it can call Microsoft graph API to query your apps :
I'm building a set of scripts and templates to create a Service Fabric cluster in Azure. I've got a script that creates a key vault and a self-signed certificate and successfully uploads it to the vault. Another script creates the cluster but it's hitting an error at the point that the certs are linked to the VMs. The error from the New-AzureRmResourceGroupDeployment command is:-
{
"status": "Failed",
"error": {
"code": "ResourceDeploymentFailure",
"message": "The resource operation completed with terminal provisioning state 'Failed'.",
"details": [
{
"code": "KeyVaultAccessForbidden",
"message": "Key Vault https://VAULT-NAME.vault.azure.net/secrets/clusterCert/SECRET-ID either has not been enabled for deployment or the vault id provided, /subscriptions/SUBSCRIPTION-ID/resourceGroups/jg-sf/providers/Microsoft.KeyVault/vaults/VAULTNAME, does not match the Key Vault's true resource id."
}
]
}
}
VAULT-NAME, SUBSCRIPTION-ID and SECRET-ID are all correct. The key vault has been created with the parameter "enabledForTemplateDeployment": true, as evidenced in the following screenshot.
My scripts and templates can be seen in GitHub - https://github.com/goochjs/azure-testbed.
How do I diagnose the issue?
Thanks,
Jeremy.
How do you create the key vault, I use the following script to create key vault and get CertificateURL.
New-AzureRmKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -Location $Location -sku standard -EnabledForDeployment
#Creates a new selfsigned cert and exports a pfx cert to a directory on disk
$NewCert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -DnsName $CertDNSName
Export-PfxCertificate -FilePath $CertFileFullPath -Password $SecurePassword -Cert $NewCert
Import-PfxCertificate -FilePath $CertFileFullPath -Password $SecurePassword -CertStoreLocation Cert:\LocalMachine\My
#Reads the content of the certificate and converts it into a json format
$Bytes = [System.IO.File]::ReadAllBytes($CertFileFullPath)
$Base64 = [System.Convert]::ToBase64String($Bytes)
$JSONBlob = #{
data = $Base64
dataType = 'pfx'
password = $Password
} | ConvertTo-Json
$ContentBytes = [System.Text.Encoding]::UTF8.GetBytes($JSONBlob)
$Content = [System.Convert]::ToBase64String($ContentBytes)
#Converts the json content a secure string
$SecretValue = ConvertTo-SecureString -String $Content -AsPlainText -Force
#Creates a new secret in Azure Key Vault
$NewSecret = Set-AzureKeyVaultSecret -VaultName $KeyVaultName -Name $KeyVaultSecretName -SecretValue $SecretValue -Verbose
#Writes out the information you need for creating a secure cluster
Write-Host
Write-Host "Resource Id: "$(Get-AzureRmKeyVault -VaultName $KeyVaultName).ResourceId
Write-Host "Secret URL : "$NewSecret.Id
Write-Host "Thumbprint : "$NewCert.Thumbprint
More information about this, please refer to this blog.
I suggest you could check your Resource Id format. The correct format is like /subscriptions/***************/resourceGroups/westus-mykeyvault/providers/Microsoft.KeyVault/vaults/shuisfsvault. You could create SF cluster on Azure Portal firstly.
If it still does not work, I suggest you could check your key vault, do you give enough permission to it?
Note: For test, you could give all permission to the user.
I am trying to script an environment using the Azure cli. I have created a few function apps and would like to add a host key or at least retrieve the default one that is created automatically. The azure cli has no support at all for this.
There seems to be an api (documentation for it seems to be sparse) on the function itself that allows me to get the keys, however you need a key to use it so.. no help there.
https://github.com/Azure/azure-webjobs-sdk-script/wiki/Key-management-API
Eg: https://example-functions.azurewebsites.net/admin/host/keys?code=somecodeyoualreadyknow
I have seen some other examples that use the webapps scm api to download the json file that contains the keys however I'm not sure how to authenticate with this API. I have a service principal (userid, password, tenantid) and I was hoping to not have to add another authentication scheme to my script.
I was just able to make this work with the Azure CLI using this command:
az rest --method post --uri \
"/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/sites/$FUNCTION_APP_NAME/host/default/listKeys?api-version=2018-11-01" \
--query functionKeys.default --output tsv
I realize this is a couple years late on the answer, but it might help people who are searching now.
Here are the steps.
Assuming you already have your Kudu deployment credentials. (it sounds like you already know how to do this. You can get it via an ARM call from your service principle, etc)
From kudu deployment creds, you can get a JWT that lets you call the Functions key API.
From the Functions API, you can get all your keys (including your master).
Here's a powershell script that demonstrates the exact calls to go from Kudu deployment creds to Function Master key:
# You need to start with these:
$site = "YourSiteName"
$username='YourDeploymentUserName'
$password='YourDeploymentPassword'
# Now...
$apiBaseUrl = "https://$($site).scm.azurewebsites.net/api"
$siteBaseUrl = "https://$($site).azurewebsites.net"
# For authenticating to Kudu
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
# Call Kudu /api/functions/admin/token to get a JWT that can be used with the Functions Key API
$jwt = Invoke-RestMethod -Uri "$apiBaseUrl/functions/admin/token" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET
# Call Functions Key API to get the master key
$x = Invoke-RestMethod -Uri "$siteBaseUrl/admin/host/systemkeys/_master" -Headers #{Authorization=("Bearer {0}" -f $jwt)} -Method GET
$masterKey = $x.value
I do not know how to get "kudu" credentials with my service principal credentials
If C# code is acceptable, we could use Microsoft.Azure.Management.ResourceManager.Fluent and Microsoft.Azure.Management.Fluent to do that easily. The following is the demo that how to get kudu credentials and run Key management API .I test it locally, it works correctly on my side.
string clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync().Result;
}
If you just want to get the keys and don't need to automate the authentication process:
Get-AzResource -Name RESOURCE-NAME | Invoke-AzResourceAction -Action host/default/listkeys -Force
Thanks both for your replies. Using your answer Mike S and rummaging around the csharp fluent source code (thanks Tom Sun) I ended up with this. Sure do need a lot of tokens! The credentials I start with are what you would get back from az ad sp create-for-rbac -n $name --role contributor
$credentials = (ConvertFrom-Json $env:AzureCliLogin)
$tenant = $credentials.tenant
$clientId = $credentials.appId
$clientSecret = $credentials.password
$subscriptionId = "<subscription id>"
$body = #{
"grant_type"="client_credentials";
"client_id"=$clientId;
"client_secret"=$clientSecret;
"resource"="https://management.azure.com/"
}
$authInfo = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenant/oauth2/token" -Body $body -Method Post -Headers #{"Content-Type"="application/x-www-form-urlencoded"}
$publishData = Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/$name/publishxml?api-version=2016-08-01" -Method Post -Headers #{"Authorization"="Bearer $($authInfo.access_token)"}
$userName = $publishData.publishData.publishProfile[0].userName
$password = $publishData.publishData.publishProfile[0].userPWD
$apiBaseUrl = "https://$name.scm.azurewebsites.net/api"
$siteBaseUrl = "https://$name.azurewebsites.net"
# For authenticating to Kudu
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
# Call Kudu /api/functions/admin/token to get a JWT that can be used with the Functions Key API
$jwt = Invoke-RestMethod -Uri "$apiBaseUrl/functions/admin/token" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET
# Call Functions Key API to get the master key
$x = Invoke-RestMethod -Uri "$siteBaseUrl/admin/host/systemkeys/_master" -Headers #{Authorization=("Bearer {0}" -f $jwt)} -Method GET
$masterKey = $x.value
If you want to do this in bash, see this gist for a start
Make sure you have the latest version of the Az Module.
Install:
Install-Module -Name Az -Force
or
Update:
Update-Module -Name Az
Be sure and launch a new PowerShell window after running one of the above commands.
Then you can just run the following after you set up your resource group name and function name variables:
$azureFunction = Get-AzFunctionApp -ResourceGroupName $resourceGroupName -Name $azureFunctionName
$keys = Invoke-AzResourceAction -ResourceId $($azureFunction.Id) -Action "host/default/listKeys" -Force
$defaultKey = $keys.functionKeys.default
I was trying to Create a Application in Azure AD with Azure PowerShell Certificate authentication, below is the Powershell snippet:
Login-AzureRmAccount
$certPassword = ConvertTo-SecureString $CertPassword -AsPlainText -Force
$x509 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certPath,$certPassword
$credValue = [System.Convert]::ToBase64String($x509.GetRawCertData())
$adapp = New-AzureRmADApplication -DisplayName $ApplicationName -HomePage $URL -IdentifierUris $URL -CertValue $credValue -StartDate $startDate -EndDate $endDate
$sp = New-AzureRmADServicePrincipal -ApplicationId $adapp.ApplicationId
Set-AzureRmKeyVaultAccessPolicy -VaultName $VaultName -ServicePrincipalName $sp.ServicePrincipalNames[1] -PermissionsToKeys all –PermissionsToSecrets all -ResourceGroupName $ResourceGroupName
The Azure AD application was created successfully, however for Azure AD application with Certificate Authentication, the customKeyIdentifier and value of in the keyCredentials is null after creation, this is the portion of manifest of my application I downloaded from Azure portal:
"keyCredentials": [{
"customKeyIdentifier": null,
"endDate": "2018-01-25T11:55:35.7680698Z",
"keyId": "ca1e536c-2220-478b-af73-1198d125bb5f",
"startDate": "2017-01-25T11:55:35.7680698Z",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": null
} ]
The certificate is a self signed certificate created using makecert command generated locally.
I am using Powershell Version of 2.0.1
C# Code to retrieve the token with Application Id & Thumbprint
public static async Task GetAccessToken(string authority,
string resource, string scope) {
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, AssertionCert);
return result.AccessToken; }
This Code errors out at var result with "Keyset does not exists"
Is there any way to resolve this issue?
Thank you :)
Did you look at the answer here?
Create a Application in Azure AD with Azure PowerShell Certificate authentication
In the comments he mentions that CustomKeyIdentifier being null does not matter for authentication.
Did you try authenticating regardless of the null value?\
EDIT:
If you want to generate a thumbprint for a public certificate you own, you can do so using the following powershell cmdlets:
$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cer.Import(“mycer.cer”)
$bin = $cer.GetCertHash()
$base64Thumbprint = [System.Convert]::ToBase64String($bin)
I hope this helps.