Add PFX Certificate to Azure WebApp using ARM (not for ssl) - azure

Using Rest Management APIS that are backed by Azure Resource Manager the following code adds a certificate from keyvault to ARM.
var secret = keyvaultClient.GetSecretAsync(vaultUri, options.CertificateName).GetAwaiter().GetResult();
var certUploaded = client.Certificates.CreateOrUpdateCertificateWithHttpMessagesAsync(
options.ResourceGroupName, options.CertificateName,
new Certificate {
PfxBlob = secret.Value,
Location = app.Body.Location
}).GetAwaiter().GetResult();
var appSettings = client.Sites.ListSiteAppSettingsWithHttpMessagesAsync(options.ResourceGroupName, options.WebAppName).GetAwaiter().GetResult();
var existing = (appSettings.Body.Properties["WEBSITE_LOAD_CERTIFICATES"] ?? "").Split(',').ToList();
if (!existing.Contains(certUploaded.Body.Thumbprint))
existing.Add(certUploaded.Body.Thumbprint);
appSettings.Body.Properties["WEBSITE_LOAD_CERTIFICATES"] = string.Join(",",existing);
appSettings.Body.Properties[$"CN_{options.CertificateName}"] = certUploaded.Body.Thumbprint;
var result = client.Sites.UpdateSiteAppSettingsWithHttpMessagesAsync(options.ResourceGroupName, options.WebAppName, appSettings.Body).GetAwaiter().GetResult();
the problem is that when loading it in the webapp
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
// Replace below with your cert's thumbprint
"0CE28C6246317AEB00B88C88934700865C71CBE0",
false);
Trace.TraceError($"{certCollection.Count}");
Console.WriteLine($"{certCollection.Count}");
// Get the first cert with the thumbprint
if (certCollection.Count > 0)
{
X509Certificate2 cert = certCollection[0];
// Use certificate
Console.WriteLine(cert.FriendlyName);
}
certStore.Close();
it is not loaded.
If I instead upload it using the portal, everything works as expected.
I also noticed that the certificates uploaded in the portal do not exist in ARM, only the certs added with the code in the start of the post exists.:
So what do we need to do to make a certificate available to webapp that do not involve manual uploading to portal?

The problem was that the certificates should be added to the resource group of the serverfarm that the webapp is hosted within and not the resourcegroup of the webapp.
Changing the code to deploy to the correct resourcegroup solved everything.
For reference my updated code is here:
var vaultUri = $"https://{options.VaultName}.vault.azure.net";
var keyvaultClient = new KeyVaultClient((_, b, c) => Task.FromResult(options.VaultAccessToken));
using (var client = new WebSiteManagementClient(
new TokenCredentials(cred.AccessToken)))
{
client.SubscriptionId = cred.SubscriptionId;
var app = client.Sites.GetSite(options.ResourceGroupName, options.WebAppName);
var serverFarmRG = Regex.Match(app.ServerFarmId, "resourceGroups/(.*?)/").Groups[1];
var secret = keyvaultClient.GetSecretAsync(vaultUri, options.CertificateName).GetAwaiter().GetResult();
var certUploaded = client.Certificates.CreateOrUpdateCertificate(
serverFarmRG.Value, options.CertificateName,
new Certificate
{
PfxBlob = secret.Value,
Location = app.Location
});
var appSettings = client.Sites.ListSiteAppSettings(options.ResourceGroupName, options.WebAppName);
appSettings.Properties["WEBSITE_LOAD_CERTIFICATES"] = string.Join(",", client.Certificates.GetCertificates(serverFarmRG.Value).Value.Select(k => k.Thumbprint));
appSettings.Properties[$"CN_{options.CertificateName}"] = certUploaded.Thumbprint;
var result = client.Sites.UpdateSiteAppSettings(options.ResourceGroupName, options.WebAppName, appSettings);

Related

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

Retreive the host key from within my azure function

To read an Application setting in Azure function I can do
Environment.GetEnvironmentVariable("MyVariable", EnvironmentVariableTarget.Process);
Is it possible to get a Host key in a similar way? I like to identify the caller of my azure function based on the key they are using but hate to have a copy of this key in Application settings
You could install 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.
For more detail, you could refer to this SO thread with C# code or use powershell to get it.
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;
}
The output:

