Skip to content
This repository has been archived by the owner on Oct 9, 2023. It is now read-only.

Commit

Permalink
Add oauth http proxy for external server & Extract email from azure c…
Browse files Browse the repository at this point in the history
…laim (#553)

Signed-off-by: byhsu <byhsu@linkedin.com>
Add oauth http proxy for external server because in on-prem cluster use cases, the client might not have access to the external network
Extract email from azure claim
  • Loading branch information
ByronHsu committed May 16, 2023
1 parent fc3db04 commit a853dac
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 17 deletions.
7 changes: 7 additions & 0 deletions auth/authzserver/claims_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ func verifyClaims(expectedAudience sets.String, claimsRaw map[string]interface{}
}
}

EmailKey := "email"
// In some cases, "user_info" field doesn't exist in the raw claim,
// but we can get email from "email" field
if emailClaim, found := claimsRaw[EmailKey]; found {
email := emailClaim.(string)
userInfo.Email = email
}
// If this is a user-only access token with no scopes defined then add `all` scope by default because it's equivalent
// to having a user's login cookie or an ID Token as means of accessing the service.
if len(clientID) == 0 && scopes.Len() == 0 {
Expand Down
2 changes: 2 additions & 0 deletions auth/authzserver/claims_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ func Test_verifyClaims(t *testing.T) {
"sub": "123",
"client_id": "my-client",
"scp": []interface{}{"all", "offline"},
"email": "byhsu@linkedin.com",
})

assert.NoError(t, err)
assert.Equal(t, sets.NewString("all", "offline"), identityCtx.Scopes())
assert.Equal(t, "my-client", identityCtx.AppID())
assert.Equal(t, "123", identityCtx.UserID())
assert.Equal(t, "https://myserver", identityCtx.Audience())
assert.Equal(t, "byhsu@linkedin.com", identityCtx.UserInfo().Email)
})

