I am trying to call a Azure Notification Hub REST API , based on this documentation. As they said , I tried to create a Header of API and it giving me an error "The credentials contained in the authorization header are not in the WRAP format".
My Demo DefaultFullSharedAccessSignature is :
Endpoint=sb://shinetrialhub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=BaGJbFDQZ+hkbi2MdUj7gU0tOM+aC/k+mez9J/y54Qc=
Here my API: https://shinetrialhub-ns.servicebus.windows.net/shinetrialhub/messages/?api-version=2015-01
by adding valid header (please see the MSDN document)
You need to generate Shared Access Signature Authentication with Service Bus. I've been using code below to achieve this:
resourceUri: https://shinetrialhub-ns.servicebus.windows.net/shinetrialhub/
keyName: RootManageSharedAccessKey
key: the value for RootManageSharedAccessKey
private string GetSasToken(string resourceUri, string keyName, string key)
{
var expiry = GetExpiry();
var stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = string.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
private string GetExpiry()
{
var sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToString((int) sinceEpoch.TotalSeconds + 102000); //token valid for that many seconds
}
Also make sure you have all the right headers, as shown in the documentation.
Related
I'm having intermittent 403 errors trying to access a blob storage, via Azure CDN, with the symmetric access key. It seems that sometimes there's a header added for "Range", in the format of "bytes=xxx". The full error message is below:
{'Date': 'Mon, 12 Dec 2022 13:07:40 GMT', 'Content-Type': 'application/xml', 'Content-Length': '697', 'Connection': 'keep-alive', 'x-ms-request-id': '3f89c2c1-e01e-0050-132a-0eeb42000000', 'x-ms-error-code': 'AuthenticationFailed', 'x-azure-ref': '20221212T130740Z-6rfkrgx8qt0shbtz3x46rwnhrn0000000630000000002ayd', 'X-Cache': 'TCP_MISS'}
<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:3f89c2c1-e01e-0050-132a-0eeb42000000
Time:2022-12-12T13:07:40.7638741Z</Message><AuthenticationErrorDetail>The MAC signature found in the HTTP request 'xxxxxx=' is not the same as any computed signature. Server used following string to sign: 'GET
bytes=0-8388607
x-ms-date:Mon, 12 Dec 2022 13:07:36 GMT
x-ms-version:2020-04-08
/deviceimage2zgjscikl7kny/images/data-prod-1.1.packer'.</AuthenticationErrorDetail></Error>
I was able to reproduce the error by generating the MAC signature in Python, but I saw it originally using the Go SDK and az CLI.
We added a rule at the CDN to Bypass caching, and it seems to have improved the situation (problem happens less frequently), but we are still seeing it on occasion.
Has anyone else experienced this? And is there a workaround?
Trying to access a blob storage with an access key, via Azure CDN
I tried in my environment and got below results:
Initially, I got a same when I tried to access blob storage with CDN using Postman.
Postman:
The above error states that signature and date is incorrect. So, we can't pass directly storage access key. You need to create a signature string that represents the given request, sign the string with the HMAC-SHA256 algorithm (using your storage key to sign), and encode the result in base 64.
For creating signature, I used below .NET code:
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
class Program
{
static void Main(string[] args)
{
ListBlobs();
Console.WriteLine("done");
Console.ReadLine();
}
static void ListBlobs()
{
string Account = "venkat123";
string Key = "<Storage account key>";
string Container = "test";
string apiversion = "2021-06-08";
DateTime dt = DateTime.UtcNow;
string StringToSign = String.Format("GET\n"
+ "\n" // content encoding
+ "\n" // content language
+ "\n" // content length
+ "\n" // content md5
+ "\n" // content type
+ "\n" // date
+ "\n" // if modified since
+ "\n" // if match
+ "\n" // if none match
+ "\n" // if unmodified since
+ "\n" // range
+ "x-ms-date:" + dt.ToString("R") + "\nx-ms-version:" + apiversion + "\n" // headers
+ "/{0}/{1}\ncomp:list\nrestype:container", Account, Container);
string auth = SignThis(StringToSign, Key, Account);
Console.WriteLine($"the date is: {dt.ToString("R")}");
Console.WriteLine($"the auth token is: {auth}");
Console.WriteLine("*********");
string method = "GET";
string urlPath = string.Format("https://{0}.blob.core.windows.net/{1}?restype=container&comp=list", Account, Container);
Uri uri = new Uri(urlPath);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = method;
request.Headers.Add("x-ms-date", dt.ToString("R"));
request.Headers.Add("x-ms-version", apiversion);
request.Headers.Add("Authorization", auth);
Console.WriteLine("***list all the blobs in the specified container, in xml format***");
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
Console.WriteLine(reader.ReadToEnd());
}
}
}
private static String SignThis(String StringToSign, string Key, string Account)
{
String signature = string.Empty;
byte[] unicodeKey = Convert.FromBase64String(Key);
using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
{
Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(StringToSign);
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
String authorizationHeader = String.Format(
CultureInfo.InvariantCulture,
"{0} {1}:{2}",
"SharedKey",
Account,
signature);
return authorizationHeader;
}
}
Console:
Above executed code, date and signature which I copied and used in postman, and it worked successfully.
Postman:
My application generates SAS tokens to access existing blobs within my container. However, my SAS token dose not look like it is expiring. I am able to view and get blob from container way past expiration time I am claiming.
Here is the code :
public string GenerateSasToken([NotNull] string containerName, [NotNull] string blobName)
{
var startTime = DateTimeOffset.UtcNow
var expiredTime = startTime.AddSeconds(20);
var blobClient = new BlobClient(_options.Value.ConnectionString, containerName, blobName);
var sasBuilder = new BlobSasBuilder(BlobContainerSasPermissions.Read, expiredTime)
{
BlobName = blobName,
BlobContainerName = containerName,
StartsOn = startTime,
ExpiresOn = expiredTime
};
var uri = blobClient.GenerateSasUri(sasBuilder);
return uri.ToString();
}
Token been generated is valid and I am able to use it, but it dose not expire after 20 seconds in fact it dose not expire even after 15 minutes.
Am I missing something within this API?
Thank you!
Edit:
I am attaching SAS token that was generated.
?sv=2020-08-04&st=2022-01-24T21%3A20%3A41Z&se=2022-01-24T21%3A21%3A01Z&sr=b&sp=r&sig=signature-here
Even though the SAS token is expired, because of the browser caching, you would still be able to access the blob storage using the same SAS token
In order to avoid this, you can override the cache-control header in the SAS token as suggested by #Gaurav Mantri
You need to set CacheControl value in your BlobSasBuilder function to override the cache-control header
Your BlobSasBuilder function can be as below:
var sasBuilder = new BlobSasBuilder(BlobContainerSasPermissions.Read, expiredTime)
{
BlobName = blobName,
BlobContainerName = containerName,
StartsOn = startTime,
ExpiresOn = expiredTime,
CacheControl = "max-age=" + expiredTime
};
In an Azure API Management Policy Expression I need to create a JWT signed with a private key.
When I try to use RSACryptoServiceProvider - just to check whether this feedback already got resolved - I get this error when trying to save the policy:
Usage of type 'System.Security.Cryptography.RSACryptoServiceProvider' is not supported within expressions
Following a hint from maxim-kim, I tried RSA.Create() and to convert from this tutorial
var privateKey = "whatever";
RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKey, out _);
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
var now = DateTime.Now;
var unixTimeSeconds = new DateTimeOffset(now).ToUnixTimeSeconds();
var jwt = new JwtSecurityToken(
audience: _settings.Audience,
issuer: _settings.Issuer,
claims: new Claim[] {
new Claim(JwtRegisteredClaimNames.Iat, unixTimeSeconds.ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(nameof(claims.FirstName), claims.FirstName),
new Claim(nameof(claims.LastName), claims.LastName),
new Claim(nameof(claims.Email), claims.Email)
},
notBefore: now,
expires: now.AddMinutes(30),
signingCredentials: signingCredentials
);
string token = new JwtSecurityTokenHandler().WriteToken(jwt);
return new JwtResponse
{
Token = token,
ExpiresAt = unixTimeSeconds,
};
but got the next error:
'RSA' does not contain a definition for 'ImportRSAPrivateKey' and no extension method 'ImportRSAPrivateKey' accepting a first argument of type 'RSA' could be found (are you missing a using directive or an assembly reference?)
So my question: Is there a way to create a signed JWT in an Azure API Management Policy Expression?
Thanks to this and other articles, I managed to sign in an APIM policy. Therefore I would like to share this.
<set-variable name="signedPayload" value="#{
using (RSA rsa = context.Deployment.Certificates["thumbprint"].GetRSAPrivateKey())
{
long unixTimeStampInSeconds = DateTimeOffset.Now.ToUnixTimeSeconds();
string header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
string claimset = String.Format("{{ \"scope\": \"https://www.googleapis.com/auth/devstorage.read_write\", \"aud\": \"https://oauth2.googleapis.com/token\", \"iss\": \"blahblah.gserviceaccount.com\", \"iat\": {0}, \"exp\": {1} }}", unixTimeStampInSeconds, unixTimeStampInSeconds + 3599);
string payload = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(claimset));
byte[] signature = rsa.SignData(Encoding.UTF8.GetBytes(payload), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return System.Net.WebUtility.UrlEncode(payload + "." + System.Convert.ToBase64String(signature));
}
}" />
RSA initialization based on dynamically resolved private and public keys is not supported today.
If RSA parameters are not request specific you can upload x509 certificate to APIM containing required RSA parameters and use it within expressions:
using (var rsa = context.Deployment.Certificates["thumbprint"].GetRSAPrivateKey())
{
....
}
I am trying to get a msi token for a specific User defined identity. Our app service has 2 user defined identities and I want a token on behalf of one of the user assigned identity.
Here is the code:
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/&object_id=<ObjectId>&client_id=<clientId>");
req.Headers["Metadata"] = "true";
req.Method = "GET";
try
{
// Call /token endpoint
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
// Pipe response Stream to a StreamReader, and extract access token
StreamReader streamResponse = new StreamReader(response.GetResponseStream());
string stringResponse = streamResponse.ReadToEnd();
Dictionary<string, string> list =
JsonConvert.DeserializeObject<Dictionary<string, string>>(stringResponse);
string accessToken = list["access_token"];
System.IO.File.WriteAllText(#".\Log.txt", accessToken);
}
catch (Exception e)
{
string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
System.IO.File.WriteAllText(#".\Log.txt", errorText);
throw;
}
It is deployed in an azure app service. When I hit this section I see this error:
An attempt was made to access a socket in a way forbidden by its access permissions
I tried connecting to http://169.254.169.254 to get the token using kudu console. But this endpoint does not seem to accessible there.
I did try to use AzureServiceTokenProvider from Microsoft.Azure.Services.AppAuthentication for generating msi token but could not find any documentation about how to use it for multiple user assigned identities.
Edit:
Update 1:
I tried to use endpoint from MSI_ENDPOINT environment variable instead of 169.254.169.254. But it looks like MSI_ENDPOINT value is not set when I run the app service.
Here is the code I have tried:
var endpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
string apiVersion = "2018-02-01";
string resource = "https://management.azure.com/";
string objectId = "<objectid>";
string clientId = "<clientId>";
// Build request to acquire managed identities for Azure resources token
//HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
// "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/&object_id=4aef1720-b3b1-4935-8d68-e330508907fa&client_id=558ecc75-8697-4419-bab9-aa2c87043cfd");
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
String.Format(
"{0}?resource={1}&api-version={2}&object_id={3}&client_id={4}",
endpoint,
resource,
apiVersion,
objectId,
clientId));
req.Headers["Metadata"] = "true";
req.Method = "GET";
try
{
// Call /token endpoint
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
// Pipe response Stream to a StreamReader, and extract access token
StreamReader streamResponse = new StreamReader(response.GetResponseStream());
string stringResponse = streamResponse.ReadToEnd();
Dictionary<string, string> list =
JsonConvert.DeserializeObject<Dictionary<string, string>>(stringResponse);
string accessToken = list["access_token"];
System.IO.File.WriteAllText(#".\Log.txt", accessToken);
}
catch (Exception e)
{
string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
string log = "MSI_ENDPOINT : " + endpoint + "\n";
log += ("ErrorText : " + errorText + "\n");
System.IO.File.WriteAllText(#".\Log.txt", errorText);
throw;
}
Firstly, this link How to use managed identities for App Service and Azure Functions provides good documentation specific to MSI for App Services.
Here is quick sample code.. to get token for a specific user assigned managed service identity as you've asked in your question.
resource - The AAD resource URI of the resource for which a token should be obtained.
apiversion - The version of the token API to be used. "2017-09-01" is currently the only version supported.
clientId - The ID of the user-assigned identity to be used. If omitted, the system-assigned identity is used.
public static async Task<HttpResponseMessage> GetToken(string resource, string apiversion, string clientId)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Secret", Environment.GetEnvironmentVariable("MSI_SECRET"));
return await client.GetAsync(String.Format("{0}/?resource={1}&api-version={2}&clientid={3}", Environment.GetEnvironmentVariable("MSI_ENDPOINT"), resource, apiversion,clientId));
}
Overall I see a few changes that you should notice in the sample code above:
Make use of MSI_ENDPOINT to construct URL at runtime
parameter should be clientid and not client_id
parameter object_id is not needed
api version should be "2017-09-01" as documentation in above link says that's the only one supported.
About your issue with MSI_ENDPOINT value not being set when you run the app service, please take a look at this note from same link in Microsoft Docs
Screenshot from documentation that is relevant for all parameters used
i want to send message to Azure service bus by Azure scheduler using post
like demo in this page
http://www.prasadthinks.com/
but i don't know how to set 'authorization' property in Http Header.
As far as I know, the 'authorization' property must contains the service bus's access token.
You could use your shared access policies's key-name and key to generate the access token by using codes.
More details, you could refer to below codes.
string keyName = "keyname";
string key = "key";
var sasToken = createToken("http://yourservicebusname.servicebus.windows.net/queuename", keyName, key);
createToken function:
private static string createToken(string resourceUri, string keyName, string key)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 7200); //EXPIRES in 2h
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
//this is the auth token
var sasToken = String.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
The result is like below:
This is the 'authorization' property, you could copy it.But this token has two hours limit.
The azure scheduler job setting like below:
Besides, the azure scheduler job have already support send the message to the service bus, you don't need to create the sas token by yourself, you could just add the keyName and key in its authentication settings.
More details, you could refer to below images: