github.com/spinnaker/spin@v1.30.0/config/auth/iap/user_iap_auth_helper.go (about) 1 // Copyright (c) 2018, Snap Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "crypto/rand" 19 "encoding/base64" 20 "fmt" 21 "net" 22 "net/http" 23 "net/url" 24 25 "golang.org/x/oauth2" 26 27 "github.com/spinnaker/spin/util/execcmd" 28 ) 29 30 const ( 31 googleOauthHost = "accounts.google.com" 32 googleOauthPath = "/o/oauth2/v2/auth" 33 googleOauthSchema = "https" 34 googleOauthResponseType = "code" 35 googleOauthScope = "openid email" 36 ) 37 38 // returns the token get from google for IAP 39 func GetIapToken(iapConfig Config) (string, error) { 40 if iapConfig.IapIdToken != "" { 41 return iapConfig.IapIdToken, nil 42 } 43 44 if iapConfig.ServiceAccountKeyPath != "" && iapConfig.IapClientId != "" { 45 return GetIDTokenWithServiceAccount(iapConfig) 46 } 47 48 if iapConfig.IapClientRefresh == "" { 49 return userInteract(iapConfig) 50 } 51 52 return RequestIapIDToken(iapConfig.IapClientRefresh, 53 iapConfig.OAuthClientId, 54 iapConfig.OAuthClientSecret, 55 iapConfig.IapClientId) 56 } 57 58 // userInteract lets the spin user fetch a token. 59 func userInteract(cfg Config) (string, error) { 60 listener, err := net.Listen("tcp", "127.0.0.1:0") 61 if err != nil { 62 return "", err 63 } 64 65 port := listener.Addr().(*net.TCPAddr).Port 66 clientStateToken := make([]byte, serverStateTokenLen) 67 if _, err = rand.Read(clientStateToken); err != nil { 68 return "", err 69 } 70 71 clientState := base64.URLEncoding.EncodeToString(clientStateToken) 72 accessToken := make(chan string) 73 74 rcv := &oauthReceiver{ 75 port: port, 76 clientState: clientState, 77 doneChan: make(chan error), 78 callback: func(token *oauth2.Token, config *oauth2.Config, s2 string) (string, error) { 79 iapToken, err := RequestIapIDToken(token.AccessToken, 80 cfg.OAuthClientId, 81 cfg.OAuthClientSecret, 82 cfg.IapClientId) 83 if err != nil { 84 close(accessToken) 85 return "", err 86 } 87 accessToken <- iapToken 88 return "", nil 89 }, 90 clientId: cfg.OAuthClientId, 91 clientSecret: cfg.OAuthClientSecret, 92 } 93 94 srv := http.Server{Addr: listener.Addr().String(), Handler: rcv, ConnState: rcv.killWhenReady} 95 go srv.Serve(listener) 96 97 url := oauthURL(cfg.OAuthClientId, clientState, port) 98 99 resStr := fmt.Sprintf("Your browser has been opened to visit:\n%s\n\n", url) 100 if err = execcmd.OpenUrl(url); err != nil { 101 resStr = fmt.Sprintf("Follow this link in your browser:\n%s\n\n", url) 102 } 103 fmt.Println(resStr) 104 105 return <-accessToken, nil 106 } 107 108 func oauthURL(clientId string, clientState string, port int) string { 109 oauthUrl := url.URL{ 110 Scheme: googleOauthSchema, 111 Host: googleOauthHost, 112 Path: googleOauthPath, 113 } 114 115 q := oauthUrl.Query() 116 q.Add("client_id", clientId) 117 q.Add("state", clientState) 118 q.Add("response_type", googleOauthResponseType) 119 q.Add("scope", googleOauthScope) 120 q.Add("redirect_uri", fmt.Sprintf("http://localhost:%d", port)) 121 oauthUrl.RawQuery = q.Encode() 122 123 return oauthUrl.String() 124 }