Skip to content

Azure AD

Warning

This feature is only available in team namespaces

Abstract

Abstract

The NAIS platform provides support for simple, declarative provisioning of an Azure AD client configured with sensible defaults.

An Azure AD client allows your application to leverage Azure AD for authentication and authorization.

The most common cases include:

Info

See the NAV Security Guide for NAV-specific usage of this client.

Concepts

The following describes a few core concepts in Azure AD referred to throughout this documentation.

Tenants

A tenant represents an organization in Azure AD. Each tenant will have their own set of applications, users and groups. In order to log in to a tenant, you must use an account specific to that tenant.

NAV has two tenants in Azure AD:

  • nav.no - available in all clusters, default tenant for production clusters
  • trygdeetaten.no - only available in dev-*-clusters, default tenant for development clusters

Warning

If your use case requires you to use nav.no in the dev-*-clusters, then you must explicitly configure this. Note that you cannot interact with clients or applications across different tenants.

The same application in different clusters will result in unique Azure AD clients, with each having their own client IDs and access policies. For instance, the following applications in the same nav.no tenant will result in separate, unique clients in Azure AD:

  • app-a in dev-gcp
  • app-a in prod-gcp

Naming format

An Azure AD client has an associated name within a tenant. NAIS uses this name for lookups and identification.

All clients provisioned through NAIS will be registered in Azure AD using the following naming scheme:

<cluster>:<namespace>:<app-name>
Example
dev-gcp:aura:nais-testapp

Scopes in token requests

Equivalently, the identifier used to refer to the application almost follows the same format.

The only notable difference is that : replaced by .

api://<cluster>.<namespace>.<app-name>

The above means that instead of using the API provider's client ID:

api://e89006c5-7193-4ca3-8e26-d0990d9d981f/.default

you can do this:

api://dev-gcp.aura.nais-testapp/.default

Client ID

An Azure AD client has its own ID that uniquely identifies the client within a tenant, and is used in authentication requests to Azure AD.

Your application's Azure AD client ID is available at multiple locations:

  1. The environment variable AZURE_APP_CLIENT_ID, available inside your application at runtime
  2. In the Kubernetes resource - kubectl get azureapp <app-name> -o wide
  3. The Azure Portal. You may have to click on All applications if it does not show up in Owned applications. Search using the naming scheme mentioned earlier: <cluster>:<namespace>:<app>.

Configuration

Spec

See the NAIS manifest.

Getting started

spec:
  azure:
    application:
      enabled: true
        
      # optional, enum of {trygdeetaten.no, nav.no}
      # defaults to trygdeetaten.no in dev-* clusters, nav.no in production
      tenant: nav.no

      # optional, generated defaults shown
      replyURLs: 
        - "https://my-app.dev.nav.no/oauth2/callback"

      # optional
      claims:
        extra:
          - "NAVident"
          - "azp_name"
        groups:
          - id: "<object ID of Azure AD group>"

  # optional, only relevant if your application should receive requests from consumers
  accessPolicy:
    inbound:
      rules:
        - application: app-a
          namespace: othernamespace
          cluster: dev-fss
        - application: app-b

  # required for on-premises only
  webproxy: true 

Accessing external hosts

Azure AD is a third-party service outside of our clusters, which is not reachable by default like most third-party services.

Google Cloud Platform (GCP)

The following outbound external hosts are automatically added when enabling this feature:

  • login.microsoftonline.com
  • graph.microsoft.com

You do not need to specify these explicitly.

On-premises

You must enable and use webproxy for external communication.

Reply URLs

A redirect URI, or reply URL, is the location that the authorization server will send the user to once the app has been successfully authorized, and granted an authorization code or access token. The code or token is contained in the redirect URI or reply token so it's important that you register the correct location as part of the app registration process.

-- Microsoft's documentation on reply URLs

Defaults

If you have not specified any reply URLs, we will automatically generate a reply URL for each ingress specified using this formula:

spec.ingresses[n] + "/oauth2/callback"
Example

In other words, this:

