Skip to content

Commit

Permalink
#41 OIDC initial support
Browse files Browse the repository at this point in the history
  • Loading branch information
bnfinet committed Apr 9, 2019
1 parent 718eab7 commit 7a715e7
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 147 deletions.
150 changes: 3 additions & 147 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package cfg

import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"strconv"
"strings"

"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"

"github.com/spf13/viper"
securerandom "github.com/theckman/go-securerandom"
Expand Down Expand Up @@ -110,7 +106,7 @@ var (
GenOAuth *oauthConfig

// OAuthClient is the configured client which will call the provider
// this actually carries the oauth2 client ala oauthclient.Client(oauth2.NoContext, providerToken)
// this actually carries the oauth2 http client ala oauthclient.Client(oauth2.NoContext, providerToken)
OAuthClient *oauth2.Config
// OAuthopts authentication options
OAuthopts oauth2.AuthCodeOption
Expand Down Expand Up @@ -270,6 +266,8 @@ please update your config file to change '%s:' to '%s:' as per %s
}
}

configureOAuth()

// don't log the secret!
// log.Debugf("secret: %s", string(Cfg.JWT.Secret))
}
Expand Down Expand Up @@ -466,148 +464,6 @@ func SetDefaults() {
Cfg.WebApp = false
}

// OAuth defaults and client configuration
err := UnmarshalKey("oauth", &GenOAuth)
if err != nil {
log.Errorf("error configuring oauth %s", err.Error())
}
if err == nil {
if GenOAuth.Provider == Providers.Google {
setDefaultsGoogle()
// setDefaultsGoogle also configures the OAuthClient
} else if GenOAuth.Provider == Providers.GitHub {
setDefaultsGitHub()
configureOAuthClient()
} else if GenOAuth.Provider == Providers.ADFS {
setDefaultsADFS()
configureOAuthClient()
} else if GenOAuth.Provider == Providers.OIDC && GenOAuth.ProviderURL != "" {
if err := setOAuthDefaultsOIDCDiscovery(); err != nil {
log.Errorf("error setting endpoints from OIDC Discovery: %s", err.Error())
}
} else {
configureOAuthClient()
}
}
}

type oidcDiscoveryJSON struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
UserInfoURL string `json:"userinfo_endpoint"`
ScopesSupported []string `json:"scopes_supported"`
}

// retrieve JSON from provider's /.well-known/openid-configuration and configure endpoints
func setOAuthDefaultsOIDCDiscovery() error {
// func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnownURL := strings.TrimSuffix(GenOAuth.ProviderURL, "/") + "/.well-known/openid-configuration"
log.Infof("configuring oauth enpoints via OIDC Discovery from %s", wellKnownURL)
req, err := http.NewRequest("GET", wellKnownURL, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("oidc: unable to read response body: %v", err)
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%s: %s", resp.Status, body)
}

var oidcD oidcDiscoveryJSON
err = json.Unmarshal(body, &oidcD)
if err != nil {
return fmt.Errorf("oidc: couldn't read json from provider: %v", err)
}

if GenOAuth.ProviderURL != oidcD.Issuer {
return fmt.Errorf("oidc: issuers did not match, wanted: %q got: %q", GenOAuth.ProviderURL, oidcD.Issuer)
}

// SUCCESS!!
// set the retrieved items

GenOAuth.AuthURL = oidcD.AuthURL
GenOAuth.TokenURL = oidcD.TokenURL
GenOAuth.UserInfoURL = oidcD.UserInfoURL

if len(oidcD.ScopesSupported) > 0 {
GenOAuth.Scopes = oidcD.ScopesSupported
} else {
GenOAuth.Scopes = []string{"openid"}
}

log.Debugf("set oauth.AuthURL %s", GenOAuth.AuthURL)
log.Debugf("set oauth.TokenURL %s", GenOAuth.TokenURL)
log.Debugf("set oauth.UserInfoURL %s", GenOAuth.UserInfoURL)
log.Debugf("set oauth.Scopes %s", GenOAuth.Scopes)

return nil
}

