Openstack.Net SDK cannot access services - c#

We've setup an OpenStack system on our own hardware installing all components, with everything seemingly fine as we've created networks and VMs through the web interface.
I'm trying to use openstack.net SDK to do things programatically. I seem to be able to Authenticate fine using a username and password, but when it comes to accessing other services that are installed, we get errors suggesting the API endpoints aren't available to the user.
The code we're using is below which works fine until the CreateServer line at which point I get the error
"Unable to authenticate user and retrieve authorized service endpoints."
Uri baseUrl = new Uri("http://mycloudip:5000/v2.0");
CloudIdentity cloudId = new CloudIdentity()
{
Username = userName,
Password = password
};
CloudIdentityProvider cip = new CloudIdentityProvider(cloudId, baseUrl);
UserAccess ua = cip.Authenticate(cloudId);
CloudServersProvider provider = new CloudServersProvider(cloudId);
Metadata metaData = new Metadata(); // Add some metadata just because we can
metaData.Add("Description", "Example 4 - Getting Started");
string serverName = "Example4";
string imageId = "48df4181-040e-4821-8723-d9e4ba908d2f";
string flavorId = "3";
NewServer newServer = provider.CreateServer(serverName, imageId, flavorId, DiskConfiguration.Manual, metaData);
I can see all the service urls in the Access and Security >> API Endpoints section whilst logged on as the same user in the dashboard, but UserAccess.ServiceCatalog doesn't seem to be populated with anything.
Any help or pointers much appreciated.

The default IIdentityProvider used by the CloudServersProvider implementation in openstack.net SDK 1.3.2.0 is designed around the authentication requirements for Rackspace. In order to authenticate against a different OpenStack-compatible installation, you'll need to follow the steps described in the following documentation:
OpenStack Authentication (openstack.net API Reference Documentation)
The following is an excerpt of the current documentation:
This page describes the process for authenticating against reference OpenStack installations, including but not limited to DevStack and the Rackspace Private Cloud.
Usage Notes
Client authentication against a reference OpenStack installation requires the following.
Create an instance of CloudIdentityWithProject and initialize its properties with the desired authentication credentials. The CloudIdentityWithProject credentials class allows the tenantName and tenantId properties described in the OpenStack documentation to be defined.
Create an instance of OpenStackIdentityProvider, and pass the previously created credentials to the constructor.
When creating a service provider instance, such as CloudFilesProvider or CloudQueuesProvider, pass null for the CloudIdentity parameter and the identity provider from the previous step as the IIdentityProvider parameter.
Limitations
The OpenStackIdentityProvider only supports authentication using username and password credentials, along with optionally specifying the tenant name and/or tenant ID (referred to as the project name and ID starting with the Identity Service API v3).

Related

Client is unauthorized to retrieve access tokens using this method or client not authorized for any scope requested API Directory C#

