mirror of https://github.com/coder/coder.git
Entra External Auth for ADO (#12201)
This commit is contained in:
parent
4439a920e4
commit
320c2eac6f
|
@ -499,6 +499,9 @@ func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAut
|
||||||
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
|
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
|
||||||
oauthConfig = &jwtConfig{oc}
|
oauthConfig = &jwtConfig{oc}
|
||||||
}
|
}
|
||||||
|
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevopsEntra) {
|
||||||
|
oauthConfig = &entraV1Oauth{oc}
|
||||||
|
}
|
||||||
if entry.Type == string(codersdk.EnhancedExternalAuthProviderJFrog) {
|
if entry.Type == string(codersdk.EnhancedExternalAuthProviderJFrog) {
|
||||||
oauthConfig = &exchangeWithClientSecret{oc}
|
oauthConfig = &exchangeWithClientSecret{oc}
|
||||||
}
|
}
|
||||||
|
@ -569,6 +572,9 @@ func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
|
||||||
case codersdk.EnhancedExternalAuthProviderGitea:
|
case codersdk.EnhancedExternalAuthProviderGitea:
|
||||||
copyDefaultSettings(config, giteaDefaults(config))
|
copyDefaultSettings(config, giteaDefaults(config))
|
||||||
return
|
return
|
||||||
|
case codersdk.EnhancedExternalAuthProviderAzureDevopsEntra:
|
||||||
|
copyDefaultSettings(config, azureDevopsEntraDefaults(config))
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
// No defaults for this type. We still want to run this apply with
|
// No defaults for this type. We still want to run this apply with
|
||||||
// an empty set of defaults.
|
// an empty set of defaults.
|
||||||
|
@ -730,6 +736,41 @@ func giteaDefaults(config *codersdk.ExternalAuthConfig) codersdk.ExternalAuthCon
|
||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func azureDevopsEntraDefaults(config *codersdk.ExternalAuthConfig) codersdk.ExternalAuthConfig {
|
||||||
|
defaults := codersdk.ExternalAuthConfig{
|
||||||
|
DisplayName: "Azure DevOps (Entra)",
|
||||||
|
DisplayIcon: "/icon/azure-devops.svg",
|
||||||
|
Regex: `^(https?://)?dev\.azure\.com(/.*)?$`,
|
||||||
|
}
|
||||||
|
// The tenant ID is required for urls and is in the auth url.
|
||||||
|
if config.AuthURL == "" {
|
||||||
|
// No auth url, means we cannot guess the urls.
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := url.Parse(config.AuthURL)
|
||||||
|
if err != nil {
|
||||||
|
// We need a valid URL to continue with.
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only extract the tenant ID if the path is what we expect.
|
||||||
|
// The path should be /{tenantId}/oauth2/authorize.
|
||||||
|
parts := strings.Split(auth.Path, "/")
|
||||||
|
if len(parts) < 4 && parts[2] != "oauth2" || parts[3] != "authorize" {
|
||||||
|
// Not sure what this path is, abort.
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
tenantID := parts[1]
|
||||||
|
|
||||||
|
tokenURL := auth.ResolveReference(&url.URL{Path: fmt.Sprintf("/%s/oauth2/token", tenantID)})
|
||||||
|
defaults.TokenURL = tokenURL.String()
|
||||||
|
|
||||||
|
// TODO: Discover a validate url for Azure DevOps.
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
var staticDefaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
|
var staticDefaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
|
||||||
codersdk.EnhancedExternalAuthProviderAzureDevops: {
|
codersdk.EnhancedExternalAuthProviderAzureDevops: {
|
||||||
AuthURL: "https://app.vssps.visualstudio.com/oauth2/authorize",
|
AuthURL: "https://app.vssps.visualstudio.com/oauth2/authorize",
|
||||||
|
@ -811,6 +852,26 @@ func (c *jwtConfig) Exchange(ctx context.Context, code string, opts ...oauth2.Au
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When authenticating via Entra ID ADO only supports v1 tokens that requires the 'resource' rather than scopes
|
||||||
|
// When ADO gets support for V2 Entra ID tokens this struct and functions can be removed
|
||||||
|
type entraV1Oauth struct {
|
||||||
|
*oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
const azureDevOpsAppID = "499b84ac-1321-427f-aa17-267ca6975798"
|
||||||
|
|
||||||
|
func (c *entraV1Oauth) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
||||||
|
return c.Config.AuthCodeURL(state, append(opts, oauth2.SetAuthURLParam("resource", azureDevOpsAppID))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *entraV1Oauth) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
|
||||||
|
return c.Config.Exchange(ctx, code,
|
||||||
|
append(opts,
|
||||||
|
oauth2.SetAuthURLParam("resource", azureDevOpsAppID),
|
||||||
|
)...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// exchangeWithClientSecret wraps an OAuth config and adds the client secret
|
// exchangeWithClientSecret wraps an OAuth config and adds the client secret
|
||||||
// to the Exchange request as a Bearer header. This is used by JFrog Artifactory.
|
// to the Exchange request as a Bearer header. This is used by JFrog Artifactory.
|
||||||
type exchangeWithClientSecret struct {
|
type exchangeWithClientSecret struct {
|
||||||
|
|
|
@ -25,6 +25,7 @@ func (e EnhancedExternalAuthProvider) Git() bool {
|
||||||
EnhancedExternalAuthProviderBitBucketCloud,
|
EnhancedExternalAuthProviderBitBucketCloud,
|
||||||
EnhancedExternalAuthProviderBitBucketServer,
|
EnhancedExternalAuthProviderBitBucketServer,
|
||||||
EnhancedExternalAuthProviderAzureDevops,
|
EnhancedExternalAuthProviderAzureDevops,
|
||||||
|
EnhancedExternalAuthProviderAzureDevopsEntra,
|
||||||
EnhancedExternalAuthProviderGitea:
|
EnhancedExternalAuthProviderGitea:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -34,8 +35,10 @@ func (e EnhancedExternalAuthProvider) Git() bool {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnhancedExternalAuthProviderAzureDevops EnhancedExternalAuthProvider = "azure-devops"
|
EnhancedExternalAuthProviderAzureDevops EnhancedExternalAuthProvider = "azure-devops"
|
||||||
EnhancedExternalAuthProviderGitHub EnhancedExternalAuthProvider = "github"
|
// Authenticate to ADO using an app registration in Entra ID
|
||||||
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
|
EnhancedExternalAuthProviderAzureDevopsEntra EnhancedExternalAuthProvider = "azure-devops-entra"
|
||||||
|
EnhancedExternalAuthProviderGitHub EnhancedExternalAuthProvider = "github"
|
||||||
|
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
|
||||||
// EnhancedExternalAuthProviderBitBucketCloud is the Bitbucket Cloud provider.
|
// EnhancedExternalAuthProviderBitBucketCloud is the Bitbucket Cloud provider.
|
||||||
// Not to be confused with the self-hosted 'EnhancedExternalAuthProviderBitBucketServer'
|
// Not to be confused with the self-hosted 'EnhancedExternalAuthProviderBitBucketServer'
|
||||||
EnhancedExternalAuthProviderBitBucketCloud EnhancedExternalAuthProvider = "bitbucket-cloud"
|
EnhancedExternalAuthProviderBitBucketCloud EnhancedExternalAuthProvider = "bitbucket-cloud"
|
||||||
|
|
|
@ -23,6 +23,7 @@ application. The following providers are supported:
|
||||||
- [GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html)
|
- [GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html)
|
||||||
- [BitBucket](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/)
|
- [BitBucket](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/)
|
||||||
- [Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops)
|
- [Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops)
|
||||||
|
- [Azure DevOps (via Entra ID)](https://learn.microsoft.com/en-us/entra/architecture/auth-oauth2)
|
||||||
|
|
||||||
Example callback URL:
|
Example callback URL:
|
||||||
`https://coder.example.com/external-auth/primary-github/callback`. Use an
|
`https://coder.example.com/external-auth/primary-github/callback`. Use an
|
||||||
|
@ -108,6 +109,20 @@ CODER_EXTERNAL_AUTH_0_AUTH_URL="https://app.vssps.visualstudio.com/oauth2/author
|
||||||
CODER_EXTERNAL_AUTH_0_TOKEN_URL="https://app.vssps.visualstudio.com/oauth2/token"
|
CODER_EXTERNAL_AUTH_0_TOKEN_URL="https://app.vssps.visualstudio.com/oauth2/token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Azure DevOps (via Entra ID)
|
||||||
|
|
||||||
|
Azure DevOps (via Entra ID) requires the following environment variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
CODER_EXTERNAL_AUTH_0_ID="primary-azure-devops"
|
||||||
|
CODER_EXTERNAL_AUTH_0_TYPE=azure-devops-entra
|
||||||
|
CODER_EXTERNAL_AUTH_0_CLIENT_ID=xxxxxx
|
||||||
|
CODER_EXTERNAL_AUTH_0_CLIENT_SECRET=xxxxxxx
|
||||||
|
CODER_EXTERNAL_AUTH_0_AUTH_URL="https://login.microsoftonline.com/<TENANT ID>/oauth2/authorize"
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: Your app registration in Entra ID requires the `vso.code_write` scope
|
||||||
|
|
||||||
### GitLab self-managed
|
### GitLab self-managed
|
||||||
|
|
||||||
GitLab self-managed requires the following environment variables:
|
GitLab self-managed requires the following environment variables:
|
||||||
|
|
|
@ -2003,6 +2003,7 @@ export const DisplayApps: DisplayApp[] = [
|
||||||
// From codersdk/externalauth.go
|
// From codersdk/externalauth.go
|
||||||
export type EnhancedExternalAuthProvider =
|
export type EnhancedExternalAuthProvider =
|
||||||
| "azure-devops"
|
| "azure-devops"
|
||||||
|
| "azure-devops-entra"
|
||||||
| "bitbucket-cloud"
|
| "bitbucket-cloud"
|
||||||
| "bitbucket-server"
|
| "bitbucket-server"
|
||||||
| "gitea"
|
| "gitea"
|
||||||
|
@ -2012,6 +2013,7 @@ export type EnhancedExternalAuthProvider =
|
||||||
| "slack";
|
| "slack";
|
||||||
export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [
|
export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [
|
||||||
"azure-devops",
|
"azure-devops",
|
||||||
|
"azure-devops-entra",
|
||||||
"bitbucket-cloud",
|
"bitbucket-cloud",
|
||||||
"bitbucket-server",
|
"bitbucket-server",
|
||||||
"gitea",
|
"gitea",
|
||||||
|
|
Loading…
Reference in New Issue