# Azure Active Directory plugin for client authentication
This plugin provides an integration with Azure Active Directory device flow. If no tokens are present in the kubectl configuration, it will prompt a device code which can be used to login in a browser. After login it will automatically fetch the tokens and store them in the kubectl configuration. In addition it will refresh and update the tokens in the configuration when expired.
## Usage
1. Create an Azure Active Directory *Web App / API* application for `apiserver` following these [instructions](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-app-registration). The callback URL does not matter (just cannot be empty).
2. Create a second Azure Active Directory native application for `kubectl`. The callback URL does not matter (just cannot be empty).
3. On `kubectl` application's configuration page in Azure portal grant permissions to `apiserver` application by clicking on *Required Permissions*, click the *Add* button and search for the apiserver application created in step 1. Select "Access apiserver" under the *DELEGATED PERMISSIONS*. Once added click the *Grant Permissions* button to apply the changes.
4. Configure the `apiserver` to use the Azure Active Directory as an OIDC provider with following options
* Replace the `APISERVER_APPLICATION_ID` with the application ID of `apiserver` application
* Replace `TENANT_ID` with your tenant ID.
* For a list of alternative username claims that are supported by the OIDC issuer check the JSON response at `https://sts.windows.net/TENANT_ID/.well-known/openid-configuration`.
5. Configure `kubectl` to use the `azure` authentication provider
* Replace `USER_NAME` and `TENANT_ID` with your user name and tenant ID
* Replace `APPLICATION_ID` with the application ID of your`kubectl` application ID
* Replace `APISERVER_APPLICATION_ID` with the application ID of your `apiserver` application ID
* Be sure to also (create and) select a context that uses above user
6. The access token is acquired when first `kubectl` command is executed
```
kubectl get pods
To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code DEC7D48GA to authenticate.
```
* After signing in a web browser, the token is stored in the configuration, and it will be reused when executing further commands.
* The resulting username in Kubernetes depends on your [configuration of the `--oidc-username-claim` and `--oidc-username-prefix` flags on the API server](https://kubernetes.io/docs/admin/authentication/#configuring-the-api-server). If you are using any authorization method you need to give permissions to that user, e.g. by binding the user to a role in the case of RBAC.
return"",fmt.Errorf("failed to refresh token: %v",err)
}
idToken,ok:=token.Extra("id_token").(string)
if!ok{
// id_token isn't a required part of a refresh token response, so some
// providers (Okta) don't return this value.
//
// See https://github.com/kubernetes/kubernetes/issues/36847
return"",fmt.Errorf("token response did not contain an id_token, either the scope \"openid\" wasn't requested upon login, or the provider doesn't support id_tokens as part of the refresh response.")
}
// Create a new config to persist.
newCfg:=make(map[string]string)
forkey,val:=rangep.cfg{
newCfg[key]=val
}
// Update the refresh token if the server returned another one.
iftoken.RefreshToken!=""&&token.RefreshToken!=rt{
newCfg[cfgRefreshToken]=token.RefreshToken
}
newCfg[cfgIDToken]=idToken
// Persist new config and if successful, update the in memory config.
iferr=p.persister.Persist(newCfg);err!=nil{
return"",fmt.Errorf("could not persist new tokens: %v",err)
}
p.cfg=newCfg
returnidToken,nil
}
// tokenEndpoint uses OpenID Connect discovery to determine the OAuth2 token
// endpoint for the provider, the endpoint the client will use the refresh
klog.Warningf("WARNING: in-tree openstack auth plugin is now deprecated. please use the \"client-keystone-auth\" kubectl/client-go credential plugin instead")
ttl,found:=config["ttl"]
if!found{
ttlDuration=DefaultTTLDuration
// persist to config
config["ttl"]=ttlDuration.String()
iferr=persister.Persist(config);err!=nil{
returnnil,fmt.Errorf("failed to persist config: %s",err)
}
}else{
ttlDuration,err=time.ParseDuration(ttl)
iferr!=nil{
returnnil,fmt.Errorf("failed to parse ttl config: %s",err)
}
}
authOpt:=gophercloud.AuthOptions{
IdentityEndpoint:config["identityEndpoint"],
Username:config["username"],
Password:config["password"],
DomainName:config["name"],
TenantID:config["tenantId"],
TenantName:config["tenantName"],
}
getter:=tokenGetter{}
// not empty
if(authOpt!=gophercloud.AuthOptions{}){
iflen(authOpt.IdentityEndpoint)==0{
returnnil,fmt.Errorf("empty %q in the config for openstack auth provider","identityEndpoint")