I am getting the following error when trying to access the directory api by getting a list of users
Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.", Uri:""
Based on my previous googling efforts and reading stack overflow I am not sure what is causing this. As far as I am aware I have everything set up correctly. I have it setup to use the drive api in a very similar fashion and that works perfectly.
My service account does have domain wide delegation which is why I think it may have something to do with the second part of the error. Any ideas what could be causing this?
protected async virtual Task<DirectoryService?> GetDirectoryService()
{
if (currentDirectory == null)
{
string[] scopes = new string[] { DirectoryService.Scope.AdminDirectoryUser };
var initializer = new ServiceAccountCredential.Initializer(configuration["GoogleServiceAccount:AccountEmail"]){Scopes = scopes, User = configuration["GoogleServiceAccount:UserEmail"] };
var cred = new ServiceAccountCredential(initializer.FromPrivateKey(configuration["GoogleServiceAccount:SecretKey"]));
currentDirectory = new DirectoryService(new BaseClientService.Initializer { HttpClientInitializer = cred, ApplicationName = "DriveAPI" });
}
return currentDirectory;
User = configuration["GoogleServiceAccount:UserEmail"]
User is the user on your domain that you want to delegate as not the service accounts email address.
update
Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested
This error message in my exprence normally means that you are using code that does not match the type of credetinals you are using.
There are serval types of authorization, service account, Oauth installed and oauth web (lets ignore mobile for now). The code used for these credentials is different.
So if you use a service account key file with code designed for an Ouath2 installed app. You will normally get "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested"
The code I normally use for delegation with a service account is as follows. it looks similar to yours so i'm inclined to agree that you are using service account code. Which means to me you are probably using the wrong key file. I would double check. Open it it should say "type": "service_account".
// Load the Service account credentials and define the scope of its access.
var credential = GoogleCredential.FromFile(PathToServiceAccountKeyFile)
.CreateWithUser("user#mydomain.com")
.CreateScoped(new[] {DriveService.ScopeConstants.Drive});
My suggestion is to now double check and ensure that you are using the service account key file from google cloud console that was created by a user on your domain, and that you configured domain wide deligation for and have added the admin sdk scopes for remember the OAuth Scopes for admin have to be set, as well configuring an authorized user.

How do I get my multi-tenant Azure Function to access resources in another tenant?

I'm fairly confident I set everything up right for a multi-tenant app.
In Tenant A:
I created an Azure function and enabled a system managed identity. I granted the Managed Identity permissions to the Graph API. I confirmed my API can obtain an access token for the Managed Identity and access the Graph API with it. I added Azure AD authentication to the app and created an App Registration for the app. I configured the necessary Graph API permissions in the API Permissions settings of the app registration. I also enabled the various options to enable multi-tenant access.
In Tenant B:
I accessed the special URL to start the admin consent process for the app in Tenant A. I confirmed that I was prompted to consent to the permissions that I specified in the app registration in Tenant A. I can see that a couple new enterprise application entries were created in Tenant B for the app in Tenant A.
So far so good. However, no matter what I do, I cannot obtain a token to access the graph API in the context of Tenant B. It ALWAYS gives me a token for the managed identity in Tenant A, and accesses the info in Tenant A.
The code the retrieves an access token using the managed identity is here:
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { AdditionallyAllowedTenants = { "*" }, TenantId = "<Tenant B ID>" });
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }, null, null, "<Tenant B ID"));
var accessToken = token.Token;
Now, I have tried all sorts of different combinations here. But, from everything I read, I am supposed to be able to specify the Tenant ID of another tenant in these methods and obtain an access token for that specific tenant. However, even when I specify Tenant B's ID, I always get a token back for the managed identity in Tenant A. If I use the token, of course, I end up accessing all of Tenant A's information.
Ironically, if I remove the DefaultAzureCredentialOptions and include the tenantID in the GetToken request I get an error telling me that I'm trying to obtain a token for a different tenant (than my own) and that I need to add the AdditionallyAllowedTenants option. It clears the error up when I add that, but then it doesn't obtain a token for the other tenant.
Perhaps I am still approaching this wrong. I want to host a multi-tenant Azure Function that runs in the context of the Tenant where the request originated from to access information within that tenant using a managed identity that exists within that tenant. I can obtain the Tenant ID context from the claims sent to the Azure function during authentication, but no matter how I try to specify that Tenant ID in my code, it will not get a token for that Tenant.
UPDATE and SOLUTION:
Thanks to Philippe's great write up, I did finally get this. I had some confusion around the mechanisms at play here and want to add this note for clarity on how I solved it.
If you've followed most of the documents you have an Azure Function app in your "host" tenant A, with an associated app registration in tenant A. The app registration has certain API permissions configured, and you have consented to this app and permissions in Tenant B.
To access the resources in Tenant B, you need to create a secret key or certificate on the app registration in Tenant A. You then need to authenticate using the Client ID, and secret key/certificate of the app registration in Tenant A, but you will request a token for Tenant B. Here it is in code using a certificate for authentication:
var appToken = new ClientCertificateCredential(tenantID, appID, appCert, new ClientCertificateCredentialOptions { AdditionallyAllowedTenants = { "*" } });
var graphServiceClient = new GraphServiceClient(appToken);
Here, we assign the following to the variables:
tenantID = Tenant B's ID
appID = Tenant A's App registration client ID
appCert = Tenant A's App registration cert
We have to include the AdditionallyAllowedTenants config parameter, and the * authorizes a token from any tenant. We then take that credential and use it to build a new GraphServiceClient that is connected to Tenant B.
You've created two separate and independent identities for your service:
The managed identity: You don't need to deal with credentials, but this identity can only be used within the same tenant. You can't use your managed identity to directly access another tenant, and you can't (currently) use your managed identity to authenticate as your app registration.
The app registration: This identity can be used to access data in another tenant, but (currently) you do need to deal with credentials.
The DefaultAzureCredential will attempt different ways of authenticating until it finds one that works:
First, it first tries to authenticate using an EnvironmentCredential. This will look for certain environment variables needed to authenticate. Depending on the variables it finds, it will end up creating a ClientSecretCredential (to authenticate using the app's client secret) or ClientCertificateCredential (using an app's client certificate).
Next, if no suitable environment variables were found, it tries to authenticate using ManagedIdentityCredential, which (as the name suggests) will attempt to use a managed identity. For you, this will always succeed (because there is a managed identity available to be used).
Next, if no managed identity was available, it will continue trying with various other options. This is all described in detail in the documentation.
For your scenario, you basically have two options today.
Option 1, using DefaultAzureCredential and environment variables
Place the credentials for your multi-tenant app registration in environment variables and your existing code will "just work".
For example:
AZURE_TENANT_ID - the target tenant where you want to obtain the token
AZURE_CLIENT_ID - your multi-tenant app registration's app ID
AZURE_CLIENT_SECRET - a client secret for your multi-tenant app registration
Note: In general, it is better to use a certificate than a secret.
Option 2, using ClientSecretCredential or ClientCertificateCredential
Instead of using DefaultAzureCredential, you can directly create a ClientSecretCredential or a ClientCertificateCredential. You'll need to store the credential somewhere safe.
For example, on approach you could follow which would avoid any credentials in environment variable or in code:
Store credentials for your multi-tenant app in a key vault.
Allow your function's managed identity to access the credential in the key vault.
In your function, use your managed identity to retrieve the credential from Key Vault (e.g. using ManagedIdentityCredential), in your tenant.
Use that credential to authenticate as your multi-tenant app registration, in the target tenant (i.e. ClientSecretCredential or ClientCertificateCredential)
https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme
Managed Identities can only get tokens within the tenant that they exist in.
You'd need a "traditional" multi-tenant app registration + client certificate/secret for this case.

How to Add CNAME in Azure DNS via C# code

I have requirement to have multiple subdomain.
My site is hosted in virtual machine with name test.mydomain.com
I have manually added CNAME Testing1 in azureDNS and configure that in IIS site bindings Testing1.mydomain.com. and this is working
But i want to make it automatic.intead of manual process. need to do it from code or from powershell.
Anyone having knowledge about this please help me
Thanks
You'll need to start by creating a Service Principal account since it's the authority you'll use to authenticate via the SDK into Azure.
Here's the documentation to create a service principal:
Authenticating with a password
Authenticating with a certificate
Install the following packages to your C# project:
Microsoft.Azure.Management.Dns
Microsoft.Rest.ClientRuntime.Azure.Authentication
Microsoft.Azure.Management.ResourceManager (note this is a preview package)
You'll need to populate the following variables for the sample below:
tenantID: The ID of the tenant your Service Principal and DNS resource are on
clientId: The Service Principal identifier
secret: Whatever your secret mechanism is for the Service Principal you picked above
subscriptionId: The ID of the Azure subscription your DNS resource is on
resourceGroupName: The name of the resource group your DNS resource is located in
zoneName: The name of the DNS zone you're modifying (the host name)
recordSetName: A name to assign to the record set
Then you'll use the following logic to create the necessary CNAMEs necessary in Azure DNS using the Service Principal values.
var dnsClient = new DnsManagementClient(serviceCreds);
dnsClient.SubscriptionId = subscriptionId;
// Create record set parameters
var recordSetParams = new RecordSet();
recordSetParams.TTL = 3600;
recordSetParams.CnameRecord = new CnameRecord
{
Cname = "<Your CNAME Value>"
};
//Create the record in Azure DNS
await dnsClient.RecordSets.CreateOrUpdateAsync(resourceGroupName, zoneName, recordSetName, RecordType.Cname, recordSetParams);

What is the proper way to work with the OneDrive API?

