From 770a222fecf25f0c6ff05247784b7e0642259e0f Mon Sep 17 00:00:00 2001 From: Daniel Kerwin Date: Thu, 7 Nov 2019 11:37:42 +0100 Subject: [PATCH 1/6] Update build matrix and use new variables for the built-in credentials --- .travis.yml | 7 +------ Makefile | 4 ++-- go.mod | 2 ++ go.sum | 2 -- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1406c28..6c4efad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,7 @@ language: go env: BUILD_NUMBER=$TRAVIS_BUILD_NUMBER matrix: include: - - go: 1.11.x - env: GO111MODULE=on - script: - - make vet - - make test - - go: 1.12.x + - go: 1.13.x script: - make vet - make test diff --git a/Makefile b/Makefile index c40f003..36bef6a 100644 --- a/Makefile +++ b/Makefile @@ -85,8 +85,8 @@ bin/$(ARCH)/$(BIN): -ldflags "-X $(PKG)/version.VERSION=$(VERSION) \ -X $(PKG)/version.GITHASH=$(GIT_HASH) \ -X $(PKG)/version.DOB=$(DOB) \ - -X $(PKG)/cmd.defaultClientID=$(CLIENT_ID) \ - -X $(PKG)/cmd.defaultClientSecret=$(CLIENT_SECRET)" + -X $(PKG)/cmd.buildTimeClientID=$(CLIENT_ID) \ + -X $(PKG)/cmd.buildTimeClientSecret=$(CLIENT_SECRET)" # Run go vet on repo vet: diff --git a/go.mod b/go.mod index b797524..f17c4d6 100644 --- a/go.mod +++ b/go.mod @@ -20,3 +20,5 @@ require ( k8s.io/klog v0.4.0 // indirect sigs.k8s.io/yaml v1.1.0 // indirect ) + +go 1.13 diff --git a/go.sum b/go.sum index b16dfd1..bbb7689 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/square/go-jose.v2 v2.1.9 h1:YCFbL5T2gbmC2sMG12s1x2PAlTK5TZNte3hjZEIcCAg= -gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.0 h1:nLzhkFyl5bkblqYBoiWJUt5JkWOzmiaBtCxdJAqJd3U= gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= From e4f4d9360506391778ba3d8bc8b667e2cba7c045 Mon Sep 17 00:00:00 2001 From: Daniel Kerwin Date: Thu, 7 Nov 2019 11:47:07 +0100 Subject: [PATCH 2/6] Rework provider setup. Dexter now supports arbitrary oidc providers with very little overhead --- cmd/auth.go | 415 ++++++++++++++++++++++--------------------------- cmd/azure.go | 104 +++++++++++++ cmd/google.go | 65 ++++++++ main.go | 2 +- utils/utils.go | 57 +++++++ 5 files changed, 411 insertions(+), 232 deletions(-) create mode 100644 cmd/azure.go create mode 100644 cmd/google.go diff --git a/cmd/auth.go b/cmd/auth.go index 9614efd..a557911 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -4,38 +4,80 @@ import ( "context" "errors" "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "os/signal" + "os/user" + "path/filepath" + "sync" + "syscall" + "time" + "github.com/coreos/go-oidc" "github.com/ghodss/yaml" "github.com/gini/dexter/utils" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - "golang.org/x/oauth2/microsoft" "gopkg.in/square/go-jose.v2/jwt" - "io/ioutil" k8sRuntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/clientcmd" clientCmdApi "k8s.io/client-go/tools/clientcmd/api" clientCmdLatest "k8s.io/client-go/tools/clientcmd/api/latest" - "net/http" - "net/url" - "os" - "os/signal" - "os/user" - "path/filepath" - "regexp" - "strings" - "sync" - "syscall" - "time" ) -// dexterOIDC: struct to store the required data and provide methods to -// authenticate with Googles OpenID implementation -type dexterOIDC struct { - endpoint string // azure or google - azureTenant string // azure tenant +var ( + // default injected at build time. This is optional + buildTimeClientID string + buildTimeClientSecret string + + // commandline flags + clientID string + clientSecret string + callback string + kubeConfig string + dryRun bool + + // Cobra command + AuthCmd = &cobra.Command{ + Use: "auth", + Short: "Authenticate with OIDC provider", + Long: `Use your Google login to get a JWT (JSON Web Token) and update your +local k8s config accordingly. A refresh token is added and automatically refreshed +by kubectl. Existing token configurations are overwritten. +For details go to: https://blog.gini.net/ + +dexters authentication flow +=========================== + +1. Open a browser window/tab and redirect you to Google (https://accounts.google.com) +2. You login with your Google credentials +3. You will be redirected to dexters builtin webserver and can now close the browser tab +4. dexter extracts the token from the callback and patches your ~/.kube/config + +➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! +`, + } +) + +// helper type to render the k8s config +type CustomClaim struct { + Email string `json:"email"` +} + +// interface that all OIDC providers need to implement +type OIDCProvider interface { + PreflightCheck() error + CreateOauth2Config() error + GenerateAuthUrl() string + StartHTTPServer() error +} + +// DexterOIDC: struct to store the required data and provide methods to +// authenticate with OpenID providers +type DexterOIDC struct { clientID string // clientID commandline flag clientSecret string // clientSecret commandline flag callback string // callback URL commandline flag @@ -50,55 +92,42 @@ type dexterOIDC struct { signalChan chan os.Signal // react on signals from the outside world } -// initialize the struct, parse commandline flags and install a signal handler -func (d *dexterOIDC) initialize() error { - // install signal handler - signal.Notify( - oidcData.signalChan, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT) - - // get active user (to get the homedirectory) - usr, err := user.Current() - - if err != nil { - return errors.New(fmt.Sprintf("failed to determine current user: %s", err)) +// ensure that the required parameters are defined and that the values make sense +func (d DexterOIDC) PreflightCheck() error { + if d.clientID == "" || d.clientSecret == "" { + return errors.New("clientID and clientSecret cannot be empty") } - // construct the path to the users .kube/config file as a default - kubeConfigDefaultPath := filepath.Join(usr.HomeDir, ".kube", "config") - - // setup commandline flags - AuthCmd.PersistentFlags().StringVarP(&d.endpoint, "endpoint", "e", "google", "OIDC-providers: google or azure") - AuthCmd.PersistentFlags().StringVarP(&d.azureTenant, "tenant", "t", "common", "Your azure tenant") - AuthCmd.PersistentFlags().StringVarP(&d.clientID, "client-id", "i", "REDACTED", "Google clientID") - AuthCmd.PersistentFlags().StringVarP(&d.clientSecret, "client-secret", "s", "REDACTED", "Google clientSecret") - AuthCmd.PersistentFlags().StringVarP(&d.callback, "callback", "c", "http://127.0.0.1:64464/callback", "Callback URL. The listen address is dreived from that.") - AuthCmd.PersistentFlags().StringVarP(&d.kubeConfig, "kube-config", "k", kubeConfigDefaultPath, "Overwrite the default location of kube config (~/.kube/config)") - AuthCmd.PersistentFlags().BoolVarP(&d.dryRun, "dry-run", "d", false, "Toggle config overwrite") + return nil +} - // create random string as CSRF protection for the oauth2 flow - d.state = utils.RandomString() +// create Oauth2 configuration +func (d *DexterOIDC) CreateOauth2Config() error { + return d.DefaultOauth2Config() +} - return nil +// create Oauth2 configuration +func (d *DexterOIDC) AuthInfoToOauth2(authInfo *clientCmdApi.AuthInfo) { + d.clientSecret = authInfo.AuthProvider.Config["client-secret"] + d.clientID = authInfo.AuthProvider.Config["client-id"] } -// setup and populate the OAuth2 config -func (d *dexterOIDC) createOauth2Config() error { +func (d *DexterOIDC) DefaultOauth2Config() error { // no commandline client credentials supplied if d.clientID == "REDACTED" && d.clientSecret == "REDACTED" { // no builtin defaults - let's try auto-configuration - if defaultClientID == "" && defaultClientSecret == "" { + if buildTimeClientID == "" && buildTimeClientSecret == "" { log.Info("Autopilot mode - no credentials set") - if err := d.autoConfigureOauth2Config(); err != nil { + if authInfo, err := ExtractAuthInfo(d.kubeConfig); err != nil { return errors.New(fmt.Sprintf("failed to extract oidc configuration from the kube config: %s", err)) + } else { + d.AuthInfoToOauth2(authInfo) } - } else if defaultClientID != "" && defaultClientSecret != "" { + } else if buildTimeClientID != "" && buildTimeClientSecret != "" { // use build-time defaults if no clientId & clientSecret was provided log.Info("Using builtin credentials - no credentials set") - d.clientID = defaultClientID - d.clientSecret = defaultClientSecret + d.clientID = buildTimeClientID + d.clientSecret = buildTimeClientSecret } } @@ -106,110 +135,19 @@ func (d *dexterOIDC) createOauth2Config() error { oidc.ClientContext(context.Background(), d.httpClient) // populate oauth2 config - d.Oauth2Config.ClientID = oidcData.clientID - d.Oauth2Config.ClientSecret = oidcData.clientSecret - d.Oauth2Config.RedirectURL = oidcData.callback - - switch oidcData.endpoint { - case "azure": - d.Oauth2Config.Endpoint = microsoft.AzureADEndpoint(oidcData.azureTenant) - d.Oauth2Config.Scopes = []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "email"} - case "google": - d.Oauth2Config.Endpoint = google.Endpoint - d.Oauth2Config.Scopes = []string{oidc.ScopeOpenID, "profile", "email"} - default: - return errors.New(fmt.Sprintf("unsupported endpoint: %s", oidcData.endpoint)) - } + d.Oauth2Config.ClientID = d.clientID + d.Oauth2Config.ClientSecret = d.clientSecret + d.Oauth2Config.RedirectURL = d.callback return nil } -// populate the Oauth2Config object from the kube config. Return an error when the operation failed -func (d *dexterOIDC) autoConfigureOauth2Config() error { - // initialize the clientConfig and error variables - var clientCfg *clientCmdApi.Config - var err error - - // try to load the credentials from the kubeconfig specified on the commandline - if d.kubeConfig != "" { - clientCfg, err = clientcmd.LoadFromFile(d.kubeConfig) - - if err != nil { - return errors.New(fmt.Sprintf("failed to load kubeconfig from %s: %s", d.kubeConfig, err)) - } - } else { - // try to load credentials from CurrentContext - clientCfg, err = clientcmd.NewDefaultClientConfigLoadingRules().Load() - - if err != nil { - return errors.New(fmt.Sprintf("failed to load kubeconfig from the default locations: %s", err)) - } - } - - // loop through all the contexts until we find the current context - for contextName, context := range clientCfg.Contexts { - // find the context definition that matches the current active context - if contextName == clientCfg.CurrentContext { - // loop through the global authentication definitions - for authName, authInfo := range clientCfg.AuthInfos { - // find the authentication definition that is in use in the current context - if authName == context.AuthInfo { - // ensure it is oidc based - if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" { - // verify that relevant keys exist - for _, key := range []string{"client-id", "client-secret", "idp-issuer-url"} { - if _, ok := authInfo.AuthProvider.Config[key]; !ok { - return errors.New(fmt.Sprintf("%s is missing in kubeconfig: %s", key, err)) - } - } - - // set client credentials and idp url based on the kubeconfig definition - d.clientSecret = authInfo.AuthProvider.Config["client-secret"] - d.clientID = authInfo.AuthProvider.Config["client-id"] - idp := authInfo.AuthProvider.Config["idp-issuer-url"] - - // set endpoint based on a match on the issuer URL - if strings.Contains(idp, "google") { - oidcData.endpoint = "google" - - } else if strings.Contains(idp, "microsoft") { - oidcData.endpoint = "azure" - - - re, err := regexp.Compile(`[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}`) //find uuid, this is tenant - - if err != nil { - // failed to extract the azure tenant, use default "common - oidcData.azureTenant = "common" - return nil - - } - - res := re.FindStringSubmatch(idp) - if len(res) == 1 { - oidcData.azureTenant = res[0] // found tenant - } else { - // failed to find tenant, use common - oidcData.azureTenant = "common" - } - } - - return nil - } - } - } - } - } - - return errors.New("failed to auto-configure OIDC from kubeconfig") -} - -func (d *dexterOIDC) authUrl() string { +func (d DexterOIDC) GenerateAuthUrl() string { return d.Oauth2Config.AuthCodeURL(d.state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent")) } // start HTTP server to receive callbacks. This has to be run in a go routine -func (d *dexterOIDC) startHttpServer() { +func (d DexterOIDC) StartHTTPServer() error { // set HTTP server listen address from callback URL parsedURL, err := url.Parse(d.callback) @@ -221,11 +159,56 @@ func (d *dexterOIDC) startHttpServer() { d.httpServer.Addr = parsedURL.Host http.HandleFunc("/callback", d.callbackHandler) - d.httpServer.ListenAndServe() + + go func(d DexterOIDC) { + if err := d.httpServer.ListenAndServe(); err != nil { + log.Errorf("Failed to start HTTP server: %s", err) + } + }(d) + + for { + select { + // flow was completed or error occured + case <-d.quitChan: + log.Debugf("Shutdown signal received. We're done here") + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + + d.httpServer.Shutdown(ctx) + cancel() + log.Infof("Shutdown completed") + return nil + // OS signal was received + case sig := <-d.signalChan: + close(d.quitChan) + return fmt.Errorf("signal %d (%s) received. Initiating shutdown", sig, sig) + default: + } + } + +} + +// initialize the struct, parse commandline flags and install a signal handler +func (d *DexterOIDC) initialize() { + // install signal handler + signal.Notify( + d.signalChan, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT) + + // create random string as CSRF protection for the oauth2 flow + d.state = utils.RandomString() + + d.clientID = clientID + d.clientSecret = clientSecret + d.callback = callback + d.kubeConfig = kubeConfig + d.dryRun = dryRun } // accept callbacks from your browser -func (d *dexterOIDC) callbackHandler(w http.ResponseWriter, r *http.Request) { +func (d *DexterOIDC) callbackHandler(w http.ResponseWriter, r *http.Request) { log.Info("callback received") // Get code and state from the passed form value @@ -240,7 +223,7 @@ func (d *dexterOIDC) callbackHandler(w http.ResponseWriter, r *http.Request) { } // compare callback state and initial state - if callbackState != oidcData.state { + if callbackState != d.state { log.Error("state mismatch! Someone could be tampering with your connection!") http.Error(w, "state mismatch! Someone could be tampering with your connection!", http.StatusBadRequest) return @@ -250,7 +233,7 @@ func (d *dexterOIDC) callbackHandler(w http.ResponseWriter, r *http.Request) { // create context and exchange authCode for token ctx := oidc.ClientContext(r.Context(), d.httpClient) - token, err := oidcData.Oauth2Config.Exchange(ctx, code) + token, err := d.Oauth2Config.Exchange(ctx, code) if err != nil { log.Errorf("Failed to exchange auth code: %s", err) @@ -268,7 +251,7 @@ func (d *dexterOIDC) callbackHandler(w http.ResponseWriter, r *http.Request) { } // We're done here - oidcData.quitChan <- struct{}{} + d.quitChan <- struct{}{} w.WriteHeader(http.StatusOK) w.Write([]byte("Authentication completed. It's safe to close this window now ;-)")) @@ -276,12 +259,8 @@ func (d *dexterOIDC) callbackHandler(w http.ResponseWriter, r *http.Request) { return } -type CustomClaim struct { - Email string `json:"email"` -} - // write the k8s config -func (d *dexterOIDC) writeK8sConfig(token *oauth2.Token) error { +func (d *DexterOIDC) writeK8sConfig(token *oauth2.Token) error { // acquire lock d.k8sMutex.Lock() defer d.k8sMutex.Unlock() @@ -324,7 +303,7 @@ func (d *dexterOIDC) writeK8sConfig(token *oauth2.Token) error { } // write the rendered config snipped when dry-run is enabled - if oidcData.dryRun { + if d.dryRun { // create a JSON representation json, err := k8sRuntime.Encode(clientCmdLatest.Codec, config) @@ -378,96 +357,70 @@ func (d *dexterOIDC) writeK8sConfig(token *oauth2.Token) error { return nil } -var ( - // default injected at build time. This is optional - defaultClientID string - defaultClientSecret string - - // initialize dexter OIDC config - oidcData = dexterOIDC{ - Oauth2Config: &oauth2.Config{}, - httpClient: &http.Client{Timeout: 2 * time.Second}, - quitChan: make(chan struct{}), - signalChan: make(chan os.Signal, 1), - } - - // Cobra command - AuthCmd = &cobra.Command{ - Use: "auth", - Short: "Authenticate with OIDC provider", - Long: `Use your Google login to get a JWT (JSON Web Token) and update your -local k8s config accordingly. A refresh token is added and automatically refreshed -by kubectl. Existing token configurations are overwritten. -For details go to: https://blog.gini.net/ - -dexters authentication flow -=========================== - -1. Open a browser window/tab and redirect you to Google (https://accounts.google.com) -2. You login with your Google credentials -3. You will be redirected to dexters builtin webserver and can now close the browser tab -4. dexter extracts the token from the callback and patches your ~/.kube/config +// initialize the command +func init() { + kubeConfigDefaultPath := "" -➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! -`, - RunE: authCommand, + // get active user (to get the homedirectory) + if usr, err := user.Current(); err != nil { + log.Errorf("failed to determine current user: %s", err) + } else { + // construct the path to the users .kube/config file as a default + kubeConfigDefaultPath = filepath.Join(usr.HomeDir, ".kube", "config") } -) -// initialize the command -func init() { // add the auth command rootCmd.AddCommand(AuthCmd) - // parse commandline flags - if err := oidcData.initialize(); err != nil { - log.Errorf("Failed to initialize OIDC provider: %s", err) - os.Exit(1) - } + // setup commandline flags + AuthCmd.PersistentFlags().StringVarP(&clientID, "client-id", "i", "REDACTED", "Google clientID") + AuthCmd.PersistentFlags().StringVarP(&clientSecret, "client-secret", "s", "REDACTED", "Google clientSecret") + AuthCmd.PersistentFlags().StringVarP(&callback, "callback", "c", "http://127.0.0.1:64464/callback", "Callback URL. The listen address is dreived from that.") + AuthCmd.PersistentFlags().StringVarP(&kubeConfig, "kube-config", "k", kubeConfigDefaultPath, "Overwrite the default location of kube config (~/.kube/config)") + AuthCmd.PersistentFlags().BoolVarP(&dryRun, "dry-run", "d", false, "Toggle config overwrite") } -// the command to run -func authCommand(cmd *cobra.Command, args []string) error { - if oidcData.clientID == "" || oidcData.clientSecret == "" { - log.Error("clientID and clientSecret cannot be empty!") - return errors.New("clientID and clientSecret cannot be empty!") +// initiate the OIDC flow. This func should be called in each cobra command +func AuthenticateToProvider(provider OIDCProvider) error { + // ensure that the required fields and values are sane + if err := provider.PreflightCheck(); err != nil { + return fmt.Errorf("failed to complete the provider preflight check: %s", err) } - // setup oauth2 object - if err := oidcData.createOauth2Config(); err != nil { - log.Errorf("oauth2 configuration failed: %s", err) - return err + // create the Oauth2 configuration + if err := provider.CreateOauth2Config(); err != nil { + return fmt.Errorf("failed to create the Oauth2 provider configuration: %s", err) } log.Info("Starting auth browser session. Please check your browser instances...") - if err := utils.OpenURL(oidcData.authUrl()); err != nil { - log.Errorf("Failed to open browser session: %s", err) - return err + if err := utils.OpenURL(provider.GenerateAuthUrl()); err != nil { + return fmt.Errorf("failed to open browser session: %s", err) } - log.Infof("Spawning http server to receive callbacks (%s)", oidcData.callback) + log.Info("Spawning http server to receive callbacks") // spawn HTTP server - go oidcData.startHttpServer() + if err := provider.StartHTTPServer(); err != nil { + return fmt.Errorf("HTTP server: %s", err) + } - for { - select { - // flow was completed or error occured - case <-oidcData.quitChan: - log.Debugf("Shutdown signal received. We're done here") + return nil +} - ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) +// extract relevant authentication data from the given kube config +func ExtractAuthInfo(kubeConfig string) (*clientCmdApi.AuthInfo, error) { + var clientCfg *clientCmdApi.Config + var authInfo *clientCmdApi.AuthInfo + var err error - oidcData.httpServer.Shutdown(ctx) - log.Infof("Shutdown completed") - return nil - // OS signal was received - case sig := <-oidcData.signalChan: - log.Infof("Signal %d (%s) received. Initiating shutdown", sig, sig) - close(oidcData.quitChan) - return errors.New("Signal %d (%s) received. Initiating shutdown") - default: - } + if clientCfg, err = utils.ParseKubernetesClientConfig(kubeConfig); err != nil { + return nil, err } + + if authInfo, err = utils.ExtractOIDCAuthProvider(clientCfg); err != nil { + return nil, err + } + + return authInfo, nil } diff --git a/cmd/azure.go b/cmd/azure.go new file mode 100644 index 0000000..828cae0 --- /dev/null +++ b/cmd/azure.go @@ -0,0 +1,104 @@ +package cmd + +import ( + "net/http" + "os" + "regexp" + "strings" + "time" + + "github.com/coreos/go-oidc" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/oauth2" + "golang.org/x/oauth2/microsoft" + clientCmdApi "k8s.io/client-go/tools/clientcmd/api" +) + +type AzureOIDC struct { + DexterOIDC // embed the base provider + tenant string // azure tenant +} + +func (a *AzureOIDC) AuthInfoToOauth2(authInfo *clientCmdApi.AuthInfo) { + // fallback ti tenant common + a.tenant = "common" + + // call parent method to initialize client credentials + a.DexterOIDC.AuthInfoToOauth2(authInfo) + + // extract the issuer url + idp := authInfo.AuthProvider.Config["idp-issuer-url"] + + // set endpoint based on a match on the issuer URL + if strings.Contains(idp, "microsoft") { + //find a uuid, this is the tenant + re, err := regexp.Compile(`[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}`) + + if err != nil { + // failed to extract the azure tenant, use default "common + return + } + + res := re.FindStringSubmatch(idp) + if len(res) == 1 { + a.tenant = res[0] // found tenant + return + } + } else { + log.Info("No Microsoft auth provider configuration found") + } +} + +var ( + // initialize dexter OIDC config + azureProvider = &AzureOIDC{ + DexterOIDC{ + Oauth2Config: &oauth2.Config{}, + httpClient: &http.Client{Timeout: 2 * time.Second}, + quitChan: make(chan struct{}), + signalChan: make(chan os.Signal, 1), + }, + "", + } + + azureCmd = &cobra.Command{ + Use: "azure", + Short: "Authenticate with OIDC provider", + Long: `Use your Microsoft login to get a JWT (JSON Web Token) and update your +local k8s config accordingly. A refresh token is added and automatically refreshed +by kubectl. Existing token configurations are overwritten. +For details go to: https://blog.gini.net/ + +dexters authentication flow +=========================== + +1. Open a browser window/tab and redirect you to Microsoft (https://login.microsoftonline.com/) +2. You login with your Microsoft credentials +3. You will be redirected to dexters builtin webserver and can now close the browser tab +4. dexter extracts the token from the callback and patches your ~/.kube/config + +➜ Unless you have a good reason to do so please use the built-in Microsoft credentials (if they were added at build time)! +`, + Run: AzureCommand, + } +) + +func init() { + // add the azure auth subcommand + AuthCmd.AddCommand(azureCmd) + + // setup commandline flags + azureCmd.PersistentFlags().StringVarP(&azureProvider.tenant, "tenant", "t", "common", "Your azure tenant") +} + +func AzureCommand(cmd *cobra.Command, args []string) { + azureProvider.initialize() + + azureProvider.Oauth2Config.Endpoint = microsoft.AzureADEndpoint(azureProvider.tenant) + azureProvider.Oauth2Config.Scopes = []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "email"} + + if err := AuthenticateToProvider(azureProvider); err != nil { + log.Errorf("Authentication failed: %s", err) + } +} diff --git a/cmd/google.go b/cmd/google.go new file mode 100644 index 0000000..e3c99f5 --- /dev/null +++ b/cmd/google.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "net/http" + "os" + "time" + + "github.com/coreos/go-oidc" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +type GoogleOIDC struct { + DexterOIDC +} + +var ( + googleProvider = &GoogleOIDC{ + DexterOIDC{ + Oauth2Config: &oauth2.Config{}, + httpClient: &http.Client{Timeout: 2 * time.Second}, + quitChan: make(chan struct{}), + signalChan: make(chan os.Signal, 1), + }, + } + + googleCmd = &cobra.Command{ + Use: "google", + Short: "Authenticate with OIDC provider", + Long: `Use your Google login to get a JWT (JSON Web Token) and update your +local k8s config accordingly. A refresh token is added and automatically refreshed +by kubectl. Existing token configurations are overwritten. +For details go to: https://blog.gini.net/ + +dexters authentication flow +=========================== + +1. Open a browser window/tab and redirect you to Google (https://accounts.google.com) +2. You login with your Google credentials +3. You will be redirected to dexters builtin webserver and can now close the browser tab +4. dexter extracts the token from the callback and patches your ~/.kube/config + +➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! +`, + Run: GoogleCommand, + } +) + +func init() { + // add the auth command + AuthCmd.AddCommand(googleCmd) +} + +func GoogleCommand(cmd *cobra.Command, args []string) { + googleProvider.initialize() + + googleProvider.Oauth2Config.Endpoint = google.Endpoint + googleProvider.Oauth2Config.Scopes = []string{oidc.ScopeOpenID, "profile", "email"} + + if err := AuthenticateToProvider(googleProvider); err != nil { + log.Errorf("Authentication failed: %s", err) + } +} diff --git a/main.go b/main.go index d5e44d9..b939acf 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ package main import ( - log "github.com/sirupsen/logrus" "github.com/gini/dexter/cmd" + log "github.com/sirupsen/logrus" ) func main() { diff --git a/utils/utils.go b/utils/utils.go index c8beba2..bb341e2 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,6 +6,9 @@ import ( "math/rand" "os/exec" "runtime" + + "k8s.io/client-go/tools/clientcmd" + clientCmdApi "k8s.io/client-go/tools/clientcmd/api" ) // helper to generate a random string @@ -47,3 +50,57 @@ func OpenURL(url string) error { return nil } + +// parse the k8s config +func ParseKubernetesClientConfig(kubeConfig string) (*clientCmdApi.Config, error) { + // initialize the clientConfig and error variables + var clientCfg *clientCmdApi.Config + var err error + + // try to load the credentials from the kubeconfig specified on the commandline + if kubeConfig != "" { + clientCfg, err = clientcmd.LoadFromFile(kubeConfig) + + if err != nil { + return nil, errors.New(fmt.Sprintf("failed to load kubeconfig from %s: %s", kubeConfig, err)) + } + } else { + // try to load credentials from CurrentContext + clientCfg, err = clientcmd.NewDefaultClientConfigLoadingRules().Load() + + if err != nil { + return nil, errors.New(fmt.Sprintf("failed to load kubeconfig from the default locations: %s", err)) + } + } + + return clientCfg, nil +} + +// find a OIDC provider in the config +func ExtractOIDCAuthProvider(config *clientCmdApi.Config) (*clientCmdApi.AuthInfo, error) { + // loop through all the contexts until we find the current context + for contextName, context := range config.Contexts { + // find the context definition that matches the current active context + if contextName == config.CurrentContext { + // loop through the global authentication definitions + for authName, authInfo := range config.AuthInfos { + // find the authentication definition that is in use in the current context + if authName == context.AuthInfo { + // ensure it is oidc based + if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" { + // verify that relevant keys exist + for _, key := range []string{"client-id", "client-secret", "idp-issuer-url"} { + if _, ok := authInfo.AuthProvider.Config[key]; !ok { + return nil, errors.New(fmt.Sprintf("%s is missing in kubeconfig", key)) + } + } + + return authInfo, nil + } + } + } + } + } + + return nil, errors.New("no valid OIDC auth provider found") +} From aece7de81da39d908eef836099537c59620194fe Mon Sep 17 00:00:00 2001 From: Daniel Kerwin Date: Thu, 7 Nov 2019 14:15:05 +0100 Subject: [PATCH 3/6] Extend README and enable error returns when dexter is used as a lib. Also fixes the auth help output --- README.md | 83 +++++++++++++++++++++++++++++---------------------- cmd/auth.go | 16 ++-------- cmd/azure.go | 12 +++++--- cmd/google.go | 13 ++++---- 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index a50bafd..fd187b5 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,16 @@ `dexter` is a OIDC (OpenId Connect) helper to create a hassle-free Kubernetes login experience powered by Google or Azure as Identity Provider. All you need is a properly configured Google or Azure client ID & secret. +## Supported identity providers + +| Identity Provider | State | +|--------------------|----------| +| Google | complete | +| Microsoft Azure | complete | + ## Authentication Flow -`dexter` will open a new browser window and redirect you to your configured Idp. The only interaction you have is the login at your provider and your k8s config is updated automatically. +`dexter` will open a new browser tag/window and redirect you to your configured Idp. The only interaction you have is the login at your provider and your k8s config is updated automatically. ![dexter flow](/assets/dexter_flow.png?raw=true "dexter flow") @@ -15,25 +22,31 @@ All you need is a properly configured Google or Azure client ID & secret. ![dexter in action](/assets/dexter.gif?raw=true "dexter in action") -## Configuration -### Google credentials +## OIDCProvider Configuration + +Each OpenID Connect provider requires some configuration. This basic +description may not be all you have to do but it worked at the time of +writing. + +### Google - - Open [console.developers.google.com](https://console.developers.google.com) - - Create new credentials - - OAuth Client ID - - Web Application - - Authorized redirect URIs: http://127.0.0.1:64464/callback + - Open [console.developers.google.com](https://console.developers.google.com) + - Create new credentials + - OAuth Client ID + - Web Application + - Authorized redirect URIs: http://127.0.0.1:64464/callback -### Or, configure Azure credentials +### Microsoft Azure - - Open [portal.azure.com](https://portal.azure.com) - - Go to App registrations and create a new app - - Enter reply URI http://127.0.0.1:64464/callback - - Create secret key - - Collect application ID (client ID) + - Open [portal.azure.com](https://portal.azure.com) + - Go to Appregistrations and create a new app + - Enter reply URI http://127.0.0.1:64464/callback + - Create secret key + - Collect application ID (client ID) ### Auto pilot configuration -Dexter also support auto pilot mode. If your existing kubectl context uses one of the supported OIDC-providers, Dexter will try to use the OIDC details from kubeconfig. + +`dexter` also support auto pilot mode. If your existing kubectl context uses one of the supported Identity Providers, `dexter` will try to use extract the OIDC data from kubeconfig. ## Installation @@ -87,40 +100,32 @@ Flags: Use "dexter [command] --help" for more information about a command. ``` -Running `dexter auth` will start the authentication process. +Running `dexter auth [Idp]` will start the authentication process. ``` ❯ ./build/dexter_darwin_amd64 auth --help -Use your Google login to get a JWT (JSON Web Token) and update your -local k8s config accordingly. A refresh token is added and automatically refreshed -by kubectl. Existing token configurations are overwritten. +Use a provider sub-command to authenticate against your identity provider of choice. For details go to: https://blog.gini.net/ -dexters authentication flow -=========================== - -1. Open a browser window/tab and redirect you to Google (https://accounts.google.com) -2. You login with your Google credentials -3. You will be redirected to dexters builtin webserver and can now close the browser tab -4. dexter extracts the token from the callback and patches your ~/.kube/config - -➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! - Usage: - dexter auth [flags] + dexter auth [command] + +Available Commands: + azure Authenticate with the Microsoft Azure Identity Provider + google Authenticate with the Google Identity Provider Flags: -c, --callback string Callback URL. The listen address is dreived from that. (default "http://127.0.0.1:64464/callback") -i, --client-id string Google clientID (default "REDACTED") -s, --client-secret string Google clientSecret (default "REDACTED") -d, --dry-run Toggle config overwrite - -e, --endpoint string OIDC-providers: google or azure (default "google") -h, --help help for auth - -k, --kube-config string Overwrite the default location of kube config (~/.kube/config) (default "/Users/dkerwin/.kube/config") - -t, --tenant string Your azure tenant (default "common") + -k, --kube-config string Overwrite the default location of kube config (default "/Users/dkerwin/.kube/config") Global Flags: -v, --verbose verbose output + +Use "dexter auth [command] --help" for more information about a command. ``` ## Contribution Guidelines @@ -133,9 +138,17 @@ It's awesome that you consider contributing to `dexter` and it's really simple. - update documentation if necessary - open a pull request -## Authors +## Authors & Contributors + +Initial code was written by [Daniel Kerwin](mailto:daniel@gini.net) & David González Ruiz + +Contributors (in alphabetical order): + - @andrewsav-datacom + - @Lujeni + - @pussinboots + - @tillepille -Initial code was written by [Daniel Kerwin](mailto:daniel@gini.net) & [David González Ruiz](mailto:david@gini.net) +Thank you so much! ## Acknowledgements diff --git a/cmd/auth.go b/cmd/auth.go index a557911..26c76d1 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -44,20 +44,8 @@ var ( AuthCmd = &cobra.Command{ Use: "auth", Short: "Authenticate with OIDC provider", - Long: `Use your Google login to get a JWT (JSON Web Token) and update your -local k8s config accordingly. A refresh token is added and automatically refreshed -by kubectl. Existing token configurations are overwritten. + Long: `Use a provider sub-command to authenticate against your identity provider of choice. For details go to: https://blog.gini.net/ - -dexters authentication flow -=========================== - -1. Open a browser window/tab and redirect you to Google (https://accounts.google.com) -2. You login with your Google credentials -3. You will be redirected to dexters builtin webserver and can now close the browser tab -4. dexter extracts the token from the callback and patches your ~/.kube/config - -➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! `, } ) @@ -376,7 +364,7 @@ func init() { AuthCmd.PersistentFlags().StringVarP(&clientID, "client-id", "i", "REDACTED", "Google clientID") AuthCmd.PersistentFlags().StringVarP(&clientSecret, "client-secret", "s", "REDACTED", "Google clientSecret") AuthCmd.PersistentFlags().StringVarP(&callback, "callback", "c", "http://127.0.0.1:64464/callback", "Callback URL. The listen address is dreived from that.") - AuthCmd.PersistentFlags().StringVarP(&kubeConfig, "kube-config", "k", kubeConfigDefaultPath, "Overwrite the default location of kube config (~/.kube/config)") + AuthCmd.PersistentFlags().StringVarP(&kubeConfig, "kube-config", "k", kubeConfigDefaultPath, "Overwrite the default location of kube config") AuthCmd.PersistentFlags().BoolVarP(&dryRun, "dry-run", "d", false, "Toggle config overwrite") } diff --git a/cmd/azure.go b/cmd/azure.go index 828cae0..b6b19d0 100644 --- a/cmd/azure.go +++ b/cmd/azure.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "net/http" "os" "regexp" @@ -64,10 +65,11 @@ var ( azureCmd = &cobra.Command{ Use: "azure", - Short: "Authenticate with OIDC provider", + Short: "Authenticate with the Microsoft Azure Identity Provider", Long: `Use your Microsoft login to get a JWT (JSON Web Token) and update your local k8s config accordingly. A refresh token is added and automatically refreshed by kubectl. Existing token configurations are overwritten. + For details go to: https://blog.gini.net/ dexters authentication flow @@ -80,7 +82,7 @@ dexters authentication flow ➜ Unless you have a good reason to do so please use the built-in Microsoft credentials (if they were added at build time)! `, - Run: AzureCommand, + RunE: AzureCommand, } ) @@ -92,13 +94,15 @@ func init() { azureCmd.PersistentFlags().StringVarP(&azureProvider.tenant, "tenant", "t", "common", "Your azure tenant") } -func AzureCommand(cmd *cobra.Command, args []string) { +func AzureCommand(cmd *cobra.Command, args []string) error { azureProvider.initialize() azureProvider.Oauth2Config.Endpoint = microsoft.AzureADEndpoint(azureProvider.tenant) azureProvider.Oauth2Config.Scopes = []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "email"} if err := AuthenticateToProvider(azureProvider); err != nil { - log.Errorf("Authentication failed: %s", err) + return fmt.Errorf("authentication failed: %s", err) } + + return nil } diff --git a/cmd/google.go b/cmd/google.go index e3c99f5..f123d82 100644 --- a/cmd/google.go +++ b/cmd/google.go @@ -1,12 +1,12 @@ package cmd import ( + "fmt" "net/http" "os" "time" "github.com/coreos/go-oidc" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/oauth2" "golang.org/x/oauth2/google" @@ -28,10 +28,11 @@ var ( googleCmd = &cobra.Command{ Use: "google", - Short: "Authenticate with OIDC provider", + Short: "Authenticate with the Google Identity Provider", Long: `Use your Google login to get a JWT (JSON Web Token) and update your local k8s config accordingly. A refresh token is added and automatically refreshed by kubectl. Existing token configurations are overwritten. + For details go to: https://blog.gini.net/ dexters authentication flow @@ -44,7 +45,7 @@ dexters authentication flow ➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! `, - Run: GoogleCommand, + RunE: GoogleCommand, } ) @@ -53,13 +54,15 @@ func init() { AuthCmd.AddCommand(googleCmd) } -func GoogleCommand(cmd *cobra.Command, args []string) { +func GoogleCommand(cmd *cobra.Command, args []string) error { googleProvider.initialize() googleProvider.Oauth2Config.Endpoint = google.Endpoint googleProvider.Oauth2Config.Scopes = []string{oidc.ScopeOpenID, "profile", "email"} if err := AuthenticateToProvider(googleProvider); err != nil { - log.Errorf("Authentication failed: %s", err) + return fmt.Errorf("authentication failed: %s", err) } + + return nil } From 466323ecfe4f9d129aaf101d0bdbfcffbc6cf4e0 Mon Sep 17 00:00:00 2001 From: Daniel Kerwin Date: Thu, 7 Nov 2019 17:28:13 +0100 Subject: [PATCH 4/6] Fix azure (and potentially all future providers) autopilot mode --- cmd/auth.go | 58 ++++++++++++++++++++++++++++++--------------------- cmd/azure.go | 26 ++++++++++++++++------- cmd/google.go | 3 ++- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index 26c76d1..ca9cadf 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -57,8 +57,9 @@ type CustomClaim struct { // interface that all OIDC providers need to implement type OIDCProvider interface { + ConfigureOAuth2Manully() error + Autopilot() error PreflightCheck() error - CreateOauth2Config() error GenerateAuthUrl() string StartHTTPServer() error } @@ -89,43 +90,48 @@ func (d DexterOIDC) PreflightCheck() error { return nil } -// create Oauth2 configuration -func (d *DexterOIDC) CreateOauth2Config() error { - return d.DefaultOauth2Config() -} - // create Oauth2 configuration func (d *DexterOIDC) AuthInfoToOauth2(authInfo *clientCmdApi.AuthInfo) { d.clientSecret = authInfo.AuthProvider.Config["client-secret"] d.clientID = authInfo.AuthProvider.Config["client-id"] } -func (d *DexterOIDC) DefaultOauth2Config() error { +// attempt to set client credentials +func (d *DexterOIDC) ConfigureOAuth2Manully() error { + d.Oauth2Config.RedirectURL = d.callback + // no commandline client credentials supplied if d.clientID == "REDACTED" && d.clientSecret == "REDACTED" { // no builtin defaults - let's try auto-configuration if buildTimeClientID == "" && buildTimeClientSecret == "" { - log.Info("Autopilot mode - no credentials set") - if authInfo, err := ExtractAuthInfo(d.kubeConfig); err != nil { - return errors.New(fmt.Sprintf("failed to extract oidc configuration from the kube config: %s", err)) - } else { - d.AuthInfoToOauth2(authInfo) - } - } else if buildTimeClientID != "" && buildTimeClientSecret != "" { - // use build-time defaults if no clientId & clientSecret was provided + return errors.New("cannot set client credentials: empty commandline and builtin defaults") + } else { log.Info("Using builtin credentials - no credentials set") d.clientID = buildTimeClientID d.clientSecret = buildTimeClientSecret } } - // setup oidc client context - oidc.ClientContext(context.Background(), d.httpClient) - // populate oauth2 config d.Oauth2Config.ClientID = d.clientID d.Oauth2Config.ClientSecret = d.clientSecret - d.Oauth2Config.RedirectURL = d.callback + + return nil +} + +func (d *DexterOIDC) Autopilot() error { + log.Info("Autopilot mode - no credentials set") + if authInfo, err := ExtractAuthInfo(d.kubeConfig); err != nil { + return errors.New(fmt.Sprintf("failed to extract oidc configuration from the kube config: %s", err)) + } else { + d.clientSecret = authInfo.AuthProvider.Config["client-secret"] + d.clientID = authInfo.AuthProvider.Config["client-id"] + + // populate oauth2 config + d.Oauth2Config.RedirectURL = d.callback + d.Oauth2Config.ClientID = d.clientID + d.Oauth2Config.ClientSecret = d.clientSecret + } return nil } @@ -370,16 +376,20 @@ func init() { // initiate the OIDC flow. This func should be called in each cobra command func AuthenticateToProvider(provider OIDCProvider) error { + // attempt to set client credentials with dexter data + if err := provider.ConfigureOAuth2Manully(); err != nil { + log.Infof("Fallback to autopilot mode: %s", err) + + if err := provider.Autopilot(); err != nil { + return fmt.Errorf("failed to configure oauth2 credentials: %s", err) + } + } + // ensure that the required fields and values are sane if err := provider.PreflightCheck(); err != nil { return fmt.Errorf("failed to complete the provider preflight check: %s", err) } - // create the Oauth2 configuration - if err := provider.CreateOauth2Config(); err != nil { - return fmt.Errorf("failed to create the Oauth2 provider configuration: %s", err) - } - log.Info("Starting auth browser session. Please check your browser instances...") if err := utils.OpenURL(provider.GenerateAuthUrl()); err != nil { diff --git a/cmd/azure.go b/cmd/azure.go index b6b19d0..248aa42 100644 --- a/cmd/azure.go +++ b/cmd/azure.go @@ -9,11 +9,9 @@ import ( "time" "github.com/coreos/go-oidc" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/oauth2" "golang.org/x/oauth2/microsoft" - clientCmdApi "k8s.io/client-go/tools/clientcmd/api" ) type AzureOIDC struct { @@ -21,13 +19,24 @@ type AzureOIDC struct { tenant string // azure tenant } -func (a *AzureOIDC) AuthInfoToOauth2(authInfo *clientCmdApi.AuthInfo) { +func (a *AzureOIDC) Autopilot() error { + authInfo, err := ExtractAuthInfo(a.kubeConfig) + + if err != nil { + return fmt.Errorf("failed to extract oidc configuration from the kube config: %s", err) + } + // fallback ti tenant common a.tenant = "common" // call parent method to initialize client credentials a.DexterOIDC.AuthInfoToOauth2(authInfo) + // populate oauth2 config + a.Oauth2Config.RedirectURL = a.callback + a.Oauth2Config.ClientID = a.clientID + a.Oauth2Config.ClientSecret = a.clientSecret + // extract the issuer url idp := authInfo.AuthProvider.Config["idp-issuer-url"] @@ -38,17 +47,17 @@ func (a *AzureOIDC) AuthInfoToOauth2(authInfo *clientCmdApi.AuthInfo) { if err != nil { // failed to extract the azure tenant, use default "common - return + return err } res := re.FindStringSubmatch(idp) if len(res) == 1 { a.tenant = res[0] // found tenant - return + return nil } - } else { - log.Info("No Microsoft auth provider configuration found") } + + return fmt.Errorf("no Microsoft auth provider configuration found") } var ( @@ -82,7 +91,8 @@ dexters authentication flow ➜ Unless you have a good reason to do so please use the built-in Microsoft credentials (if they were added at build time)! `, - RunE: AzureCommand, + RunE: AzureCommand, + SilenceUsage: true, } ) diff --git a/cmd/google.go b/cmd/google.go index f123d82..2bdb465 100644 --- a/cmd/google.go +++ b/cmd/google.go @@ -45,7 +45,8 @@ dexters authentication flow ➜ Unless you have a good reason to do so please use the built-in google credentials (if they were added at build time)! `, - RunE: GoogleCommand, + RunE: GoogleCommand, + SilenceUsage: true, } ) From 4bf921639989abd4bf971fb23844be7fe268f8a5 Mon Sep 17 00:00:00 2001 From: Daniel Kerwin Date: Thu, 7 Nov 2019 18:01:21 +0100 Subject: [PATCH 5/6] Add missing contributor and use github profile links --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fd187b5..834fbc4 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,11 @@ It's awesome that you consider contributing to `dexter` and it's really simple. Initial code was written by [Daniel Kerwin](mailto:daniel@gini.net) & David González Ruiz Contributors (in alphabetical order): - - @andrewsav-datacom - - @Lujeni - - @pussinboots - - @tillepille +- https://github.com/andrewsav-datacom +- https://github.com/cblims +- https://github.com/Lujeni +- https://github.com/pussinboots +- https://github.com/tillepille Thank you so much! From c5e95c153ad5d0747291cba3b5e4463753b7863b Mon Sep 17 00:00:00 2001 From: Daniel Kerwin Date: Mon, 18 Nov 2019 15:47:05 +0100 Subject: [PATCH 6/6] Fix azure autopilot mode. Tenant id is extracted AND used --- cmd/auth.go | 1 - cmd/azure.go | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index ca9cadf..b69e909 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -179,7 +179,6 @@ func (d DexterOIDC) StartHTTPServer() error { default: } } - } // initialize the struct, parse commandline flags and install a signal handler diff --git a/cmd/azure.go b/cmd/azure.go index 248aa42..429e86c 100644 --- a/cmd/azure.go +++ b/cmd/azure.go @@ -9,6 +9,7 @@ import ( "time" "github.com/coreos/go-oidc" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/oauth2" "golang.org/x/oauth2/microsoft" @@ -20,6 +21,8 @@ type AzureOIDC struct { } func (a *AzureOIDC) Autopilot() error { + log.Debug("Azure OIDC autopilot mode") + authInfo, err := ExtractAuthInfo(a.kubeConfig) if err != nil { @@ -52,7 +55,11 @@ func (a *AzureOIDC) Autopilot() error { res := re.FindStringSubmatch(idp) if len(res) == 1 { - a.tenant = res[0] // found tenant + // found tenant: override endpoint + azureProvider.tenant = res[0] + a.Oauth2Config.Endpoint = microsoft.AzureADEndpoint(azureProvider.tenant) + + log.Debugf("Extracted tenant: %s", a.tenant) return nil } }