Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Container Registry integration with Azure Active Directory - doc in progress #32

Closed
DavidObando opened this issue Mar 2, 2017 · 2 comments
Assignees

Comments

@DavidObando
Copy link
Member

DavidObando commented Mar 2, 2017

Azure Container Registry integration with Azure Active Directory

The Azure Container Registry allows users to manage a private Docker registry on the cloud. Our service enables customers to store and manage container images across all types of Azure deployments, keep container images near deployments to reduce latency and costs, maintain Windows and Linux container images in a single Docker registry, use familiar, open-source Docker command line interface (CLI) tools, and simplify registry access management with Azure Active Directory.

The integration of Azure Container Registry with Azure Active Directory is crucial in order to enable transparent authentication and authorization of users and headless services using AAD credentials. In this scenario, a user will only have to use their AAD credentials to log-in to their private registry, and the Azure Container Service will take care of the authorization validation of each operation using the provided credentials.

Under the hood Azure Container Service utilizes the oauth2 authorization protocol, as described by the Docker Registry v2 authentication via central service documentation as well as the Docker Registry v2 Bearer token specification. The JWT tokens generated by the Azure Container Registry are easy to observe in jwt.io.

Authenticating to a registry with Azure CLI

The process to log in to the registry, from the user's perspective, is simple. The user will use the Microsoft Azure CLI 2.0:

az acr login -n contosoregistry

Internally, the CLI will follow these steps:

  1. Calls to Azure Resource Manager to resolve the login server for the specified registry.
  2. Obtains refresh credentials from the profile in use. For a headless call, this will give you the registered SPN, for a regular user this will give you a refresh token.
  3. Makes an HTTPS GET call to the registry server's /v2 endpoint, without credentials. A bearer token authentication challenge is expected, specifying realm and service values. The realm contains the authentication server's URL.
  4. Makes an HTTPS POST call to the authentication server's /oauth2/exchange endpoint, with a body indicating the grant type, the service, the tenant, and the credentials.
  5. From the server's response, we extract an Azure Container Registry refresh token.
  6. Pass the refresh token as the password to the Docker CLI, using a null GUID as the username and calling docker login. From here on, the docker CLI takes care of the authorization cycle using oauth2.

At the end Docker will store the refresh token and go through the oauth2 flow on each operation it does against the Azure Container Registry.

Listing a repository with Azure CLI

The Microsoft Azure CLI 2.0 allows users to also list the repositories registries, and list tags for a repository in a registry. Here's how users can achieve listing the repositories in a registry:

az acr repository list -n contosoregistry

Internally, the CLI will follow these steps:

  1. Calls to Azure Resource Manager to resolve the login server for the specified registry.
  2. Obtains refresh credentials from the profile in use. For a headless call, this will give you the registered SPN, for a regular user this will give you a refresh token.
  3. Makes an HTTPS GET call to the registry server's /v2 endpoint, without credentials. A bearer token authentication challenge is expected, specifying realm and service values. The realm contains the authentication server's URL.
  4. Makes an HTTPS POST call to the authentication server's /oauth2/exchange endpoint, with a body indicating the grant type, the service, the tenant, and the credentials.
  5. From the server's response we extract an Azure Container Registry refresh token.
  6. Makes an HTTPS POST call to the authentication server's /oauth2/token endpoint, with a body indicating the grant type, the service, the scope, and the Azure Container Registry refresh token.
  7. From the server's response we extract an Azure Container Registry access token.
  8. Makes an HTTPS GET call to the registry server's /v2/_catalog endpoint using the access token as the bearer token.
  9. Obtains the data from the service and displays it.

When listing the tags of a repository, every step above is the same except for the call to the endpoint that gives the tags which is /v2/contosoregistry/tags/list instead of /v2/_catalog.

Azure Container Registry refresh tokens and access tokens

Let's follow an example call to list a repository:

az acr repository list -n contosoregistry

This will produce a JWT refresh token with the following payload:

{
  "jti": "365e3b5b-844e-4a21-a38c-4d8aebdd6a06",
  "sub": "user@contoso.com"
  "nbf": 1497988712,
  "exp": 1497990801,
  "iat": 1497988712,
  "iss": "Azure Container Registry",
  "aud": "contosoregistry.azurecr.io",
  "version": "1.0",
  "grant_type": "access_token_refresh_token",
  "tenant": "409520d4-8100-4d1d-ad47-72432ddcc120",
  "credential": "AQA...iAA"
  "permissions": {
    "actions": [
      "*"
    ],
    "notActions": []
  }
}

Followed by an access token with the following payload:

{
  "jti": "ec425c1e-7eda-4f70-adb5-19f927e34a41",
  "sub": "user@contoso.com"
  "nbf": 1497988907,
  "exp": 1497993407,
  "iat": 1497988907,
  "iss": "Azure Container Registry",
  "aud": "contosoregistry.azurecr.io",
  "access": [
    {
      "type": "registry",
      "name": "catalog",
      "actions": [
        "*"
      ]
    }
  ]
}

Getting credentials programatically

In order to sign in to a container you'll need to exchange AAD credentials for ACR credentials. The accepted form of credential exchange are:

  • AAD access token
  • AAD refresh token
  • AAD access token and refresh token