I want to interact with OneDrive in my WinForms application. Sadly, the Azure quick start samples do not include WinForms, just UWD.
The flow on what I have to do is consistent, namely given my Client ID, I have to obtain an Authentication Code. Given the authentication code, I can then obtain an Access Code, which will allow me to interact in a RESTful way with the OneDrive API. My plan is to have the authentication piece go in a .Net Framework Library and the file IO calls will go in another library that has no user interface access, as it will go in a Windows Service. I would pass the Access Token to the service.
AADSTS50059: No tenant-identifying information found in either the request or implied by any provided credentials.
This error corresponds to the following code fragment that I lifted from the sample .Net Core daemon quick start code.
Note: I was playing around with Scopes as I kept receiving scope errors and I saw one article, whose link I should have kept, which stated to use the API and default scope.
public bool GetRestAuthenticationToken(out string tokenAuthentication)
{
tokenAuthentication = null;
try
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(Authenticate.AppClientId)
.WithClientSecret(Authenticate.AppClientSecret)
.WithAuthority(new Uri(#"https://login.microsoftonline.com/common/oauth2/nativeclient"))
.Build();
string scope = $"onedrive.readwrite offline_access";
System.Collections.Generic.List<string> enumScopes = new System.Collections.Generic.List<string>();
enumScopes.Add("api://<GUID>/.default");
//enumScopes.Add(Authenticate.Scopes[1]);
var result = Task.Run(async () => await app.AcquireTokenForClient(enumScopes).ExecuteAsync()).Result;
...
}
...
}
I believe that I have my application configured properly now on Azure, but am not 100% positive.
API Permissions:
Authentication:
Desktop Applications: https://login.microsoftonline.com/common/oauth2/nativeclient
Desktop Applications: https://login.live.com/oauth20_desktop.srf
Implicit Grants: Access tokens & ID tokens
Live SDK support (Yes)
Default client type (Yes)
Others:
I do have a client secret and kept note of all the Overview GUIDs
Microsoft Doc 1
I tried several different URLs, but only the one not commented out works with the fragment above, but throws the referenced error.
//string redirect_uri = #"https://www.myapp.com/auth";
//string redirect_uri = "https://login.live.com/oauth20_desktop.srf";
string url = #"https://login.microsoftonline.com/common/oauth2/nativeclient";
//string url = $"https://login.live.com/oauth20_authorize.srf?client_id={appClientId}&scope={scope}&response_type=code&redirect_uri={redirect_uri}";
//string url = $"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
// $"client_id={Authenticate.AppClientId}&" +
// $"scope={scope}&" +
// $"response_type=token&" +
// $"redirect_uri={redirect_uri}";
The goal is the same, namely to obtain an access token that I can use with RESTful calls to work with files and/or directories on OneDrive, e.g.
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.GetAsync(...);
You are trying to implement Client credentials grant type to get the access token.
Based on MSAL initialization, Authority is
(Optional) The STS endpoint for user to authenticate. Usually
https://login.microsoftonline.com/{tenant} for public cloud, where
{tenant} is the name of your tenant or your tenant Id.
We assume that your tenant is "myTenent.onmicrosoft.com", then you should set it as https://login.microsoftonline.com/myTenent.onmicrosoft.com here.
I notice that you specify a scope "onedrive.readwrite" in your code. But it's not a valid permission of Microsoft Graph. The default scope of Microsoft Graph is https://graph.microsoft.com/.default.

Getting Registered App Display Name from an App Id

I have an Azure App Id which I have obtained from a token. I would like to get the display name of the application using C# so that I can add it to logs and Application Insights custom properties for a request. It is obviously more human-readable than an Id hence my need.
When I go into the portal I can go to the app registrations and type the AppId in and it shows me the name, so clearly it can be obtained (I can see from the URL https://graph.windows.net/myorganization/applications/?api-version=2.0&$top=40&$filter=appId%20eq%20%27MYAPPID%27), but how do I get this information programmatically in C#?
I could maintain my own lookup table somewhere which converts app ids to names, but this seems pointless when the information is obviously available at runtime.
If you want to get the information about your application in your c# application, you can use Microsoft.Graph.Beta to call the API. For example
Register a new application using the Azure portal
Sign in to the Azure portal using either a work or school account or
a personal Microsoft account.
If your account gives you access to more than one tenant, select your account in the top right corner, and set your portal session to the Azure AD tenant that you want.
In the left-hand navigation pane, select the Azure Active Directory service, and then select App registrations > New registration.
Configure Microsoft Graph permissions you need for your application
Code
/*
install Microsoft.Graph.Beta
install Microsoft.Graph.COre
install Microsoft.Graph.Beta.Auth
*/
string clientId = "your application id";
string appKey = "your client secret";
string tenantId = "your tenant id";
IConfidentialClientApplication confidentialClientApplication =
ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(appKey)
.Build();
ClientCredentialProvider authProvider = new
ClientCredentialProvider(confidentialClientApplication);
var apps = graphClient.Applications.Request().Filter("appId eq \'you application id\'").GetAsync().Result;
foreach(var app in apps){
Console.WriteLine(app.DisplayName);
}
For more details, please refer to the document

Categories