spec:
  ingresses:
    - "https://my.application.dev.nav.no"
    - "https://my.application.dev.nav.no/subpath"
  azure:
    application:
      enabled: true

will generate a spec equivalent to this:

spec:
  ingresses:
    - "https://my.application.dev.nav.no"
    - "https://my.application.dev.nav.no/subpath"
  azure:
    application:
      enabled: true
      replyURLs:
        - "https://my.application.dev.nav.no/oauth2/callback"
        - "https://my.application.dev.nav.no/subpath/oauth2/callback"

Overriding explicitly

You may set reply URLs manually by specifying spec.azure.application.replyURLs[]:

Example
spec:
  azure:
    application:
      enabled: true
      replyURLs:
        - "https://my.application.dev.nav.no/oauth2/callback"
        - "https://my.application.dev.nav.no/subpath/oauth2/callback"

Doing so will replace all of the default auto-generated reply URLs.

Danger

If you do override the reply URLs, make sure that you specify all the URLs that should be registered for the Azure AD client.

Ensure that these URLs conform to the restrictions and limitations of reply URLs as specified by Microsoft.

Tenants

To explicitly target a specific tenant, add a spec.azure.application.tenant to your nais.yaml:

spec:
  azure:
    application:
      enabled: true
      
      # enum of {trygdeetaten.no, nav.no}
      tenant: trygdeetaten.no 

Pre-authorization

For proper scoping of tokens when performing calls between clients, one must either:

  1. Request a token for a specific client ID using the client using the OAuth 2.0 client credentials flow (service-to-service calls).
  2. Exchange a token containing an end-user context using the OAuth 2.0 On-Behalf-Of flow (service-to-service calls on behalf of an end-user).

Azure AD will enforce authorization for both flows. In other words, you must pre-authorize any consumer clients for your application.

Clients that should receive and validate access tokens from other clients should pre-authorize said clients. These are declared by specifying spec.accessPolicy.inbound.rules[]:

spec:
  accessPolicy:
    inbound:
      rules:
        - application: app-a

        - application: app-b
          namespace: other-namespace

        - application: app-c
          namespace: other-namespace
          cluster: other-cluster

Danger

Any client referred to must already exist in Azure AD in order to be assigned the access policy permissions.

Be aware of dependency order when deploying your applications for the first time in each cluster with provisioning enabled.

Clients defined in the Spec that do not exist in Azure AD at deploy time will be skipped. Assignments will not be automatically retried.

The above configuration will pre-authorize the Azure AD clients belonging to:

  • application app-a running in the same namespace and same cluster as your application
  • application app-b running in the namespace other-namespace in the same cluster
  • application app-c running in the namespace other-namespace in the cluster other-cluster

Groups

By default, all users within the tenant is allowed to log in to your application.

For some use cases, it is desirable to restrict access to smaller groups of users.

This can be done by explicitly declaring which groups are allowed to access the application:

spec:
  azure:
    application:
      enabled: true
      claims:
        groups:
          - id: "<object ID of group in Azure AD>"

Azure AD will now only allow sign-ins and token exchanges with the on-behalf-of flow if a given user is a direct member of at least one of the groups declared.

This also controls the groups claim for a user token, which will only contain groups that are both explicitly assigned to the application and which the user is a direct member of.

Usage

Info

See the NAV Security Guide for NAV-specific usage.

Runtime Variables & Credentials

The following environment variables and files (under the directory /var/run/secrets/nais.io/azure) are available at runtime:

AZURE_APP_CLIENT_ID

Azure AD client ID. Unique ID for the application in Azure AD

Example value: e89006c5-7193-4ca3-8e26-d0990d9d981f

AZURE_APP_CLIENT_SECRET

Azure AD client secret, i.e. password for authenticating the application to Azure AD

Example value: b5S0Bgg1OF17Ptpy4_uvUg-m.I~KU_.5RR

AZURE_APP_JWKS

A JWK Set as defined in RFC7517 section 5. This will always contain a single key, i.e. AZURE_APP_JWK - the newest key registered.