Azure web app "ERR: Cert with thumbprint: ...' not found in local machine cert store."

I'm trying to use an Apple push certificate in an Asp.NET Core 1.1 app but it cannot find any certificates.
I uploaded the cert and set WEBSITE_LOAD_CERTIFICATES to *(all).
The following code is what I use to get the certificate
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var signingCert = certCollection.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (signingCert.Count == 0)
{
throw new FileNotFoundException(string.Format("Cert with thumbprint: '{0}' not found in local machine cert store.", thumbprint));
}
return signingCert[0];
}
What am I missing?
It seems that you'd like to upload your certificate to the certificates collection in Azure Websites and consume it in your web application from your site’s personal certificate store. I'm upload my certificate and use the following code to consume it in Asp.NET Core 1.1 app, the code works for me.
X509Certificate2 retVal = null;
var thumbprint = "{cert_thumbprint}";
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var signingCert = certCollection.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (signingCert.Count > 0)
{
retVal = signingCert[0];
}
Remote debug the code and the code works fine on azure website

Programmatically getting the list of azure virtual machine sizes

I am new to Azure management libraries for .net. How can we enumerate VM instance sizes available with respect to subscription or in general using Azure Management libraries for .Net or Rest APIs?
Please suggest.
You can get a list of VM sizes for a region by calling
https://management.azure.com/subscriptions/{subscription-id}/providers/Microsoft.Compute/locations/{location}/vmSizes?api-version={api-version}
As documented here - List all available virtual machine sizes in a region
There is also a .Net Class for the same, but I've not found any examples of it being used - documented here - VirtualMachineSizeOperationsExtensions.List
You can get list of VM sizes by region fillter
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/tenantdomainname.onmicrosoft.com"]);
UserCredential uc = new UserCredential(authusername,authpassword);
token = authenticationContext.AcquireToken("https://management.core.windows.net/", nativetenantid, uc);
var credentials = new TokenCredentials(token);
var computeClient = new ComputeManagementClient(credentials) { SubscriptionId = subscriptionid};
var virtualMachineSize = computeClient.VirtualMachineSizes.List(region_name).ToList();
you must need create one native client api on Azure Active Directory for token base authentication otherwise you can also use certification base authentication for client authorization.
i am using Microsoft.Azure.Management.Compute.dll, v10.0.0.0 for compute resources.
you can download here: https://www.nuget.org/packages/Microsoft.Azure.Management.Compute/13.0.4-prerelease
You can get list of VM Size by using Certificate Base Authentication
Get Certificate method
private static X509Certificate2 GetStoreCertificate(string subscriptionId, string thumbprint)
{
List<StoreLocation> locations = new List<StoreLocation>
{
StoreLocation.CurrentUser,
StoreLocation.LocalMachine
};
foreach (var location in locations)
{
X509Store store = new X509Store(StoreName.My, location);
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));
}
here i describe same way to get VM size
private static X509Certificate2 Certificate = GetStoreCertificate(Your-subscriptionid,Your-thumbprint);
Microsoft.Azure.CertificateCloudCredentials credentials = new Microsoft.Azure.CertificateCloudCredentials(Your-subscriptionid, Certificate);
var computeClient = new ComputeManagementClient(credentials) { SubscriptionId = Your-subscriptionid};
var virtualMachineSize = computeClient.VirtualMachineSizes.List(Your-region_name).ToList();

Creating Azure Traffic Manager through REST API - unexpected behaviour

