Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(azureOauth2): fixed oauth2 auth for azure custom config support #305

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 63 additions & 18 deletions cmd/gateclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ import (
_ "net/http/pprof"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"

"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/net/publicsuffix"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"sigs.k8s.io/yaml"
Expand Down Expand Up @@ -187,7 +190,7 @@ func NewGateClient(ui output.Ui, gateEndpoint, defaultHeaders, configLocation st
// TODO: Verify version compatibility between Spin CLI and Gate.
_, _, err = gateClient.VersionControllerApi.GetVersionUsingGET(gateClient.Context)
if err != nil {
ui.Error("Could not reach Gate, please ensure it is running. Failing.")
ui.Error(fmt.Sprintf("Could not reach Gate, please ensure it is running. Failing. %s", err))
return nil, err
}

Expand Down Expand Up @@ -238,7 +241,7 @@ func userConfig(gateClient *GatewayClient, configLocation string) error {
// InitializeHTTPClient will return an *http.Client configured with
// optional TLS keys as specified in the auth.Config
func InitializeHTTPClient(auth *auth.Config) (*http.Client, error) {
cookieJar, _ := cookiejar.New(nil)
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client := http.Client{
Jar: cookieJar,
Transport: http.DefaultTransport.(*http.Transport).Clone(),
Expand Down Expand Up @@ -409,31 +412,51 @@ func authenticateOAuth2(output func(string), httpClient *http.Client, endpoint s
return false, errors.Wrapf(err, "Could not refresh token from source: %v", tokenSource)
}
} else {
done := make(chan bool)
verifier, verifierCode, err := generateCodeVerifier()
codeVerifier := oauth2.SetAuthURLParam("code_verifier", verifier)
if err != nil {
return false, err
}

// Do roundtrip.
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
fmt.Fprintln(w, code)
newToken, err = config.Exchange(context.Background(), code, codeVerifier)
if err != nil {
errors.Wrapf(err, "Could not get the authentication token: %s", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
done <- true
}
}))
go http.ListenAndServe(":8085", nil)
// Note: leaving server connection open for scope of request, will be reaped on exit.

verifier, verifierCode, err := generateCodeVerifier()
if err != nil {
return false, err
}

codeVerifier := oauth2.SetAuthURLParam("code_verifier", verifier)
//AuthCodeOption can be overriden from the config file.
opts := make([]oauth2.AuthCodeOption, 0)
codeChallenge := oauth2.SetAuthURLParam("code_challenge", verifierCode)
challengeMethod := oauth2.SetAuthURLParam("code_challenge_method", "S256")
codeChallengeMethod := oauth2.SetAuthURLParam("code_challenge_method", "S256")
opts = append(opts, codeChallenge, codeChallengeMethod)

authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline, oauth2.ApprovalForce, challengeMethod, codeChallenge)
output(fmt.Sprintf("Navigate to %s and authenticate", authURL))
code := prompt(output, "Paste authorization code:")
if _, found := auth.OAuth2.AuthCodeOptions["access_type"]; !found {
auth.OAuth2.AuthCodeOptions["access_type"] = "offline"
}
if _, found := auth.OAuth2.AuthCodeOptions["prompt"]; !found {
auth.OAuth2.AuthCodeOptions["prompt"] = "consent"
}

//Add all the authCodeOptions from config.
for key, element := range auth.OAuth2.AuthCodeOptions {
opts = append(opts, oauth2.SetAuthURLParam(key, element))
}
authURL := config.AuthCodeURL("state-token", opts...)

newToken, err = config.Exchange(context.Background(), code, codeVerifier)
err = open(authURL)
if err != nil {
return false, err
output(fmt.Sprintf("Navigate to %s and authenticate", authURL))
}
<-done
}
OAuth2.CachedToken = newToken
err = login(httpClient, endpoint, newToken.AccessToken)
Expand Down Expand Up @@ -499,11 +522,15 @@ func login(httpClient *http.Client, endpoint string, accessToken string) error {
return err
}
loginReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))

_, err = httpClient.Do(loginReq) // Login to establish session.
resp, err := httpClient.Do(loginReq) // Login to establish session.
if err != nil {
return errors.New("login failed")
return errors.Errorf("login failed %s", err)
}

if resp.StatusCode != 200 {
return errors.Errorf("login failed %s", resp.Status)
}

return nil
}

Expand Down Expand Up @@ -599,3 +626,21 @@ func securePrompt(output func(string), inputMsg string) string {
secret := string(byteSecret)
return strings.TrimSpace(secret)
}

// open opens the specified URL in the default browser of the user.
func open(url string) error {
var cmd string
var args []string

switch runtime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
default: // "linux", "freebsd", "openbsd", "netbsd"
cmd = "xdg-open"
}
args = append(args, url)
return exec.Command(cmd, args...).Start()
}
13 changes: 7 additions & 6 deletions config/auth/oauth2/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import (
// Config is the configuration for using OAuth2.0 to
// authenticate with Spinnaker
type Config struct {
TokenUrl string `yaml:"tokenUrl"`
AuthUrl string `yaml:"authUrl"`
ClientId string `yaml:"clientId"`
ClientSecret string `yaml:"clientSecret"`
Scopes []string `yaml:"scopes"`
CachedToken *oauth2.Token `yaml:"cachedToken,omitempty"`
TokenUrl string `yaml:"tokenUrl"`
AuthUrl string `yaml:"authUrl"`
ClientId string `yaml:"clientId"`
ClientSecret string `yaml:"clientSecret"`
AuthCodeOptions map[string]string `yaml:"authCodeOptions,omitempty"`
Scopes []string `yaml:"scopes"`
CachedToken *oauth2.Token `yaml:"cachedToken,omitempty"`
}

func (x *Config) IsValid() bool {
Expand Down
5 changes: 5 additions & 0 deletions config/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ auth:
scopes:
- scope1
- scope2
# To set Oauth AuthCodeOptions, follow the following format. Auzre Oauth2 API needs this
# see https://pkg.go.dev/golang.org/x/oauth2#AuthCodeOption
AuthCodeOptions:
access_type: online
prompt: none

# To set a cached token, follow the following format:
# note that these yaml keys must match the golang struct tags exactly because of yaml.UnmarshalStrict
Expand Down