Skip to content

Commit

Permalink
Add Support of Microsoft Entra
Browse files Browse the repository at this point in the history
  • Loading branch information
spjmurray committed Mar 15, 2024
1 parent 88e5bd7 commit 613e51c
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ spec:
will result in undefined behaviour.
enum:
- google
- microsoft
type: string
required:
- clientID
- clientSecret
- displayName
- issuer
- type
Expand Down
5 changes: 3 additions & 2 deletions pkg/apis/unikorn/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import (

// IdentityProviderType defines the type of identity provider, and in turn
// that defines the required configuration and API interfaces.
// +kubebuilder:validation:Enum=google
// +kubebuilder:validation:Enum=google;microsoft
type IdentityProviderType string

const (
GoogleIdentity IdentityProviderType = "google"
MicrosoftEntra IdentityProviderType = "microsoft"
)

// OAuth2ClientList is a typed list of frontend clients.
Expand Down Expand Up @@ -107,7 +108,7 @@ type OAuth2ProviderSpec struct {
// ClientID is the assigned client identifier.
ClientID string `json:"clientID"`
// ClientSecret is created by the IdP for token exchange.
ClientSecret string `json:"clientSecret"`
ClientSecret *string `json:"clientSecret,omitempty"`
}

// OAuth2ProviderStatus defines the status of the server.
Expand Down
7 changes: 6 additions & 1 deletion pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 18 additions & 4 deletions pkg/oauth2/federated.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,15 @@ func (a *Authenticator) lookupOrganization(_ http.ResponseWriter, r *http.Reques
return nil, fmt.Errorf("unsupported domain")
}

// newOIDCProvider abstracts away any hacks for specific providers.
func newOIDCProvider(ctx context.Context, p *unikornv1.OAuth2Provider) (*oidc.Provider, error) {
if p.Spec.Type == unikornv1.MicrosoftEntra {
ctx = oidc.InsecureIssuerURLContext(ctx, "https://login.microsoftonline.com/{tenantid}/v2.0")
}

return oidc.NewProvider(ctx, p.Spec.Issuer)
}

// providerAuthenticationRequest takes a client provided email address and routes it
// to the correct identity provider, if we can.
func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r *http.Request, email string, query url.Values) {
Expand All @@ -418,7 +427,7 @@ func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r *

driver := providers.New(providerResource.Spec.Type, organization)

provider, err := oidc.NewProvider(r.Context(), providerResource.Spec.Issuer)
provider, err := newOIDCProvider(r.Context(), &providerResource)
if err != nil {
log.Error(err, "failed to do OIDC discovery")
return
Expand Down Expand Up @@ -520,6 +529,8 @@ func (a *Authenticator) Login(w http.ResponseWriter, r *http.Request) {
func (a *Authenticator) oidcExtractIDToken(ctx context.Context, provider *oidc.Provider, providerResource *unikornv1.OAuth2Provider, token string) (*oidc.IDToken, error) {
config := &oidc.Config{
ClientID: providerResource.Spec.ClientID,
// TODO: this is a Entra-ism
SkipIssuerCheck: true,
}

idTokenVerifier := provider.Verifier(config)
Expand Down Expand Up @@ -575,7 +586,7 @@ func (a *Authenticator) OIDCCallback(w http.ResponseWriter, r *http.Request) {
return
}

provider, err := oidc.NewProvider(r.Context(), providerResource.Spec.Issuer)
provider, err := newOIDCProvider(r.Context(), &providerResource)
if err != nil {
log.Error(err, "failed to do OIDC discovery")
return
Expand All @@ -587,10 +598,13 @@ func (a *Authenticator) OIDCCallback(w http.ResponseWriter, r *http.Request) {
// the extracted code verifier.
authURLParams := []oauth2.AuthCodeOption{
oauth2.SetAuthURLParam("client_id", state.ClientID),
oauth2.SetAuthURLParam("client_secret", providerResource.Spec.ClientSecret),
oauth2.SetAuthURLParam("code_verifier", state.CodeVerfier),
}

if providerResource.Spec.ClientSecret != nil {
authURLParams = append(authURLParams, oauth2.SetAuthURLParam("client_secret", *providerResource.Spec.ClientSecret))
}

tokens, err := a.oidcConfig(r, &providerResource, endpoint, nil).Exchange(r.Context(), query.Get("code"), authURLParams...)
if err != nil {
authorizationError(w, r, state.ClientRedirectURI, ErrorServerError, "oidc code exchange failed: "+err.Error())
Expand Down Expand Up @@ -626,7 +640,7 @@ func (a *Authenticator) OIDCCallback(w http.ResponseWriter, r *http.Request) {

driver := providers.New(providerResource.Spec.Type, &organization)

if _, err := driver.Groups(r.Context(), tokens.AccessToken); err != nil {
if _, err := driver.Groups(r.Context(), idToken, tokens.AccessToken); err != nil {
authorizationError(w, r, state.ClientRedirectURI, ErrorServerError, "failed to lookup user groups: "+err.Error())
return
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/oauth2/providers/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ package providers
import (
unikornv1 "github.com/unikorn-cloud/identity/pkg/apis/unikorn/v1alpha1"
"github.com/unikorn-cloud/identity/pkg/oauth2/providers/google"
"github.com/unikorn-cloud/identity/pkg/oauth2/providers/microsoft"
)

func New(providerType unikornv1.IdentityProviderType, organization *unikornv1.Organization) Provider {
//nolint:gocritic
switch providerType {
case unikornv1.GoogleIdentity:
return google.New(organization)
case unikornv1.MicrosoftEntra:
return microsoft.New()
}

return newNullProvider()
Expand Down
4 changes: 3 additions & 1 deletion pkg/oauth2/providers/google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"net/http"
"net/url"

"github.com/coreos/go-oidc/v3/oidc"

unikornv1 "github.com/unikorn-cloud/identity/pkg/apis/unikorn/v1alpha1"
)

Expand Down Expand Up @@ -57,7 +59,7 @@ type Groups struct {
Groups []Group `json:"groups"`
}

func (p *Provider) Groups(ctx context.Context, accessToken string) ([]string, error) {
func (p *Provider) Groups(ctx context.Context, idToken *oidc.IDToken, accessToken string) ([]string, error) {
if p.organization.Spec.ProviderOptions == nil || p.organization.Spec.ProviderOptions.Google == nil {
return nil, nil
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/oauth2/providers/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package providers

import (
"context"

"github.com/coreos/go-oidc/v3/oidc"
)

type Provider interface {
Expand All @@ -26,5 +28,5 @@ type Provider interface {
Scopes() []string

// Groups returns a list of groups the user belongs to.
Groups(ctx context.Context, accessToken string) ([]string, error)
Groups(ctx context.Context, idToken *oidc.IDToken, accessToken string) ([]string, error)
}
46 changes: 46 additions & 0 deletions pkg/oauth2/providers/microsoft/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright 2024 the Unikorn Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package microsoft

import (
"context"

"github.com/coreos/go-oidc/v3/oidc"
)

type Provider struct {
}

func New() *Provider {
return &Provider{}
}

func (*Provider) Scopes() []string {
return []string{}
}

func (p *Provider) Groups(ctx context.Context, idToken *oidc.IDToken, accessToken string) ([]string, error) {
var claims struct {
Groups []string `json:"groups"`
}

if err := idToken.Claims(&claims); err != nil {
return nil, err
}

return claims.Groups, nil
}
4 changes: 3 additions & 1 deletion pkg/oauth2/providers/null.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package providers

import (
"context"

"github.com/coreos/go-oidc/v3/oidc"
)

// nullProvider does nothing.
Expand All @@ -31,6 +33,6 @@ func (*nullProvider) Scopes() []string {
return nil
}

func (*nullProvider) Groups(ctx context.Context, accessToken string) ([]string, error) {
func (*nullProvider) Groups(ctx context.Context, idToken *oidc.IDToken, accessToken string) ([]string, error) {
return nil, nil
}

0 comments on commit 613e51c

Please sign in to comment.