func setDefaultsGoogle() {
log.Info("configuring Google OAuth")
GenOAuth.UserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
OAuthClient = &oauth2.Config{
ClientID: GenOAuth.ClientID,
ClientSecret: GenOAuth.ClientSecret,
Scopes: []string{
// You have to select a scope from
// https://developers.google.com/identity/protocols/googlescopes#google_sign-in
"https://www.googleapis.com/auth/userinfo.email",
},
Endpoint: google.Endpoint,
}
if GenOAuth.PreferredDomain != "" {
log.Infof("setting Google OAuth preferred login domain param 'hd' to %s", GenOAuth.PreferredDomain)
OAuthopts = oauth2.SetAuthURLParam("hd", GenOAuth.PreferredDomain)
}
}

func setDefaultsADFS() {
log.Info("configuring ADFS OAuth")
OAuthopts = oauth2.SetAuthURLParam("resource", GenOAuth.RedirectURL) // Needed or all claims won't be included
}

func setDefaultsGitHub() {
// log.Info("configuring GitHub OAuth")
if GenOAuth.AuthURL == "" {
GenOAuth.AuthURL = github.Endpoint.AuthURL
}
if GenOAuth.TokenURL == "" {
GenOAuth.TokenURL = github.Endpoint.TokenURL
}
if GenOAuth.UserInfoURL == "" {
GenOAuth.UserInfoURL = "https://api.github.com/user?access_token="
}
if len(GenOAuth.Scopes) == 0 {
// https://github.com/vouch/vouch-proxy/issues/63
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
GenOAuth.Scopes = []string{"read:user"}
}
}

func configureOAuthClient() {
log.Infof("configuring %s OAuth with Endpoint %s", GenOAuth.Provider, GenOAuth.AuthURL)
OAuthClient = &oauth2.Config{
ClientID: GenOAuth.ClientID,
ClientSecret: GenOAuth.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: GenOAuth.AuthURL,
TokenURL: GenOAuth.TokenURL,
},
RedirectURL: GenOAuth.RedirectURL,
Scopes: GenOAuth.Scopes,
}
}

func getOrGenerateJWTSecret() string {
Expand Down
156 changes: 156 additions & 0 deletions pkg/cfg/cfg_oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package cfg

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"

"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"
)

type oidcDiscoveryJSON struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
UserInfoURL string `json:"userinfo_endpoint"`
ScopesSupported []string `json:"scopes_supported"`
}

func configureOAuth() {
// OAuth defaults and client configuration
if err := UnmarshalKey("oauth", &GenOAuth); err != nil {
log.Fatalf("error configuring oauth %s", err.Error())
}
if GenOAuth.Provider == Providers.Google {
setDefaultsGoogle()
// setDefaultsGoogle also configures the OAuthClient
} else if GenOAuth.Provider == Providers.GitHub {
setDefaultsGitHub()
configureOAuthClient()
} else if GenOAuth.Provider == Providers.ADFS {
setDefaultsADFS()
configureOAuthClient()
} else if GenOAuth.Provider == Providers.OIDC && GenOAuth.ProviderURL != "" {
if err := setOAuthDefaultsOIDCDiscovery(); err != nil {
log.Errorf("error setting endpoints from OIDC Discovery: %s", err.Error())
}
} else {
configureOAuthClient()
}

}

// retrieve JSON from provider's /.well-known/openid-configuration and configure endpoints
func setOAuthDefaultsOIDCDiscovery() error {
// func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnownURL := strings.TrimSuffix(GenOAuth.ProviderURL, "/") + "/.well-known/openid-configuration"
log.Infof("configuring oauth enpoints via OIDC Discovery from %s", wellKnownURL)
req, err := http.NewRequest("GET", wellKnownURL, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("oidc: unable to read response body: %v", err)
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%s: %s", resp.Status, body)
}

var oidcD oidcDiscoveryJSON
err = json.Unmarshal(body, &oidcD)
if err != nil {
return fmt.Errorf("oidc: couldn't read json from provider: %v", err)
}

if GenOAuth.ProviderURL != oidcD.Issuer {
return fmt.Errorf("oidc: issuers did not match, wanted: %q got: %q", GenOAuth.ProviderURL, oidcD.Issuer)
}

// SUCCESS!!
// set the retrieved items

GenOAuth.AuthURL = oidcD.AuthURL
GenOAuth.TokenURL = oidcD.TokenURL
GenOAuth.UserInfoURL = oidcD.UserInfoURL

if len(oidcD.ScopesSupported) > 0 {
GenOAuth.Scopes = oidcD.ScopesSupported
} else {
GenOAuth.Scopes = []string{"openid"}
}

log.Debugf("set oauth.AuthURL %s", GenOAuth.AuthURL)
log.Debugf("set oauth.TokenURL %s", GenOAuth.TokenURL)
log.Debugf("set oauth.UserInfoURL %s", GenOAuth.UserInfoURL)
log.Debugf("set oauth.Scopes %s", GenOAuth.Scopes)

return nil
}

