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

Microsoft Entra ID provider #2390

Merged
merged 20 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/docs/configuration/alpha_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,16 @@ character.
| `default` | _[]string_ | _(Optional)_ Default specifies a default value or values that will be<br/>passed to the IdP if not overridden. |
| `allow` | _[[]URLParameterRule](#urlparameterrule)_ | _(Optional)_ Allow specifies rules about how the default (if any) may be<br/>overridden via the query string to `/oauth2/start`. Only<br/>values that match one or more of the allow rules will be<br/>forwarded to the IdP. |

### MicrosoftEntraIDOptions

(**Appears on:** [Provider](#provider))



| Field | Type | Description |
| ----- | ---- | ----------- |
| `allowedTenants` | _[]string_ | AllowedTenants is a list of allowed tenants. In case of multi-tenant apps, incoming tokens are<br/>issued by different issuers and OIDC issuer verification needs to be disabled.<br/>When not specified, all tenants are allowed. Redundant for single-tenant apps<br/>(regular ID token validation matches the issuer). |

### OIDCOptions

(**Appears on:** [Provider](#provider))
Expand Down Expand Up @@ -418,6 +428,7 @@ Provider holds all configuration for a single provider
| `clientSecretFile` | _string_ | ClientSecretFile is the name of the file<br/>containing the OAuth Client Secret, it will be used if ClientSecret is not set. |
| `keycloakConfig` | _[KeycloakOptions](#keycloakoptions)_ | KeycloakConfig holds all configurations for Keycloak provider. |
| `azureConfig` | _[AzureOptions](#azureoptions)_ | AzureConfig holds all configurations for Azure provider. |
| `microsoftEntraIDConfig` | _[MicrosoftEntraIDOptions](#microsoftentraidoptions)_ | MicrosoftEntraIDConfig holds all configurations for Entra ID provider. |
| `ADFSConfig` | _[ADFSOptions](#adfsoptions)_ | ADFSConfig holds all configurations for ADFS provider. |
| `bitbucketConfig` | _[BitbucketOptions](#bitbucketoptions)_ | BitbucketConfig holds all configurations for Bitbucket provider. |
| `githubConfig` | _[GitHubOptions](#githuboptions)_ | GitHubConfig holds all configurations for GitHubC provider. |
Expand Down
18 changes: 0 additions & 18 deletions docs/docs/configuration/providers/azure_ad.md

This file was deleted.

4 changes: 4 additions & 0 deletions docs/docs/configuration/providers/gitea.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ id: gitea
title: Gitea
---

:::note
This is not actually its own provider. For more details and options please refer to the [GitHub Provider Options](github.md)
:::

1. Create a new application: `https://< your gitea host >/user/settings/applications`
2. Under `Redirect URI` enter the correct URL i.e. `https://<proxied host>/oauth2/callback`
3. Note the Client ID and Client Secret.
Expand Down
17 changes: 9 additions & 8 deletions docs/docs/configuration/providers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ with Redirect URI(s) for the domain you intend to run `oauth2-proxy` on.

Valid providers are :

- [Google](google.md) _default_
- [Azure](azure.md)
- [ADFS](adfs.md)
- [Bitbucket](bitbucket.md)
- [DigitalOcean](digitalocean.md)
- [Facebook](facebook.md)
- [GitHub](github.md)
- [Gitea](gitea.md)
- [Keycloak](keycloak.md)/[Keycloak OIDC](keycloak_oidc.md)
- [GitHub](github.md)
- [GitLab](gitlab.md)
- [Google](google.md) _default_
- [Keycloak](keycloak.md) (Deprecated)
- [Keycloak OIDC](keycloak_oidc.md)
- [LinkedIn](linkedin.md)
- [Microsoft Azure AD](azure_ad.md)
- [OpenID Connect](openid_connect.md)
- [login.gov](login_gov.md)
- [Microsoft Azure](ms_azure_ad.md) (Deprecated)
- [Microsoft Entra ID](ms_entra_id.md)
- [Nextcloud](nextcloud.md)
- [DigitalOcean](digitalocean.md)
- [Bitbucket](bitbucket.md)
- [OpenID Connect](openid_connect.md)

The provider can be selected using the `provider` configuration value.

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/configuration/providers/keycloak.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
id: keycloak
title: Keycloak
title: Keycloak (Deprecated)
---

:::note
This is the legacy provider for Keycloak, use [Keycloak OIDC Auth Provider](keycloak_oidc.md) if possible.
This is the legacy and deprecated provider for Keycloak, use [Keycloak OIDC Auth Provider](keycloak_oidc.md) if possible.
:::

1. Create new client in your Keycloak realm with **Access Type** 'confidential' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
---
id: azure
title: Azure
title: Azure (Deprecated)
---

:::note
This is the legacy and deprecated provider for Azure, use [Microsoft Entra ID](ms_entra_id.md) if possible.
:::

## Config Options

| Flag | Toml Field | Type | Description | Default |
Expand Down
158 changes: 158 additions & 0 deletions docs/docs/configuration/providers/ms_entra_id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
id: ms_entra_id
title: Microsoft Entra ID
---

Provider for Microsoft Entra ID. Fully compliant with OIDC, with support for group overage and multi-tenant apps.

## Config Options

The provider is OIDC-compliant, so all the OIDC parameters are honored. Additional provider-specific configuration parameters are:

| Flag | Toml Field | Type | Description | Default |
| --------------------------- | -------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `--entra-id-allowed-tenant` | `entra_id_allowed_tenants` | string \| list | List of allowed tenants. In case of multi-tenant apps, incoming tokens are issued by different issuers and OIDC issuer verification needs to be disabled. When not specified, all tenants are allowed. Redundant for single-tenant apps (regular ID token validation matches the issuer). | |

## Configure App registration
To begin, create an App registration, set a redirect URI, and generate a secret. All account types are supported, including single-tenant, multi-tenant, multi-tenant with Microsoft accounts, and Microsoft accounts only.

<details>
<summary>See Azure Portal example</summary>
<div class="videoBlock">
<iframe src="https://www.youtube.com/embed/IUNfxhOzr4E"></iframe>
</div>
</details>

<details>
<summary>See Terraform example</summary>
```
resource "azuread_application" "auth" {
display_name = "oauth2-proxy"
sign_in_audience = "AzureADMyOrg" # Others are also supported

web {
redirect_uris = [
"https://podinfo.lakis.tech/oauth2/callback",
]
}
// We don't specify any required API permissions - we allow user consent only
}

resource "azuread_service_principal" "sp" {
client_id = azuread_application.auth.client_id
app_role_assignment_required = false
}

resource "azuread_service_principal_password" "pass" {
service_principal_id = azuread_service_principal.sp.id
}

```
</details>

### Configure groups
If you want to make use of groups, you can configure *groups claim* to be present in ID Tokens issued by the App registration.
<details>
<summary>See Azure Portal example</summary>
<div class="videoBlock">
<div class="videoBlock">
<iframe src="https://www.youtube.com/embed/QZmP5MKEJis"></iframe>
</div>
</div>
</details>
<details>
<summary>See Terraform example</summary>
```
resource "azuread_application" "auth" {
display_name = "oauth2-proxy"
sign_in_audience = "AzureADMyOrg"

group_membership_claims = [
"SecurityGroup"
]

web {
redirect_uris = [
"https://podinfo.lakis.tech/oauth2/callback",
]
}
}

resource "azuread_service_principal" "sp" {
client_id = azuread_application.auth.client_id
app_role_assignment_required = false
}

resource "azuread_service_principal_password" "pass" {
service_principal_id = azuread_service_principal.sp.id
}

```
</details>

### Scopes and claims
For single-tenant and multi-tenant apps without groups, the only required scope is `openid` (See: [Scopes and permissions](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#the-openid-scope)).

To make use of groups - for example use `allowed_groups` setting or authorize based on groups inside your service - you need to enable *groups claims* in the App Registration. When enabled, list of groups is present in the issued ID token. No additional scopes are required besides `openid`. This works up to 200 groups.

When user has more than 200 group memberships, OAuth2-Proxy attempts to retrieve the complete list from Microsoft Graph API's [`transitiveMemberOf`](https://learn.microsoft.com/en-us/graph/api/user-list-transitivememberof). Endpoint requires `User.Read` scope (delegated permission). This permission can be by default consented by user during first login. Set scope to `openid User.Read` to request user consent. Without proper scope, user with 200+ groups will authenticate with 0 groups. See: [group overages](https://learn.microsoft.com/en-us/security/zero-trust/develop/configure-tokens-group-claims-app-roles#group-overages).

Alternatively to user consent, both `openid` and `User.Read` permissions can be consented by admistrator. Then, user is not asked for consent on the first login, and group overage works with `openid` scope only. Admin consent can also be required for some tenants. It can be granted with [azuread_service_principal_delegated_permission_grant](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal_delegated_permission_grant) terraform resource.

For personal microsoft accounts, required scope is `openid profile email`.

See: [Overview of permissions and consent in the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview).

### Multi-tenant apps
To authenticate apps from multiple tenants (including personal Microsoft accounts), set the common OIDC issuer url and disable verification:
```toml
oidc_issuer_url=https://login.microsoftonline.com/common/v2.0
insecure_oidc_skip_issuer_verification=true
```
jjlakis marked this conversation as resolved.
Show resolved Hide resolved
`insecure_oidc_skip_issuer_verification` setting is required to disable following checks:
* Startup check for matching issuer URL returned from [discovery document](https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration) with `oidc_issuer_url` setting. Required, as document's `issuer` field doesn't equal to `https://login.microsoftonline.com/common/v2.0`. See [OIDC Discovery 4.3](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation).
* Matching ID token's `issuer` claim with `oidc_issuer_url` setting during ID token validation. Required to support tokens issued by diffrerent tenants. See [OIDC Core 3.1.3.7](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation).

To provide additional security, Entra ID provider performs check on the ID token's `issuer` claim to match the `https://login.microsoftonline.com/{tenant-id}/v2.0` template.

### Example configurations
Single-tenant app without groups (*groups claim* not enabled). Consider using generic OIDC provider:
```toml
provider="entra-id"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this fit the theme of other provider names?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly do you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really have a naming scheme. Most are just lower case like github, bitbucket, google or bitbucket but we have login.gov as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps at some point we should standardise these, so they are all, for example, PascalCase or something?

oidc_issuer_url="https://login.microsoftonline.com/<tenant-id>/v2.0"
client_id="<client-id>"
client_secret="<client-secret>"
scope="openid"
```

Single-tenant app with up to 200 groups (*groups claim* enabled). Consider using generic OIDC provider:
```toml
provider="entra-id"
oidc_issuer_url="https://login.microsoftonline.com/<tenant-id>/v2.0"
client_id="<client-id>"
client_secret="<client-secret>"
scope="openid"
allowed_groups=["ac51800c-2679-4ecb-8130-636380a3b491"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the allowed groups is a list of group IDs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes as per every provider

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, maybe it would be possible/better to have the group names/email addresses? But I guess that doesn't make things unique at the provider side 🤔

```
jjlakis marked this conversation as resolved.
Show resolved Hide resolved

Single-tenant app with more than 200 groups:
```toml
provider="entra-id"
oidc_issuer_url="https://login.microsoftonline.com/<tenant-id>/v2.0"
client_id="<client-id>"
client_secret="<client-secret>"
scope="openid User.Read"
allowed_groups=["968b4844-d5e7-4e18-a834-59927959369f"]
```
jjlakis marked this conversation as resolved.
Show resolved Hide resolved

Multi-tenant app with Microsoft personal accounts & one Entra tenant allowed, with group overage considered:
```toml
provider="entra-id"
oidc_issuer_url="https://login.microsoftonline.com/common/v2.0"
client_id="<client-id>"
client_secret="<client-secret>"
insecure_oidc_skip_issuer_verification=true
scope="openid profile email User.Read"
entra_id_allowed_tenant=["9188040d-6c67-4c5b-b112-36a304b66dad","<my-tenant-id>"] # Allow only <my-tenant-id> and Personal MS Accounts tenant
email_domains="*"
```
32 changes: 16 additions & 16 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@ const sidebars = {
id: 'configuration/providers/index',
},
items: [
'configuration/providers/google',
'configuration/providers/azure',
'configuration/providers/adfs',
'configuration/providers/facebook',
'configuration/providers/github',
'configuration/providers/gitea',
'configuration/providers/keycloak',
'configuration/providers/keycloak_oidc',
'configuration/providers/gitlab',
'configuration/providers/linkedin',
'configuration/providers/azure_ad',
'configuration/providers/openid_connect',
'configuration/providers/login_gov',
'configuration/providers/nextcloud',
'configuration/providers/digitalocean',
'configuration/providers/bitbucket',
"configuration/providers/adfs",
"configuration/providers/azure",
"configuration/providers/bitbucket",
"configuration/providers/digitalocean",
"configuration/providers/facebook",
"configuration/providers/gitea",
"configuration/providers/github",
"configuration/providers/gitlab",
"configuration/providers/google",
"configuration/providers/keycloak",
"configuration/providers/keycloak_oidc",
"configuration/providers/linkedin",
"configuration/providers/login_gov",
"configuration/providers/ms_entra_id",
"configuration/providers/nextcloud",
"configuration/providers/openid_connect",
],
},
'configuration/session_storage',
Expand Down
14 changes: 14 additions & 0 deletions docs/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
}

.videoBlock {
position: relative;
padding-bottom: 75%;
height: 0;
}

.videoBlock iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
6 changes: 6 additions & 0 deletions pkg/apis/options/legacy_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ type LegacyProvider struct {
KeycloakGroups []string `flag:"keycloak-group" cfg:"keycloak_groups"`
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
AzureGraphGroupField string `flag:"azure-graph-group-field" cfg:"azure_graph_group_field"`
EntraIDAllowedTenants []string `flag:"entra-id-allowed-tenant" cfg:"entra_id_allowed_tenants"`
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team"`
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository"`
GitHubOrg string `flag:"github-org" cfg:"github_org"`
Expand Down Expand Up @@ -550,6 +551,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
flagSet.StringSlice("keycloak-group", []string{}, "restrict logins to members of these groups (may be given multiple times)")
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
flagSet.String("azure-graph-group-field", "", "configures the group field to be used when building the groups list(`id` or `displayName`. Default is `id`) from Microsoft Graph(available only for v2.0 oidc url). Based on this value, the `allowed-group` config values should be adjusted accordingly. If using `id` as group field, `allowed-group` should contains groups IDs, if using `displayName` as group field, `allowed-group` should contains groups name")
flagSet.StringSlice("entra-id-allowed-tenant", []string{}, "list of tenants allowed for MS Entra ID multi-tenant application")
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
flagSet.String("github-org", "", "restrict logins to members of this organisation")
Expand Down Expand Up @@ -756,6 +758,10 @@ func (l *LegacyProvider) convert() (Providers, error) {
UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials,
TargetPrincipal: l.GoogleTargetPrincipal,
}
case "entra-id":
provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{
AllowedTenants: l.EntraIDAllowedTenants,
}
}

if l.ProviderName != "" {
Expand Down
13 changes: 13 additions & 0 deletions pkg/apis/options/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type Provider struct {
KeycloakConfig KeycloakOptions `json:"keycloakConfig,omitempty"`
// AzureConfig holds all configurations for Azure provider.
AzureConfig AzureOptions `json:"azureConfig,omitempty"`
// MicrosoftEntraIDConfig holds all configurations for Entra ID provider.
MicrosoftEntraIDConfig MicrosoftEntraIDOptions `json:"microsoftEntraIDConfig,omitempty"`
// ADFSConfig holds all configurations for ADFS provider.
ADFSConfig ADFSOptions `json:"ADFSConfig,omitempty"`
// BitbucketConfig holds all configurations for Bitbucket provider.
Expand Down Expand Up @@ -101,6 +103,9 @@ const (
// AzureProvider is the provider type for Azure
AzureProvider ProviderType = "azure"

// MicrosoftEntraIDProvider is the provider type for Entra OIDC
MicrosoftEntraIDProvider ProviderType = "entra-id"

// BitbucketProvider is the provider type for Bitbucket
BitbucketProvider ProviderType = "bitbucket"

Expand Down Expand Up @@ -155,6 +160,14 @@ type AzureOptions struct {
GraphGroupField string `json:"graphGroupField,omitempty"`
}

type MicrosoftEntraIDOptions struct {
// AllowedTenants is a list of allowed tenants. In case of multi-tenant apps, incoming tokens are
// issued by different issuers and OIDC issuer verification needs to be disabled.
// When not specified, all tenants are allowed. Redundant for single-tenant apps
// (regular ID token validation matches the issuer).
AllowedTenants []string `json:"allowedTenants,omitempty"`
tuunit marked this conversation as resolved.
Show resolved Hide resolved
}

type ADFSOptions struct {
// Skip adding the scope parameter in login request
// Default value is 'false'
Expand Down
Loading
Loading