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

Make AzureProvider a child type of OIDCProvider #2384

Closed
jjlakis opened this issue Jan 11, 2024 · 4 comments
Closed

Make AzureProvider a child type of OIDCProvider #2384

jjlakis opened this issue Jan 11, 2024 · 4 comments

Comments

@jjlakis
Copy link
Contributor

jjlakis commented Jan 11, 2024

Make AzureProvider a child type of OIDCProvider

This issue aims to collect information and considerations regarding making AzureProvider type a child type of OIDCProvider.

Reasoning

Azure authentication endpoint (V2) follows the OIDC specification, so this refactor would reduce complexity for the provider-specific files and re-use most of the generic OIDC features.

The root of this initiative lies in enabling Workload identity for the Azure provider. Azure authentication supports client_assertion certificate credential for a token exchange endpoint. Certificate credential is a replacement for client-secret exchange and it's supported natively with Azure Workload Identity. The support for cert credentials for Azure provider was initially proposed by myself in #2364, but as @tuunit pointed out, client_assertion is a feature of OIDC and we should rather implement this in generic OIDC provider and make the Azure provider re-use most of the methods.

What's outstanding in Azure authentication

In vast majority of the cases, Azure authentication can be used with generic OIDC. Specifically when:

  • Using Azure authentication V2 endpoint
  • Having less that 200 groups
  • Using single-tenant App Registration

So we need to cover three corner cases.

Handling V1 endpoint

Sadly, V1 endpoint isn't compliant with OIDC, and whole idea of V2 is to make it more compliant with the standards. Also, lots of the complexity in the provider code comes from backward compatiblity with V1 endpoint. For example: V1 endpoint requires a resource parameter in the authorization request that is incompliant with OIDC, V2 supports compliant scope. This results in the ugly conditions in the code. Azure AD endpoint comparison perfectly describes the differences.

It's hard to say how common V1 endpoint is. If we want to keep the V1 endpoint support, I would suggest to keep the existing provider as legacy and introduce new one that base on generic OIDC, otherwise we will end up buidling conditions and overriding methods. We can also take advantage of the refactor and name the new provider for example azure-entra-id.

Handling groups

Some higlights regarding groups:

  • Azure JWT returns up to 200 groups IDs in the groups claim.
  • Thanks to #1574, groups are read from Graph API instead of the claim.
  • Additionally to the change above, user can set --azure-graph-group-field to use friendly group displayNames instead of ids.
  • Unfortunately reading groups from the Graph API requires a wide permission and it's often an overkill. #1972 is about to resolve this problem - call Graph API only when needed (when configured different graph group field or number of groups exceeds 200).

So the implementation of EnrichSession() has to be custom for this case, implemented ideally with sync of #1972, this would cover single-tenant & multi-Azure tenant group overage.

However, when using multi-tenant login with personal microsoft account (for example: Github) and not through Azure tenant, we are completely unable to read the groups through Graph API. The GroupMember.Read.All permission requires admin constent on the tenant. ID tokens comming from personal MS account (github, outlook, etc.) are signed by some internal MS tenant (for example 9188040d-6c67-4c5b-b112-36a304b66dad) and we won't get this consent. In this scenario we can't read groups from access token neither, as access tokens for such accounts are not JWTs.

Current version of Azure provider does not allow personal microsoft account.

Handling multi-tenant apps

Multi-tenant app OIDC endpoint addresses do not include the tenant id:

https://login.microsoftonline.com/common/v2.0

The OIDC discovery document has a following entry:

"issuer":"https://login.microsoftonline.com/{tenantid}/v2.0"

Which is pretty much a template and cannot be validated by regular OIDC middleware. As current documentation states, to make the azure multi-tenancy working, we need to disable issuer verification with CLI flag:

--insecure-oidc-skip-issuer-verification

This flag is sufficient to make the current version of oauth2-proxy working with multi azure tenant apps (no personal account). Skipping issuer verification is inevitable, it's even listed in the recommended approach for issuer verification in the multi-tenant apps:

https://github.com/Azure-Samples/guidance-identity-management-for-multitenant-apps/blob/master/docs/04-working-with-claims.md#issuer-validation

(this document is 8 years old but still remains generally valid)

What we can do though is to create a CLI parameter to specify list of allowed tenants or insecurely allow all tenants. Then, according to the documentation above, check against the tid or issuer claim in the idToken and decide whether user is authenticated.

The client_secret for multi-tenant app has to be a global App registration password, in oppose to Service Principal Password which is tenant-specific.

Other considerations

AccessToken failover fo email claim

Currently we do fallback to access token when reading the email claim:
https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/azure.go#L262-L273