func setDefaultsGoogle() {
log.Info("configuring Google OAuth")
GenOAuth.UserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
OAuthClient = &oauth2.Config{
ClientID: GenOAuth.ClientID,
ClientSecret: GenOAuth.ClientSecret,
Scopes: []string{
// You have to select a scope from
// https://developers.google.com/identity/protocols/googlescopes#google_sign-in
"https://www.googleapis.com/auth/userinfo.email",
},
Endpoint: google.Endpoint,
}
if GenOAuth.PreferredDomain != "" {
log.Infof("setting Google OAuth preferred login domain param 'hd' to %s", GenOAuth.PreferredDomain)
OAuthopts = oauth2.SetAuthURLParam("hd", GenOAuth.PreferredDomain)
}
}

func setDefaultsADFS() {
log.Info("configuring ADFS OAuth")
OAuthopts = oauth2.SetAuthURLParam("resource", GenOAuth.RedirectURL) // Needed or all claims won't be included
}

func setDefaultsGitHub() {
// log.Info("configuring GitHub OAuth")
if GenOAuth.AuthURL == "" {
GenOAuth.AuthURL = github.Endpoint.AuthURL
}
if GenOAuth.TokenURL == "" {
GenOAuth.TokenURL = github.Endpoint.TokenURL
}
if GenOAuth.UserInfoURL == "" {
GenOAuth.UserInfoURL = "https://api.github.com/user?access_token="
}
if len(GenOAuth.Scopes) == 0 {
// https://github.com/vouch/vouch-proxy/issues/63
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
GenOAuth.Scopes = []string{"read:user"}
}
}

func configureOAuthClient() {
log.Infof("configuring %s OAuth with Endpoint %s", GenOAuth.Provider, GenOAuth.AuthURL)
OAuthClient = &oauth2.Config{
ClientID: GenOAuth.ClientID,
ClientSecret: GenOAuth.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: GenOAuth.AuthURL,
TokenURL: GenOAuth.TokenURL,
},
RedirectURL: GenOAuth.RedirectURL,
Scopes: GenOAuth.Scopes,
}
}

4 comments on commit 7a715e7

@jessecooper
Copy link

Choose a reason for hiding this comment

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

Is there a PR for this to go into master?

@bnfinet
Copy link
Member Author

@bnfinet bnfinet commented on 7a715e7 Sep 9, 2019

Choose a reason for hiding this comment

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

There is not.

I'd prefer to use https://github.com/coreos/go-oidc if possible, including verification of the provider token.

There's some glue code needed and management of some other settings including GenOAuth.UserInfoURL but it should be fairly straight forward.

Would you be interested in working on a PR for that?

@jessecooper
Copy link

Choose a reason for hiding this comment

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

I can give it a fork, review the code and see what I might be able to do. Go is not my first language but I dabbled a bit quite a few years ago. Whats the best way to chat with you about Vouch and the direction of the project?

@bnfinet
Copy link
Member Author

Choose a reason for hiding this comment

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

@jessecooper that would be pretty sweet, would love the help.

The best way to chat would be in the freenode IRC channel #vouch

I'm happy to set a time with you, with a preference for PDT(-07:00) working hours.

For consistency, lets keep any further conversation here in github within issue #41

Thanks much!

Please sign in to comment.