Ideally you'll present both the AAD access token and the AAD refresh token. The AAD access token is used to talk to the Azure Resource Manager and query for the set of permissions that the user has for the container registry resource. The AAD refresh token is used in two ways:

  1. If no AAD access token was presented, the AAD refresh token is used to obtain an AAD access token.
  2. The AAD refresh token is sent back to the user so they can initiate a token refresh cycle against AAD. If no AAD refresh token is sent, then the client won't have this credential at hand to initiate a credential refresh.

The cycle to get credentials looks as follows:

  1. Call /oauth2/exchange presenting the AAD refresh token and the AAD access token. The service will return you an ACR refresh token.
  2. Call /oauth2/token presenting the ACR refresh token. The service will return you an ACR access token which you can use to call the Azure Container Registry's APIs.

Calling /oauth2/exchange to get an ACR refresh token

Assume you have the following:

  1. A valid container registry, which here we'll call contosoregistry.azurecr.io
  2. The AAD tenant identifier associated to the credentials, which here we'll take to be 409520d4-8100-4d1d-ad47-72432ddcc120.
  3. Valid AAD access token and AAD refresh token credentials with access to the aforementioned container registry.

Here's how such a call looks when done via curl:

export registry="contosoregistry.azurecr.io"
export tenant="409520d4-8100-4d1d-ad47-72432ddcc120"
export aad_refresh_token="AQA...iAA"
export aad_access_token="eyJ...H-g"
curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d \
"grant_type=access_token_refresh_token&service=$registry&tenant=$tenant&refresh_token=$aad_refresh_token&access_token=$aad_access_token" \
https://$registry/oauth2/exchange

The body of the POST message is a querystring-like text that specifies the following values:

  • grant_type, which can take a value of access_token_refresh_token, or access_token, or refresh_token.
  • service, which must indicate the name of your Azure container registry.
  • tenant, which is the AAD tenant associated to the AAD credentials.
  • refresh_token, the AAD refresh token, mandatory when grant_type is access_token_refresh_token or refresh_token.
  • access_token, the AAD access token, mandatory when grant_type is access_token_refresh_token or access_token.

The outcome of this operation will be a response with status 200 OK and a body with the following JSON payload:

{"refresh_token":"eyJ...L7a"}

This response is the ACR refresh token which you can inspect with jwt.io. You can now use it to obtain an ACR access token programmatically or simply send it to the docker login command to get docker talking to the Azure Container Registry.

Authenticating docker with an ACR refresh token

Once you have obtained an ACR refresh token, you can use the docker CLI to sign in to your registry like this:

export registry="contosoregistry.azurecr.io"
export acr_username="00000000-0000-0000-0000-000000000000"
export acr_refresh_token="eyJ...L7a"
docker login -u "$acr_username" -p "$acr_refresh_token" $registry

The null GUID tells the container registry that this is an ACR refresh token during the login flow.

Calling /oauth2/token to get an ACR access token

Assume you have the following:

  1. A valid container registry, which here we'll call contosoregistry.azurecr.io.
  2. A valid ACR refresh token.
  3. The desired scope for the operation, as specified here and here; in this example we'll ask for the "registry:catalog:*" scope that will allow us to call the /v2/_catalog API.

Here's how such a call looks when done via curl:

export registry="contosoregistry.azurecr.io"
export acr_refresh_token="eyJ...L7a"
export scope="registry:catalog:*"
curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d \
"grant_type=refresh_token&service=$registry&scope=$scope&refresh_token=$acr_refresh_token" \
https://$registry/oauth2/token

The body of the POST message is a querystring-like text that specifies the following values:

  • grant_type which is expected to be refresh_token.
  • service, which must indicate the name of your Azure container registry.
  • scope, which is expected to be a valid scope, and can be specified more than once for multiple scope requests.
  • refresh_token, which must be a valid ACR refresh token, as obtained by calling /oauth2/exchange.

The outcome of this operation will be a response with status 200 OK and a body with the following JSON payload:

{"access_token":"eyJ...xcg"}

This response is the ACR access token which you can inspect with jwt.io. You can now use it to call APIs exposed by the Azure Container Registry

Calling an Azure Container Registry API

Assume you have the following:

  1. A valid container registry, which here we'll call contosoregistry.azurecr.io.
  2. A correctly crafted ACR access token

Here's how a call to an Azure Container Registry API would look like when done via curl:

export registry="contosoregistry.azurecr.io"
export acr_access_token="eyJ...xcg"
curl -v -H "Authorization: Bearer $acr_access_token" https://$registry/v2/_catalog

This should result in a status 200 OK, and a body with a JSON payload listing the repositories held in this registry:

{"repositories":["alpine","hello-world","contoso-marketing"]}
@sajayantony
Copy link
Contributor

sajayantony commented Jun 20, 2017

Please add documentation for how to use /exchange to obtain the token as well as details for crafting the token.
Could you also explain how a user can manually obtain this token and update their docker config without doing a docker login or use that JWT for docker login
Also please move this to docs/AAD-OAuth.md

@DavidObando
Copy link
Member Author

@sajayantony I've updated this. Let me know what you think, I'll go ahead with copying it into docs/AAD-OAuth.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants