This project provides a stand alone Azure Active Directory library for Go, that is used also by the go-autorest project, which is the core of azure-sdk-for-go library.
go get -u github.com/ccojocar/adal
An Active Directory application is required in order to use this library. This application can be registered in the Azure Portal following these guidelines or using the Azure CLI.
-
Register a new application with a
secret
credentialaz ad app create \ --display-name example-app \ --homepage https://example-app/home \ --identifier-uris https://example-app/app \ --password secret
-
Create a service principal using the
Application ID
from previous stepaz ad sp create --id "Application ID"
- Replace
Application ID
withappId
from step 1.
- Replace
-
Create a private key
openssl genrsa -out "example-app.key" 2048
-
Create the certificate
openssl req -new -key "example-app.key" -subj "/CN=example-app" -out "example-app.csr" openssl x509 -req -in "example-app.csr" -signkey "example-app.key" -out "example-app.crt" -days 10000
-
Create the PKCS12 version of the certificate containing also the private key
openssl pkcs12 -export -out "example-app.pfx" -inkey "example-app.key" -in "example-app.crt" -passout pass:
-
Register a new application with the certificate content form
example-app.crt
certificateContents="$(tail -n+2 "example-app.crt" | head -n-1)" az ad app create \ --display-name example-app \ --homepage https://example-app/home \ --identifier-uris https://example-app/app \ --key-usage Verify --end-date 2018-01-01 \ --key-value "${certificateContents}"
-
Create a service principal using the
Application ID
from previous stepaz ad sp create --id "APPLICATION_ID"
- Replace
APPLICATION_ID
withappId
from step 4.
- Replace
Azure relies on a Role-Based Access Control (RBAC) model to manage the access to resources at a fine-grained level. There is a set of pre-defined roles which can be assigned to a service principal of an Azure AD application depending of your needs.
az role assignment create --assigner "SERVICE_PRINCIPAL_ID" --role "ROLE_NAME"
- Replace the
SERVICE_PRINCIPAL_ID
with theappId
from previous step. - Replace the
ROLE_NAME
with a role name of your choice.
It is also possible to define custom role definitions.
az role definition create --role-definition role-definition.json
- Check custom roles for more details regarding the content of
role-definition.json
file.
The common configuration used by all flows:
const activeDirectoryEndpoint = "https://login.microsoftonline.com/"
tenantID := "TENANT_ID"
oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID)
applicationID := "APPLICATION_ID"
callback := func(token adal.Token) error {
// This is called after the token is acquired
}
// The resource for which the token is acquired
resource := "https://management.core.windows.net/"
- Replace the
TENANT_ID
with your tenant ID. - Replace the
APPLICATION_ID
with the value from previous section.
applicationSecret := "APPLICATION_SECRET"
// Set up the configuration of the service principal
spt, err := adal.NewServicePrincipalToken(
oauthConfig,
appliationID,
applicationSecret,
resource,
callbacks...)
if err != nil {
return nil, err
}
// Evectively acqurie the token
err = spt.Refresh()
if err == nil {
token := spt.Token
}
- Replace the
APPLICATION_SECRET
with thepassword
value from previous section.
certificatePath := "./example-app.pfx"
certData, err := ioutil.ReadFile(certificatePath)
if err != nil {
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
}
// Get the certificate and private key from pfx file
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
if err != nil {
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
}
// Set up the configuration of the service principal
spt, err := adal.NewServicePrincipalTokenFromCertificate(
oauthConfig,
applicationID,
certificate,
rsaPrivateKey,
resource,
callbacks...)
// Evectively acqurie the token
err = spt.Refresh()
if err == nil {
token := spt.Token
}
- Update the certificate path to point to the example-app.pfx file which was created in previous section.
oauthClient := &http.Client{}
// Acquire the device code
deviceCode, err := adal.InitiateDeviceAuth(
oauthClient,
oauthConfig,
applicationID,
resource)
if err != nil {
return nil, fmt.Errorf("Failed to start device auth flow: %s", err)
}
// Display the authentication message
fmt.Println(*deviceCode.Message)
// Wait here until the user is authenticated
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
if err != nil {
return nil, fmt.Errorf("Failed to finish device auth flow: %s", err)
}
spt, err := adal.NewServicePrincipalTokenFromManualToken(
oauthConfig,
applicationID,
resource,
*token,
callbacks...)
if err == nil {
token := spt.Token
}
You can retrieve a token with this flow on a VM which support MSI extension.
// Get the MSI endpoint accoriding with the OS (Linux/Windows)
msiEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, fmt.Errorf("Failed to get the MSI endpoint. Error: %v", err)
}
// Set up the configuration of the service principal
spt, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, resource, callbacks...)
if err != nil {
return nil, fmt.Errorf("Failed to acquire a token using the MSI VM extension. Error: %v", err)
}
// Evectively acqurie the token
err = spt.Refresh()
if err == nil {
token := spt.Token
}
It is possible to request a token with user context using MSI. You just have to provide the user ID.
// Get the MSI endpoint accoriding with the OS (Linux/Windows)
msiEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, fmt.Errorf("Failed to get the MSI endpoint. Error: %v", err)
}
// The ID of the user for whom the token is requested
userAssignedID := <USER ID>
// Set up the configuration of the service principal
spt, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID((msiEndpoint, resource, userAssignedID, callbacks...)
if err != nil {
return nil, fmt.Errorf("Failed to acquire a token using the MSI VM extension. Error: %v", err)
}
// Evectively acqurie the token
err = spt.Refresh()
if err == nil {
token := spt.Token
}
A command line tool is available in cmd/adal.go
that can acquire a token for a given resource. It supports all flows mentioned above.
adal -h
Usage of ./adal:
-applicationId string
application id
-certificatePath string
path to pk12/PFC application certificate
-mode string
authentication mode (device, secret, cert, refresh, "msi") (default "device")
-resource string
resource for which the token is requested
-secret string
application secret
-tenantId string
tenant id
-tokenCachePath string
location of oath token cache (default "/home/cgc/.adal/accessToken.json")
Example acquire a token for https://management.core.windows.net/
using device code flow:
adal -mode device \
-applicationId "APPLICATION_ID" \
-tenantId "TENANT_ID" \
-resource https://management.core.windows.net/