That's OK, minus the fact that when using personal microsoft account (outlook, gitlab), the Access Token is not JWT. So in case email claim is missing in the id_token, we are running into a misleading error, when trying to read the claim from invalid token. Thus, we need to be sure that we include email as id_token optional claim for the app registration. For terraform, we can azuread_application_optional_claims resource.

resource "azuread_application_optional_claims" "example" {
  application_id = azuread_application.auth.id

  id_token {
    name = "email"
  }
}

Anyway seems like these issues are not valid anymore, so we can just rely on the ID Token with proper claim in place.

Reading e-mail from Profile API

Currently we read e-mail from the profile API:
https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/azure.go#L413

This is not necessary and we could read the mail from id_token from properly configured app registration (see above).

It's important to mention that when user does not have the e-mail set, the claim isn't set.

Other higlights

Multi-tenant configuration debugging

Without --insecure-oidc-skip-issuer-verification flag:

[2024/01/09 19:51:19] [main.go:60] ERROR: Failed to initialise OAuth2 Proxy: error initialising provider: could not create provider data: error building OIDC ProviderVerifier: could not get verifier builder: error while discovery OIDC configuration: oidc: issuer did not match the issuer returned by provider, expected "https://login.microsoftonline.com/common/v2.0" got "https://login.microsoftonline.com/{tenantid}/v2.0"

With invalid client secret (for example comming from Service Principal, not app registration):

[2024/01/10 10:05:21] [oauthproxy.go:843] Error redeeming code during OAuth2 callback: unexpected status "401": {"error":"invalid_client","error_description":"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app '1ef19e15-23e8-4e30-9139-d774e4a3bced'. Trace ID: 116ce007-a66a-4cd4-b65f-184ee51d9107 Correlation ID: 918d7b7b-2273-4c09-a65e-ebc76e76249c Timestamp: 2024-01-10 10:05:21Z","error_codes":[7000215],"timestamp":"2024-01-10 10:05:21Z","trace_id":"116ce007-a66a-4cd4-b65f-184ee51d9107","correlation_id":"918d7b7b-2273-4c09-a65e-ebc76e76249c","error_uri":"https://login.microsoftonline.com/error?code=7000215"}

When application password is set, but id_token has missing email claim an oauth2-proxy tries to read it from access token that isn't JWT (personal microsoft account):

[2024/01/10 10:05:55] [oauthproxy.go:843] Error redeeming code during OAuth2 callback: unable to get email and/or groups claims from token: unable to get claims from token: could not initialise claim extractor: failed to parse ID Token: oidc: malformed jwt, expected 3 parts got 1

When having email claim is available in id_token, but Graph fails as we can't read groups:

[2024/01/11 11:09:38] [oauthproxy.go:850] Error creating session during OAuth2 callback: unable to get groups from Microsoft Graph: unable to unmarshal Microsoft Graph response: unexpected status "404": {"error":{"code":"UnknownError","message":"","innerError":{"date":"2024-01-11T11:09:38","request-id":"e8c83830-fb9c-4cb4-b5c9-26f9b84acac2","client-request-id":"e8c83830-fb9c-4cb4-b5c9-26f9b84acac2"}}}

Summary

Here are the architectural questions to answer:

  • Do we create a separate provider, deprecate V1 endpoint in the existing provider, or keep the conditioning in place?
  • Do we drop the failover of e-mail claim to Access Token?
  • Do we drop reading email from Graph API and rely on the claim only?
  • Do we accept multi-tenant apps have to be configured with --insecure-oidc-skip-issuer-verification?
  • Do we want to introduce validating tenant ID for multi-tenant apps?
  • Do want to support personal microsoft accounts for multi-tenant apps and thus allow to disable reading groups from both claim & access token?

Please drop any other input, will try to follow.

@hskiba
Copy link

hskiba commented Jan 14, 2024

In #1972 I removed the v1 compatibility due to the complexity you mentioned. Maybe I'm missing something, but isn't it a simple switch in configuration to use the v2 endpoints? I don't see a reason to support the v1 endpoints, particularly if we instruct to supply an OIDC issuer URL as the current doc does.

Copy link
Contributor

This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.

@github-actions github-actions bot added the Stale label Mar 15, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Mar 24, 2024
@tuunit tuunit reopened this Mar 25, 2024
@github-actions github-actions bot removed the Stale label Mar 27, 2024
Copy link
Contributor

This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.

@github-actions github-actions bot added the Stale label May 26, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 5, 2024
@tuunit tuunit reopened this Oct 9, 2024
@jjlakis
Copy link
Contributor Author

jjlakis commented Jan 3, 2025

Closing this as new Entra provider has been merged.

@jjlakis jjlakis closed this as completed Jan 3, 2025
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

3 participants