Authentication successful but unable to retrieve and list Azure AD Users using Azure Golang SDK - azure

I am trying to use Azure Golang SDK to pull the list of Azure AD users.
Few things to Note:
1. Authentication is successful. "Authorization successful." is displayed when I use below code to fetch bearer token for Azure SPN.
This is pretty much vanilla code picked from Azure-Go-SDK repo.
//GetGraphAuthorizer gets an OAuthTokenAuthorizer for graphrbac API.
func GetGraphAuthorizer(fs auth.FileSettings) (autorest.Authorizer, error) {
if graphAuthorizer != nil {
return graphAuthorizer, nil
}
var a autorest.Authorizer
var err error
a, err = getAuthorizerForResource(grantType(), Environment().GraphEndpoint, fs)
if err == nil {
// cache
graphAuthorizer = a
fmt.Println("Authorization successful.")
} else {
graphAuthorizer = nil
fmt.Println ("Authorization failed.")
}
return graphAuthorizer, err
}
Defined a wrapper on around GetGraphAuthorizer function to instantiate userClient object:
func getADUserClient(fs auth.FileSettings) graphrbac.UsersClient {
userClient := graphrbac.NewUsersClient(azure.GetTenantId(fs))
a, _ := azure.GetGraphAuthorizer(fs)
userClient.Authorizer = a
userClient.AddToUserAgent(azure.UserAgent())
return userClient
}
Then I use the token to list users in Azure AD in below function:
adUserClient := getADUserClient(fs)
// if auth failed, then it should've displayed the failure message here but prints "Authorization successful instead"
for list, err := adUserClient.ListComplete(context.Background(), ""); list.NotDone(); err = list.Next() {
if err != nil {
fmt.Print("got error while traversing User list: ", err)
}
i := list.Value()
fmt.Println(*i.DisplayName)
fmt.Println(*i.GivenName)
}
No output what so ever!!
FYI:- I have users in Azure tenant.
I have granted SPN access to Graph API.
Any help is appreciated.

Not 100% sure but for some reason I had to grant Azure SPN "Delegated" permission to:
Directory.ReadWrite.All
Groups.ReadWrite.All
Surprisingly, Once the program listed users, I removed above "Delegated" permissions off SPN and just left the Application permissions and it continues to work!

Related

Accessing AKS cluster configuration (specifically OIDC issuer URI) from within the cluster