t.Run("Multiple audience", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions auth/authzserver/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

// RegisterHandlers registers http endpoints for handling OAuth2 flow (/authorize,
func RegisterHandlers(handler interfaces.HandlerRegisterer, authCtx interfaces.AuthenticationContext) {
// If using flyte self auth server, OAuth2Provider != nil
if authCtx.OAuth2Provider() != nil {
// Set up oauthserver endpoints. You could also use gorilla/mux or any other router.
handler.HandleFunc(authorizeRelativeURL.String(), getAuthEndpoint(authCtx))
Expand Down
12 changes: 11 additions & 1 deletion auth/authzserver/metadata_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,17 @@ func (s OAuth2MetadataProvider) GetOAuth2Metadata(ctx context.Context, r *servic
externalMetadataURL = baseURL.ResolveReference(oauth2MetadataEndpoint)
}

response, err := http.Get(externalMetadataURL.String())
httpClient := &http.Client{}

if len(s.cfg.HTTPProxyURL.String()) > 0 {
// create a transport that uses the proxy
transport := &http.Transport{
Proxy: http.ProxyURL(&s.cfg.HTTPProxyURL.URL),
}
httpClient.Transport = transport
}

response, err := httpClient.Get(externalMetadataURL.String())
if err != nil {
return nil, err
}
Expand Down
22 changes: 18 additions & 4 deletions auth/authzserver/resource_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"golang.org/x/oauth2"
)

// External auth server implementation

// ResourceServer authorizes access requests issued by an external Authorization Server.
type ResourceServer struct {
signatureVerifier oidc.KeySet
Expand Down Expand Up @@ -68,7 +70,9 @@ func unmarshalResp(r *http.Response, body []byte, v interface{}) error {
return fmt.Errorf("expected Content-Type = application/json, got %q: %v", ct, err)
}

func getJwksForIssuer(ctx context.Context, issuerBaseURL url.URL, customMetadataURL url.URL) (keySet oidc.KeySet, err error) {
func getJwksForIssuer(ctx context.Context, issuerBaseURL url.URL, cfg authConfig.ExternalAuthorizationServer) (keySet oidc.KeySet, err error) {
customMetadataURL := cfg.MetadataEndpointURL.URL

issuerBaseURL.Path = strings.TrimSuffix(issuerBaseURL.Path, "/") + "/"
var wellKnown *url.URL
if len(customMetadataURL.String()) > 0 {
Expand All @@ -77,12 +81,22 @@ func getJwksForIssuer(ctx context.Context, issuerBaseURL url.URL, customMetadata
wellKnown = issuerBaseURL.ResolveReference(oauth2MetadataEndpoint)
}

httpClient := &http.Client{}

if len(cfg.HTTPProxyURL.String()) > 0 {
// create a transport that uses the proxy
transport := &http.Transport{
Proxy: http.ProxyURL(&cfg.HTTPProxyURL.URL),
}
httpClient.Transport = transport
}

req, err := http.NewRequest(http.MethodGet, wellKnown.String(), nil)
if err != nil {
return nil, err
}

resp, err := doRequest(ctx, req)
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
Expand All @@ -104,7 +118,7 @@ func getJwksForIssuer(ctx context.Context, issuerBaseURL url.URL, customMetadata
return nil, fmt.Errorf("failed to decode provider discovery object: %v", err)
}

return oidc.NewRemoteKeySet(ctx, p.JwksUri), nil
return oidc.NewRemoteKeySet(oidc.ClientContext(ctx, httpClient), p.JwksUri), nil
}

// NewOAuth2ResourceServer initializes a new OAuth2ResourceServer.
Expand All @@ -114,7 +128,7 @@ func NewOAuth2ResourceServer(ctx context.Context, cfg authConfig.ExternalAuthori
u = fallbackBaseURL
}

verifier, err := getJwksForIssuer(ctx, u.URL, cfg.MetadataEndpointURL.URL)
verifier, err := getJwksForIssuer(ctx, u.URL, cfg)
if err != nil {
return ResourceServer{}, err
}
Expand Down
4 changes: 2 additions & 2 deletions auth/authzserver/resource_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func Test_getJwksForIssuer(t *testing.T) {
type args struct {
ctx context.Context
issuerBaseURL url.URL
customMetaURL url.URL
cfg authConfig.ExternalAuthorizationServer
}
tests := []struct {
name string
Expand All @@ -234,7 +234,7 @@ func Test_getJwksForIssuer(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getJwksForIssuer(tt.args.ctx, tt.args.issuerBaseURL, tt.args.customMetaURL)
got, err := getJwksForIssuer(tt.args.ctx, tt.args.issuerBaseURL, tt.args.cfg)
if (err != nil) != tt.wantErr {
t.Errorf("getJwksForIssuer() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
5 changes: 5 additions & 0 deletions auth/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ type Config struct {
// the `secure` setting.
AuthorizedURIs []config.URL `json:"authorizedUris" pflag:"-,Optional: Defines the set of URIs that clients are allowed to visit the service on. If set, the system will attempt to match the incoming host to the first authorized URIs and use that (including the scheme) when generating metadata endpoints and when validating audience and issuer claims. If not provided, the urls will be deduced based on the request url and the 'secure' setting."`

// HTTPProxyURL allows operators to access external OAuth2 servers using an external HTTP Proxy
HTTPProxyURL config.URL `json:"httpProxyURL" pflag:",OPTIONAL: HTTP Proxy to be used for OAuth requests."`

// UserAuth settings used to authenticate end users in web-browsers.
UserAuth UserAuthConfig `json:"userAuth" pflag:",Defines Auth options for users."`

Expand Down Expand Up @@ -187,6 +190,8 @@ type ExternalAuthorizationServer struct {
BaseURL config.URL `json:"baseUrl" pflag:",This should be the base url of the authorization server that you are trying to hit. With Okta for instance, it will look something like https://company.okta.com/oauth2/abcdef123456789/"`
AllowedAudience []string `json:"allowedAudience" pflag:",Optional: A list of allowed audiences. If not provided, the audience is expected to be the public Uri of the service."`
MetadataEndpointURL config.URL `json:"metadataUrl" pflag:",Optional: If the server doesn't support /.well-known/oauth-authorization-server, you can set a custom metadata url here.'"`
// HTTPProxyURL allows operators to access external OAuth2 servers using an external HTTP Proxy
HTTPProxyURL config.URL `json:"httpProxyURL" pflag:",OPTIONAL: HTTP Proxy to be used for OAuth requests."`
}

// OAuth2Options defines settings for app auth.
Expand Down
2 changes: 2 additions & 0 deletions auth/config/config_flags.go

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

28 changes: 28 additions & 0 deletions auth/config/config_flags_test.go

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

2 changes: 2 additions & 0 deletions auth/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ func GetHTTPRequestCookieToMetadataHandler(authCtx interfaces.AuthenticationCont
return nil
}

// IDtoken is injected into grpc authorization metadata
meta := metadata.MD{
DefaultAuthorizationHeader: []string{fmt.Sprintf("%s %s", IDTokenScheme, idToken)},
}
Expand Down Expand Up @@ -396,6 +397,7 @@ func QueryUserInfo(ctx context.Context, identityContext interfaces.IdentityConte
return QueryUserInfoUsingAccessToken(ctx, request, authCtx, accessToken)
}

// Extract User info from access token for HTTP request
func QueryUserInfoUsingAccessToken(ctx context.Context, originalRequest *http.Request, authCtx interfaces.AuthenticationContext, accessToken string) (
*service.UserInfoResponse, error) {

Expand Down
2 changes: 2 additions & 0 deletions auth/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package auth
import (
"context"

"github.com/flyteorg/flytestdlib/logger"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand All @@ -17,6 +18,7 @@ func BlanketAuthorization(ctx context.Context, req interface{}, _ *grpc.UnarySer
}

if !identityContext.Scopes().Has(ScopeAll) {
logger.Debugf(ctx, "authenticated user doesn't have required scope")
return nil, status.Errorf(codes.Unauthenticated, "authenticated user doesn't have required scope")
}

Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,12 @@ require (
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
golang.org/x/tools v0.4.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
Expand Down
14 changes: 7 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1644,7 +1644,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -1751,8 +1751,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -1998,17 +1998,17 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
Expand Down

0 comments on commit a853dac

Please sign in to comment.