Example value:

{
  "keys": [
    {
        "use": "sig",
        "kty": "RSA",
        "kid": "jXDxKRE6a4jogcc4HgkDq3uVgQ0",
        "n": "xQ3chFsz...",
        "e": "AQAB",
        "d": "C0BVXQFQ...",
        "p": "9TGEF_Vk...",
        "q": "zb0yTkgqO...",
        "dp": "7YcKcCtJ...",
        "dq": "sXxLHp9A...",
        "qi": "QCW5VQjO...",
        "x5c": [
          "MIID8jCC..."
        ],
        "x5t": "jXDxKRE6a4jogcc4HgkDq3uVgQ0",
        "x5t#S256": "AH2gbUvjZYmSQXZ6-YIRxM2YYrLiZYW8NywowyGcxp0"
    }
  ]
}
AZURE_APP_JWK

Private JWK as defined in RFC7517, i.e. a JWK with the private RSA key for creating signed JWTs when authenticating to Azure AD with a certificate.

Example value:

{
  "use": "sig",
  "kty": "RSA",
  "kid": "jXDxKRE6a4jogcc4HgkDq3uVgQ0",
  "n": "xQ3chFsz...",
  "e": "AQAB",
  "d": "C0BVXQFQ...",
  "p": "9TGEF_Vk...",
  "q": "zb0yTkgqO...",
  "dp": "7YcKcCtJ...",
  "dq": "sXxLHp9A...",
  "qi": "QCW5VQjO...",
  "x5c": [
    "MIID8jCC..."
  ],
  "x5t": "jXDxKRE6a4jogcc4HgkDq3uVgQ0",
  "x5t#S256": "AH2gbUvjZYmSQXZ6-YIRxM2YYrLiZYW8NywowyGcxp0"
}
AZURE_APP_PRE_AUTHORIZED_APPS

A JSON string. List of names and client IDs for the valid (i.e. those that exist in Azure AD) applications defined in spec.accessPolicy.inbound.rules[]

Example value:

[
  {
    "name": "dev-gcp:othernamespace:app-a",
    "clientId": "381ce452-1d49-49df-9e7e-990ef0328d6c"
  },
  {
    "name": "dev-gcp:aura:app-b",
    "clientId": "048eb0e8-e18a-473a-a87d-dfede7c65d84"
  }
]
AZURE_APP_TENANT_ID

The Azure AD tenant ID for which the Azure AD client resides in.

Example value: 77678b69-1daf-47b6-9072-771d270ac800

AZURE_APP_WELL_KNOWN_URL

The well-known URL to the metadata discovery document for the specific tenant in which the Azure AD client resides in.

Example value: https://login.microsoftonline.com/77678b69-1daf-47b6-9072-771d270ac800/v2.0/.well-known/openid-configuration

AZURE_OPENID_CONFIG_ISSUER

issuer from the metadata discovery document.

Example value: https://login.microsoftonline.com/77678b69-1daf-47b6-9072-771d270ac800/v2.0

AZURE_OPENID_CONFIG_JWKS_URI

jwks_uri from the metadata discovery document.

Example value: https://login.microsoftonline.com/77678b69-1daf-47b6-9072-771d270ac800/discovery/v2.0/keys

AZURE_OPENID_CONFIG_TOKEN_ENDPOINT

token_endpoint from the metadata discovery document.

Example value: https://login.microsoftonline.com/77678b69-1daf-47b6-9072-771d270ac800/oauth2/v2.0/token

Administration

Owner Access

Generally, configuration of the Azure AD client should be done through nais.yaml. In most cases you will not need to configure the application through the Azure Portal at all.

Access is limited in accordance with the principle of least privilege.

Rules:

  • If your Azure AD client exists in the nav.no tenant, your team's owners will automatically be given owner access.
  • Otherwise, the application will not be assigned any owners.
  • Special cases such as extra permissions are manually handled on a case-by-case basis.

If you are not registered as an owner in your team, you should either have an existing owner promote you or have them perform whatever you need.

Legacy