I am writing a job in golang that sets up an Azure user assigned managed identity using the Azure Go SDK, specifically github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi, a Kubernetes service account using the go-client for Kubernetes k8s.io/client-go/kubernetes and then using the Azure SDK to set up a Federated Identity Credential on the new user assigned managed identity.
In order to set up a federated credential on azure I need to get the oidc issuer uri.
I know how to get it using the Azure CLI, and I can simply paste a string. But I expect this code to be running on any cluster and i would expect the issuer to change. Id rather just get the issuer from a config file on the cluster itself or through the azure sdk. I am not sure how to do that. any help...
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
....some code here to set up azure clients and Kubernetes client...
//creating a new managed identity using azure client
msi, err := azureMsiClient.CreateOrUpdate(ctx, "new-umid-resource-group", "new-umid", armmsi.Identity{Location: strPtr(`East US`)}, &armmsi.UserAssignedIdentitiesClientCreateOrUpdateOptions{})
if err != nil {
fmt.Printf("could not create a managed identity", err.Error())
return err
}
....
//creating the new service account
annotations := make(map[string]string)
annotations["azure.workload.identity/client-id"] = *msi.Properties.ClientID
annotations["azure.workload.identity/tenant-id"] = "<myazuretenantid>"
labels := make(map[string]string)
labels["azure.workload.identity/use"] = "true"
mainSA := &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "myserviceaccount",
Namespace: "my-namespace",
Annotations: annotations,
Labels: labels,
},
_, err = kubeClientSet.CoreV1().ServiceAccounts("my-namespace").Create(ctx, mainSA, metav1.CreateOptions{})
if err != nil {
fmt.Println("could not create k8 service account")
return err
}
....
//creating the federated credential
aud := make([]*string, 1)
aud[0] = strPtr("api://AzureADTokenExchange")
beParms := armmsi.FederatedIdentityCredential{
Properties: &armmsi.FederatedIdentityCredentialProperties{
Audiences: aud,
Issuer: strPtr(<my cluster's OidcIssuerUri>),
Subject: strPtr(`system:serviceaccount:my-namespace:myserviceaccount`),
},
}
_, err := azureFedIdClient.CreateOrUpdate(ctx, "new-umid-resource-group","new-umid", "new-umid-fed-id", beParms, nil)
if err != nil {
fmt.Println("could not create a new federation between be auth service account and federated identity")
return err
}

How to specify x509 certificate for Azure SDK in Golang

I am trying to connect to use the Azure SDK for Golang to download files from a container online to my device and am using the connection string provided from azure to connect. For context this is running on a version of embedded Linux
I have two questions, first how do I pass a specific certificate to the azure SDK to use to connect, as currently when I connect, I get this issue
Get "https://transaction.blob.core.windows.net/transactions?comp=list&restype=container": x509: certificate signed by unknown authority
or failing that how do I generate the correct certificate to put it in /etc/ssl? Which I think is where go is looking for certificates as far as I understand.
Also second question what function from the azure sdk for go should I be using to download from a blob online if my folder structure looks like /transaction/my-libs/images/1.0.0/libimage.bin where transaction is my blob container.
func testConnection(){
Println("TESTING CONNECTION")
connStr := "..." // actual connection string hidden
serviceClient, err := azblob.NewServiceClientFromConnectionString(connStr, nil)
// crashes here <------------
//ctx := context.Background()
//container := serviceClient.NewContainerClient("transactions")
//
//_, err = container.Create(ctx, nil)
//
//blockBlob := container.NewBlockBlobClient("erebor-libraries")
//_, err = blockBlob.Download(ctx, nil)
//Open a buffer, reader, and then download!
downloadedData := &bytes.Buffer{}
reader := get.Body(RetryReaderOptions{}) // RetryReaderOptions has a lot of in-depth tuning abilities, but for the sake of simplicity, we'll omit those here.
_, err = downloadedData.ReadFrom(reader)
err = reader.Close()
if data != downloadedData.String() {
err := errors.New("downloaded data doesn't match uploaded data")
if err != nil {
return
}
}
pager := container.ListBlobsFlat(nil)
for pager.NextPage(ctx) {
resp := pager.PageResponse()
for _, v := range resp.ContainerListBlobFlatSegmentResult.Segment.BlobItems {
fmt.Println(*v.Name)
}
}
• You can use the following Azure SDK for Go command for passing a specific certificate to the Azure SDK to connect to other Azure resources by creating a service principal for it: -
‘ type ClientCertificateConfig struct {
ClientID string
CertificatePath string
CertificatePassword string
TenantID string
AuxTenants []string
AADEndpoint string
Resource string
} ‘
For more information on the creation of the client certificate and its usage, please refer to the documentation link below for more details: -
https://pkg.go.dev/github.com/Azure/go-autorest/autorest/azure/auth#ClientCertificateConfig
Also, even if your folder structure is ‘/transaction/my-libs/images/1.0.0/libimage.bin’, but the blob URL is unique with folder hierarchy mentioned in the blob URL, thus when connecting to the Azure storage account to download the blob, mention the URL in single inverted comma notation for the blob path to be specific.
Please refer to the sample code below for downloading the blobs through Azure SDK for Go: -
https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/storage/azblob#example-package
https://pkg.go.dev/github.com/Azure/azure-storage-blob-go/azblob#pkg-examples

Upload files to Azure Storage from Azure VM using SDK azblob and Managed Service Identity

I am trying to upload files to azure storage container using Go SDK for Azure storage from an Azure VM which has Azure Managed Identity attached to it. I am also using Azure auth to create a ServicePrincipalToken using MSIConfig. However I am receiving an error
RESPONSE Status: 400 Authentication information is not given in the correct format. Check the value of Authorization header.
Can someone please help me understand what I am missing?
Script I have used (modified form of the example ):
// main.go
package main
import (
"log"
"fmt"
"context"
"net/url"
"strings"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/Azure/go-autorest/autorest/azure/auth"
)
func main() {
azureServicePrincipalToken, err := auth.NewMSIConfig().ServicePrincipalToken()
if err != nil {
log.Fatal(err)
}
accountName := "<TESTSA>"
containerName := "<TESTCONTAINER>"
// Create a BlockBlobURL object to a blob in the container (we assume the container already exists).
u, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net/%s/readme.txt", accountName, containerName))
credential := azblob.NewTokenCredential(azureServicePrincipalToken.Token().AccessToken, nil)
if err != nil {
log.Fatal(err)
}
blockBlobURL := azblob.NewBlockBlobURL(*u, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
log.Println(blockBlobURL)
ctx := context.Background() // This example uses a never-expiring context
// Perform UploadStreamToBlockBlob
bufferSize := 2 * 1024 * 1024
maxBuffers := 3
_, err = azblob.UploadStreamToBlockBlob(ctx, strings.NewReader("Hello azblob"), blockBlobURL,
azblob.UploadStreamToBlockBlobOptions{BufferSize: bufferSize, MaxBuffers: maxBuffers})
if err != nil {
log.Fatal(err)
}
}
When I execute go run main.go, I receive the following error:
2020/12/26 17:58:07 https://<TESTSA>.blob.core.windows.net/<TESTCONTAINER>/readme.txt
2020/12/26 17:58:07 write error: -> github.com/Azure/azure-storage-blob-go/azblob.newStorageError, /home/<MYUSER>/go/pkg/mod/github.com/!azure/azure-storage-blob-go#v0.12.0/azblob/zc_storage_error.go:42
===== RESPONSE ERROR (ServiceCode=) =====
Description=Authentication information is not given in the correct format. Check the value of Authorization header.
RequestId:f30c063e-901e-0046-2cb0-db4781000000
Time:2020-12-26T17:58:07.7810745Z, Details:
Code: InvalidAuthenticationInfo
PUT https://<TESTSA>.blob.core.windows.net/<TESTCONTAINER>/readme.txt?blockid=j%2BItsAdqRN6EScZ3S2r8QwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D%3D&comp=block&timeout=61
Authorization: REDACTED
Content-Length: [12]
User-Agent: [Azure-Storage/0.12 (go1.13.9; linux)]
X-Ms-Client-Request-Id: [21638ec4-138c-434d-4b53-d13924e51966]
X-Ms-Version: [2019-12-12]
--------------------------------------------------------------------------------
RESPONSE Status: 400 Authentication information is not given in the correct format. Check the value of Authorization header.
Content-Length: [298]
Content-Type: [application/xml]
Date: [Sat, 26 Dec 2020 17:58:07 GMT]
Server: [Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0]
X-Ms-Request-Id: [f30c063e-901e-0046-2cb0-db4781000000]
exit status 1
I have also verified with the azcli command and I was able to upload a sample txt file helloworld to the storage container without any challenge. The commands I used:
az login --identity
az storage blob upload --container-name <TESTCONTAINER> --account-name <TESTSA> --name helloworld --file helloworld --auth-mode login
Response:
Finished[#############################################################] 100.0000%
{
"etag": "\"0x8D8A9CCDD921BA7\"",
"lastModified": "2020-12-26T18:34:22+00:00"
}
Thank you.
The code sample you refer to authorizes with Shared Key and Put Blob API, but not Azure AD.
credential, err := NewSharedKeyCredential(accountName, accountKey)
If you would like to authorize with Azure AD by ServicePrincipalToken, see Azure Active Directory authentication for Go.
applicationSecret := "APPLICATION_SECRET"
spt, err := adal.NewServicePrincipalToken(
*oauthConfig,
appliationID,
applicationSecret,
resource,
callbacks...)
if err != nil {
return nil, err
}
// Acquire a new access token
err = spt.Refresh()
if (err == nil) {
token := spt.Token
}

Authorization for azure app resource management without client ID and client secret

I'm trying to create an application where the user can manage their Azure resources (sql databases, storage). How can I authorize the user without needing their client ID and client secret.
I've looked at their documentation: https://godoc.org/github.com/Azure/go-autorest/autorest/azure/auth, however all of them requires environment variables and/or clientID/clientSecret. Is there a way where a user can provide username/password and I can get back an authorizer
type Client struct {
ServersClient postgresql.ServersClient
}
func NewCloudClient() *Client {
return &Client{}
}
func (c *Client) Init(config map[string]string) error {
var (
subscriptionID = config["subscriptionID"]
// tenantID = config["tenantID"]
// clientID = config["clientID"]
// clientSecret = config["clientSecret"]
// resourceGroup = config["resourceGroup"]
)
// oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
// if err != nil {
// return errors.Wrap(err, "error getting OAuthConfig")
// }
// spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, resourceGroup)
serversClient := postgresql.NewServersClient(subscriptionID)
//serversClient.BaseClient.Authorizer = autorest.NewBearerAuthorizer(spt)
c.ServersClient = serversClient
return nil
}
In all cases of authorization flow you will need at least client id. In some you will need more like client secret.
You can read about OAuth authorization here https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-app-types
but in general what you are aiming for is either of two cases
Authorize as user without internaction using username and password (this would be client resource owner flow
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
Authorize as user with his interaction
if this is code that can't get postback URL then use device code flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
or for anything in the browser use standard implicit grant flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow
You will quickly notice that in all cases you NEED to have client ID. This is because how Azure Authentication works.

Access Azure Batch from an Azure Function

I'm trying to use a Service Principle to access a Batch pool from an Azure Function and running into authentication issues that I don't understand. The initial login with the Service Principle works fine, but then using the credentials to access the batch pool returns a 401.
Below is a condensed version of my code with comments at the key points
module.exports.dispatch = function (context) {
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};
How can the initial login with a service principle work no problem, but then the credentials it returns not be able to access the batch pool?
The actual error says to check the auth header on the request, which I can see and the Authorisation header isn't even present.
I've triple checked the Active Directory access control for the batch account the App ID and secret are the ones belonging to the owner of the batch account. Any ideas what to try next?
The credentials expected by the Azure Batch npm client aren't the Azure Active Directory credentials/token, but the keys for the batch account. You can list your keys using the Azure CLI with a command like the following:
az batch account keys list -g "<resource-group-name>" -n "<batch-account-name>"
sample here
Then you can create the credentials parameter with those keys:
var credentials = new batch.SharedKeyCredentials('your-account-name', 'your-account-key');
You could still involve a Service Principal here if you wanted to store your batch keys in something like Key Vault, but then your code would be:
Get Service Principal auth against key vault to fetch name and key
Use name and key to create credentials
You cannot use the same OAuth token returned from the Azure Resource Management endpoint with Batch. Assuming your service principal has the correct RBAC permissions, auth with the Azure Batch endpoint: https://batch.core.windows.net/ instead (assuming you are using Public Azure).
You do not need to get the shared key credentials for the Batch account, credentials via AAD should be used instead if you are using an AAD service principal.
I happened to run across this same issue and I didn't have the option of using SharedKeyCredentials so I wanted to share my solution in case anyone else finds it helpful.
As fpark mentions, we need to get an OAuth token to use with Batch instead of the default Azure Resource Management. Below is the original code posted by Mark with the minor modification needed to make it work with Batch:
module.exports.dispatch = function (context) {
let authOptions = {tokenAudience: 'batch'};
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', authOptions, function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};

Resources