I created Traffic Manager throught its REST API using 2011-10-01 MS verion.
Resources I followed -
Create Profile -
http://msdn.microsoft.com/en-us/library/windowsazure/hh758254.aspx
Create Definition -
http://msdn.microsoft.com/en-us/library/windowsazure/hh758257.aspx
Traffic Manager got created successfully. All happies.
But after 30mins of time, traffic manager is going to INACTIVE status and all its endpoints are GONE. It shows there are no endpoints associated with it.
I am not sure what is happening around. Is it Azure problem? or is it REST API problem? or is it my way of creating Traffic manager problem.
PS - I followed this sample for making REST API calls - http://msdn.microsoft.com/en-us/library/windowsazure/gg651127.aspx
Any help would be highly appreciated.
UPDATE1
Parameters
SubscriptionID - a Valid GUID from publishsettings
Certificate - I cross checked a valid certificate present in the local cert store
endpoint1 domain name - JASH13.CLOUDAPP.NET
endpoint2 domain name - JASH23.CLOUDAPP.NET
There is no error at REST API calls level. Everything worked seamlessly.
Profile Creation -
// X.509 certificate variables.
X509Store certStore = null;
X509Certificate2Collection certCollection = null;
X509Certificate2 certificate = null;
// Request and response variables.
HttpWebRequest httpWebRequest = null;
HttpWebResponse httpWebResponse = null;
// Stream variables.
Stream responseStream = null;
StreamReader reader = null;
// URI variable.
Uri requestUri = null;
// The thumbprint for the certificate. This certificate would have been
// previously added as a management certificate within the Windows Azure management portal.
string thumbPrint = CertificateThumbprint;
// Open the certificate store for the current user.
certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
// Find the certificate with the specified thumbprint.
certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbPrint,
false);
// Close the certificate store.
certStore.Close();
// Check to see if a matching certificate was found.
if (0 == certCollection.Count)
{
throw new Exception("No certificate found containing thumbprint " + thumbPrint);
}
// A matching certificate was found.
certificate = certCollection[0];
// Create the request.
requestUri = new Uri("https://management.core.windows.net/"
+ SubscriptionId
+ "/services/WATM/profiles");
httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(requestUri);
// Add the certificate to the request.
httpWebRequest.ClientCertificates.Add(certificate);
httpWebRequest.Method = "POST";
httpWebRequest.Headers.Add("x-ms-version", "2011-10-01");
string str = #"<Profile xmlns=""http://schemas.microsoft.com/windowsazure""><DomainName>" + ProfileDomain + "</DomainName><Name>" + ProfileName + "</Name></Profile>";
byte[] bodyStart = System.Text.Encoding.UTF8.GetBytes(str.ToString());
Stream dataStream = httpWebRequest.GetRequestStream();
dataStream.Write(bodyStart, 0, str.ToString().Length);
// Make the call using the web request.
httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
// Parse the web response.
responseStream = httpWebResponse.GetResponseStream();
reader = new StreamReader(responseStream);
// Close the resources no longer needed.
httpWebResponse.Close();
responseStream.Close();
reader.Close();
Definition Creation-
// X.509 certificate variables.
X509Store certStore = null;
X509Certificate2Collection certCollection = null;
X509Certificate2 certificate = null;
// Request and response variables.
HttpWebRequest httpWebRequest = null;
HttpWebResponse httpWebResponse = null;
// Stream variables.
Stream responseStream = null;
StreamReader reader = null;
// URI variable.
Uri requestUri = null;
// The thumbprint for the certificate. This certificate would have been
// previously added as a management certificate within the Windows Azure management portal.
string thumbPrint = CertificateThumbprint;
// Open the certificate store for the current user.
certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
// Find the certificate with the specified thumbprint.
certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbPrint,
false);
// Close the certificate store.
certStore.Close();
// Check to see if a matching certificate was found.
if (0 == certCollection.Count)
{
throw new Exception("No certificate found containing thumbprint " + thumbPrint);
}
// A matching certificate was found.
certificate = certCollection[0];
// Create the request.
requestUri = new Uri("https://management.core.windows.net/"
+ SubscriptionId
+ "/services/WATM/profiles/" + ProfileName + "/definitions");
httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(requestUri);
// Add the certificate to the request.
httpWebRequest.ClientCertificates.Add(certificate);
httpWebRequest.Method = "POST";
httpWebRequest.Headers.Add("x-ms-version", "2011-10-01");
string str = #"<Definition xmlns=""http://schemas.microsoft.com/windowsazure""><DnsOptions><TimeToLiveInSeconds>300</TimeToLiveInSeconds></DnsOptions><Monitors><Monitor><IntervalInSeconds>30</IntervalInSeconds><TimeoutInSeconds>10</TimeoutInSeconds><ToleratedNumberOfFailures>3</ToleratedNumberOfFailures><Protocol>HTTP</Protocol><Port>80</Port><HttpOptions><Verb>GET</Verb><RelativePath>/</RelativePath><ExpectedStatusCode>200</ExpectedStatusCode></HttpOptions></Monitor></Monitors><Policy><LoadBalancingMethod>RoundRobin</LoadBalancingMethod><Endpoints><Endpoint><DomainName>" + PrimaryService + "</DomainName><Status>Enabled</Status></Endpoint><Endpoint><DomainName>" + SecondaryService + "</DomainName><Status>Enabled</Status></Endpoint></Endpoints></Policy></Definition>";
byte[] bodyStart = System.Text.Encoding.UTF8.GetBytes(str.ToString());
Stream dataStream = httpWebRequest.GetRequestStream();
dataStream.Write(bodyStart, 0, str.ToString().Length);
// Make the call using the web request.
httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
// Parse the web response.
responseStream = httpWebResponse.GetResponseStream();
reader = new StreamReader(responseStream);
// Close the resources no longer needed.
httpWebResponse.Close();
responseStream.Close();
reader.Close();
UPDATE2
Once the TM went into Inactive State, I checked the profile definition using REST API. In there I was not able to find any endpoints. They are missing.
<Definitions xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Definition>
<DnsOptions>
<TimeToLiveInSeconds>300</TimeToLiveInSeconds>
</DnsOptions>
<Status>Enabled</Status>
<Version>1</Version>
<Monitors>
<Monitor>
<IntervalInSeconds>30</IntervalInSeconds>
<TimeoutInSeconds>10</TimeoutInSeconds>
<ToleratedNumberOfFailures>3</ToleratedNumberOfFailures>
<Protocol>HTTP</Protocol>
<Port>80</Port>
<HttpOptions>
<Verb>GET</Verb>
<RelativePath>/</RelativePath>
<ExpectedStatusCode>200</ExpectedStatusCode>
</HttpOptions>
</Monitor>
</Monitors>
<Policy>
<LoadBalancingMethod>Performance</LoadBalancingMethod>
<Endpoints/>
<MonitorStatus>Inactive</MonitorStatus>
</Policy>
</Definition>
</Definitions>
UPDATE3
This sporadic behavior is ONLY happening with the specific cloud services and TM profile/definitiona. When I create new set of cloud services and TM profile, then everything seems to be working fine. I tested this multiple times. So the only problem is with following parameters.
endpoint1 domain name - JASH13.CLOUDAPP.NET
endpoint2 domain name -JASH23.CLOUDAPP.NET
TM Domain - ramitm.trafficmanager.net
TM profilename - ramitm
This seems like some DNS Problems for very fast REST API Operations. I was not able to get the crux of the problem, but this is how I solved it.
Previously I was getting this problem for this patter - Create -> Delete -> Re-Create
Now I made it this way - Create -> Delete -> Delay -> Re-Create
I think by introducing delay component, I am giving enough time for Azure to settle down all the DNS and infrastructure and there by update them. So after introducing delay, I was not experiencing the problem. Delay can be 5 - 10 mins.

Resources