This section only applies if you have an existing Azure AD client registered in the IaC repository.

Why migrate?

  • Declarative provisioning, straight from your application's nais.yaml
  • No longer dependent on manual user approvals in multiple IaC repositories
  • No longer dependent on Vault
  • Credentials are rotated regularly, completely transparent to the application. This ensures that credentials are fresh and lessens the impact in the case of exposure.
  • The exact same feature is present in the GCP clusters, which simplifies migration.

Tenants

Pre-authorization

Communication between legacy clients provisioned through aad-iac and clients provisioned through NAIS requires some additional configuration.

Scenario 1

Allowing a NAIS client to access an aad-iac client

Prerequisites:

  • You have a legacy client registered in the aad-iac repository.
  • You would like to pre-authorize client provisioned through NAIS.

Steps:

  • Refer to the NAIS client in aad-iac using its fully qualified name (see naming format):
<cluster>:<namespace>:<app-name>

Example:


Scenario 2

Allowing an aad-iac client to access a NAIS client

Prerequisites:

  • You have a client provisioned through NAIS.
  • You would like to pre-authorize a legacy client registered in the aad-iac repository.

Steps:

Example:

spec:
  accessPolicy:
    inbound:
      rules:
      - application: dkif
        namespace: team-rocket
        cluster: dev-fss

Migration guide - step by step

The following describes the steps needed to migrate an existing legacy client where you wish to keep the existing client ID and configuration.

If keeping the existing client ID and configuration is not important, it should be much easier to just provision new clients instead.

Warning

Be aware of the differences in tenants between the IaC repository and NAIS:

  • nonprod -> trygdeetaten.no
  • prod -> nav.no
Step 1 - Rename your application in the Azure Portal

The Display name of the application registered in the Azure Portal must match the expected format.

  • Go to the Branding tab for your client in the Azure Portal.
  • Update the Name.
Step 2 - Update your application (and any dependants) in the IaC repository
  • Ensure the name of the client registered in the IaC repository is updated to match the name set in step 1.
  • Ensure that any clients that has a reference to the previous name in their preauthorizedapplications is also updated.
Step 3 - Deploy your NAIS application with Azure AD provisioning enabled
Step 4 - Delete your application from the IaC repository
  • Verify that everything works after the migration
  • Delete the application from the IaC repository in order to maintain a single source of truth

Operations

Permanently deleting a client

Warning

Permanent deletes are irreversible. Only do this if you are certain that you wish to completely remove the client from Azure AD.

When an AzureAdApplication resource is deleted from a Kubernetes cluster, the client is by default not deleted from Azure AD.

Details

In Kubernetes terms, the Application resource owns the AzureAdApplication resource.

Deletion of the Application will trigger a deletion of the AzureAdApplication. The actual client registered in Azure AD however is not deleted by default.

If the AzureAdApplication resource is recreated -- for example by redeploying a previously deleted Application -- it will thus retain the same Azure AD client ID.

If you want to completely delete the client from Azure AD, you must add the following annotation to the AzureAdApplication resource:

kubectl annotate azureapp <app> azure.nais.io/delete=true

When this annotation is in place, deleting the AzureAdApplication resource from Kubernetes will trigger removal of the client from Azure AD.

Forcing resynchronization

Synchronization to Azure AD only happens when at least one of two things happen:

  1. Any spec.azure.* or spec.accessPolicy.inbound.rules[] value has changed.
  2. An annotation is applied to the resource:
kubectl annotate azureapp <app> azure.nais.io/resync=true

The annotation is removed after synchronization. It can then be re-applied to trigger new synchronizations.

If you previously deployed an application where the access policy for pre-authorization included a non-existing client which now exists, you should thus force resynchronization to Azure AD as shown above.

Forcing credential rotation

Credential rotation happens automatically on a regular basis.

However, if you need to trigger rotation manually you may do so by applying the following annotation:

kubectl annotate azureapp <app> azure.nais.io/rotate=true

You should then restart your pods so that the new credentials are re-injected:

kubectl rollout restart deployment <app>