Skip to content

Commit 32800b4

Browse files
author
Nick Barss
committed
added kc broker flag
1 parent 148bb41 commit 32800b4

File tree

7 files changed

+68
-25
lines changed

7 files changed

+68
-25
lines changed

cmd/saml2aws/commands/login.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525

2626
// Login login to ADFS
2727
func Login(loginFlags *flags.LoginExecFlags) error {
28-
2928
logger := logrus.WithField("command", "login")
3029

3130
account, err := buildIdpAccount(loginFlags)
@@ -258,6 +257,11 @@ func resolveLoginDetails(account *cfg.IDPAccount, loginFlags *flags.LoginExecFla
258257
loginDetails.DownloadBrowser = account.DownloadBrowser
259258
}
260259

260+
// parse KCBroker if set
261+
if account.KCBroker != "" {
262+
loginDetails.KCBroker = account.KCBroker
263+
}
264+
261265
// log.Printf("loginDetails %+v", loginDetails)
262266

263267
// if skip prompt was passed just pass back the flag values

cmd/saml2aws/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ func main() {
9191
app.Flag("disable-keychain", "Do not use keychain at all. This will also disable Okta sessions & remembering MFA device. (env: SAML2AWS_DISABLE_KEYCHAIN)").Envar("SAML2AWS_DISABLE_KEYCHAIN").BoolVar(&commonFlags.DisableKeychain)
9292
app.Flag("region", "AWS region to use for API requests, e.g. us-east-1, us-gov-west-1, cn-north-1 (env: SAML2AWS_REGION)").Envar("SAML2AWS_REGION").Short('r').StringVar(&commonFlags.Region)
9393
app.Flag("prompter", "The prompter to use for user input (default, pinentry)").StringVar(&commonFlags.Prompter)
94+
app.Flag("kc-broker", "The kc broker to use when authenticating via keycloak").StringVar(&commonFlags.KCBroker)
9495

9596
// `configure` command and settings
9697
cmdConfigure := app.Command("configure", "Configure a new IDP account.")
@@ -190,7 +191,6 @@ func main() {
190191
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
191192

192193
logrus.WithField("command", command).Debug("Running")
193-
194194
var err error
195195
switch command {
196196
case cmdScript.FullCommand():

pkg/cfg/cfg.go

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ type IDPAccount struct {
6969
Prompter string `ini:"prompter"`
7070
KCAuthErrorMessage string `ini:"kc_auth_error_message,omitempty"` // used by KeyCloak; hide from user if not set
7171
KCAuthErrorElement string `ini:"kc_auth_error_element,omitempty"` // used by KeyCloak; hide from user if not set
72+
KCBroker string `ini:"kc_broker"` // used by KeyCloak;
7273
}
7374

7475
func (ia IDPAccount) String() string {

pkg/creds/creds.go

+1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ type LoginDetails struct {
1313
URL string
1414
StateToken string // used by Okta
1515
OktaSessionCookie string // used by Okta
16+
KCBroker string // used by KeyCloak
1617
}

pkg/flags/flags.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type CommonFlags struct {
3939
DisableRememberDevice bool
4040
DisableSessions bool
4141
Prompter string
42+
KCBroker string
4243
}
4344

4445
// LoginExecFlags flags for the Login / Exec commands
@@ -147,7 +148,9 @@ func ApplyFlagOverrides(commonFlags *CommonFlags, account *cfg.IDPAccount) {
147148
if commonFlags.Prompter != "" {
148149
account.Prompter = commonFlags.Prompter
149150
}
150-
151+
if commonFlags.KCBroker != "" {
152+
account.KCBroker = commonFlags.KCBroker
153+
}
151154
// select the prompter
152155
if commonFlags.Prompter != "" {
153156
account.Prompter = commonFlags.Prompter

pkg/provider/keycloak/keycloak.go

+49-19
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error)
9696
}
9797

9898
func (kc *Client) doAuthenticate(authCtx *authContext, loginDetails *creds.LoginDetails) (string, error) {
99-
authSubmitURL, authForm, err := kc.getLoginForm(loginDetails)
99+
authSubmitURL, authForm, authCookies, err := kc.getLoginForm(loginDetails)
100100
if err != nil {
101101
return "", errors.Wrap(err, "error retrieving login form from idp")
102102
}
@@ -129,18 +129,16 @@ func (kc *Client) doAuthenticate(authCtx *authContext, loginDetails *creds.Login
129129
if err != nil {
130130
return "", errors.Wrap(err, "could not extract Webauthn parameters")
131131
}
132-
133132
webauthnSubmitURL, err := extractSubmitURL(doc)
134133
if err != nil {
135134
return "", errors.Wrap(err, "unable to locate IDP Webauthn form submit URL")
136135
}
137136

138-
doc, err = kc.postWebauthnForm(webauthnSubmitURL, credentialIDs, challenge, rpId)
137+
doc, err = kc.postWebauthnForm(webauthnSubmitURL, credentialIDs, challenge, rpId, authCookies)
139138
if err != nil {
140139
return "", errors.Wrap(err, "error posting Webauthn form")
141140
}
142141
}
143-
144142
samlResponse, err := extractSamlResponse(doc)
145143
if err != nil && authCtx.authenticatorIndexValid && passwordValid(doc, kc.authErrorValidator) {
146144
return kc.doAuthenticate(authCtx, loginDetails)
@@ -185,22 +183,58 @@ func extractWebauthnParameters(doc *goquery.Document) (credentialIDs []string, c
185183
return credentialIDs, challenge, rpID, nil
186184
}
187185

188-
func (kc *Client) getLoginForm(loginDetails *creds.LoginDetails) (string, url.Values, error) {
186+
func (kc *Client) getLoginForm(loginDetails *creds.LoginDetails) (string, url.Values, []*http.Cookie, error) {
189187

190188
res, err := kc.client.Get(loginDetails.URL)
189+
191190
if err != nil {
192-
return "", nil, errors.Wrap(err, "error retrieving form")
191+
return "", nil, nil, errors.Wrap(err, "error retrieving form")
193192
}
194193

195194
doc, err := goquery.NewDocumentFromReader(res.Body)
196195
if err != nil {
197-
return "", nil, errors.Wrap(err, "failed to build document from response")
196+
return "", nil, nil, errors.Wrap(err, "failed to build document from response")
198197
}
199198

199+
if loginDetails.KCBroker != "" {
200+
log.Printf("Attempting to parse federation chain using keycloak broker %v", loginDetails.KCBroker)
201+
KCFederationID := "#social-" + loginDetails.KCBroker
202+
var path string
203+
doc.Find(KCFederationID).Each(func(i int, s *goquery.Selection) {
204+
href, ok := s.Attr("href")
205+
if !ok {
206+
return
207+
}
208+
path = href
209+
})
210+
url, err := url.Parse(loginDetails.URL)
211+
if err != nil {
212+
return "", nil, nil, errors.Wrap(err, "error parsing url for federation")
213+
}
214+
new_url := "https://" + url.Hostname() + path
215+
req, err := http.NewRequest("GET", new_url, nil)
216+
if err != nil {
217+
return "", nil, nil, errors.Wrap(err, "error building federated request")
218+
}
219+
for _, cookie := range res.Cookies() {
220+
req.AddCookie(cookie)
221+
// log.Println("added cookie: %v to request", cookie)
222+
}
223+
res2, err := kc.client.Get(new_url)
224+
if err != nil {
225+
return "", nil, nil, errors.Wrap(err, "error retrieving federated form")
226+
}
227+
doc2, err := goquery.NewDocumentFromReader(res2.Body)
228+
if err != nil {
229+
return "", nil, nil, errors.Wrap(err, "failed to build federated document from response")
230+
}
231+
doc = doc2
232+
res = res2
233+
}
200234
if res.StatusCode == http.StatusUnauthorized {
201235
authSubmitURL, err := extractSubmitURL(doc)
202236
if err != nil {
203-
return "", nil, errors.Wrap(err, "unable to locate IDP authentication form submit URL")
237+
return "", nil, nil, errors.Wrap(err, "unable to locate IDP authentication form submit URL")
204238
}
205239
loginDetails.URL = authSubmitURL
206240
return kc.getLoginForm(loginDetails)
@@ -211,13 +245,13 @@ func (kc *Client) getLoginForm(loginDetails *creds.LoginDetails) (string, url.Va
211245
doc.Find("input").Each(func(i int, s *goquery.Selection) {
212246
updateKeyCloakFormData(authForm, s, loginDetails)
213247
})
214-
248+
authCookies := res.Cookies()
215249
authSubmitURL, err := extractSubmitURL(doc)
216250
if err != nil {
217-
return "", nil, errors.Wrap(err, "unable to locate IDP authentication form submit URL")
251+
return "", nil, nil, errors.Wrap(err, "unable to locate IDP authentication form submit URL")
218252
}
219253

220-
return authSubmitURL, authForm, nil
254+
return authSubmitURL, authForm, authCookies, nil
221255
}
222256

223257
func (kc *Client) postLoginForm(authSubmitURL string, authForm url.Values) ([]byte, error) {
@@ -279,9 +313,8 @@ func (kc *Client) postTotpForm(authCtx *authContext, totpSubmitURL string, doc *
279313
return doc, nil
280314
}
281315

282-
func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []string, challenge, rpId string) (*goquery.Document, error) {
316+
func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []string, challenge, rpId string, cookies []*http.Cookie) (*goquery.Document, error) {
283317
webauthnForm := url.Values{}
284-
285318
var assertion *okta.SignedAssertion
286319
var pickedCredentialID string
287320
for i, credentialID := range credentialIDs {
@@ -326,14 +359,14 @@ func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []str
326359
webauthnForm.Set("credentialId", pickedCredentialID)
327360
webauthnForm.Set("userHandle", "")
328361
webauthnForm.Set("error", "")
329-
330362
req, err := http.NewRequest("POST", webauthnSubmitURL, strings.NewReader(webauthnForm.Encode()))
331363
if err != nil {
332364
return nil, errors.Wrap(err, "error building MFA request")
333365
}
334-
335366
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
336-
367+
for _, cookie := range cookies {
368+
req.AddCookie(cookie)
369+
}
337370
res, err := kc.client.Do(req)
338371
if err != nil {
339372
return nil, errors.Wrap(err, "error retrieving content")
@@ -343,7 +376,6 @@ func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []str
343376
if err != nil {
344377
return nil, errors.Wrap(err, "error reading webauthn form response")
345378
}
346-
347379
return doc, nil
348380
}
349381

@@ -366,7 +398,6 @@ func extractSubmitURL(doc *goquery.Document) (string, error) {
366398
}
367399
submitURL = action
368400
})
369-
370401
if submitURL == "" {
371402
return "", fmt.Errorf("unable to locate form submit URL")
372403
}
@@ -377,7 +408,6 @@ func extractSubmitURL(doc *goquery.Document) (string, error) {
377408
func extractSamlResponse(doc *goquery.Document) (string, error) {
378409
var samlAssertion = ""
379410
var err = fmt.Errorf("unable to locate saml response field")
380-
381411
doc.Find("input").Each(func(i int, s *goquery.Selection) {
382412
name, ok := s.Attr("name")
383413
if ok && name == "SAMLResponse" {

pkg/provider/keycloak/keycloak_test.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ func TestClient_getLoginForm(t *testing.T) {
3636
kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}}
3737
loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "test", Password: "test123"}
3838

39-
submitURL, authForm, err := kc.getLoginForm(loginDetails)
39+
submitURL, authForm, authCookies, err := kc.getLoginForm(loginDetails)
4040
require.Nil(t, err)
4141
require.Equal(t, exampleLoginURL, submitURL)
4242
require.Equal(t, url.Values{
4343
"username": []string{"test"},
4444
"password": []string{"test123"},
4545
"login": []string{"Log in"},
4646
}, authForm)
47+
require.Equal(t, []*http.Cookie([]*http.Cookie{}), authCookies)
48+
4749
}
4850

4951
func TestClient_getLoginFormTryAnotherWay(t *testing.T) {
@@ -59,14 +61,15 @@ func TestClient_getLoginFormTryAnotherWay(t *testing.T) {
5961
kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}}
6062
loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "test", Password: "test123"}
6163

62-
submitURL, authForm, err := kc.getLoginForm(loginDetails)
64+
submitURL, authForm, authCookies, err := kc.getLoginForm(loginDetails)
6365
require.Nil(t, err)
6466
require.Equal(t, exampleLoginURL, submitURL)
6567
require.Equal(t, url.Values{
6668
"username": []string{"test"},
6769
"password": []string{"test123"},
6870
"login": []string{"Log in"},
6971
}, authForm)
72+
require.Equal(t, []*http.Cookie([]*http.Cookie{}), authCookies)
7073
}
7174

7275
func TestClient_getLoginFormRedirect(t *testing.T) {
@@ -93,7 +96,7 @@ func TestClient_getLoginFormRedirect(t *testing.T) {
9396
kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}}
9497
loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "test", Password: "test123"}
9598

96-
submitURL, authForm, err := kc.getLoginForm(loginDetails)
99+
submitURL, authForm, authCookies, err := kc.getLoginForm(loginDetails)
97100
require.Nil(t, err)
98101
require.Equal(t, 2, count)
99102
require.Equal(t, exampleLoginURL, submitURL)
@@ -102,6 +105,7 @@ func TestClient_getLoginFormRedirect(t *testing.T) {
102105
"password": []string{"test123"},
103106
"login": []string{"Log in"},
104107
}, authForm)
108+
require.Equal(t, []*http.Cookie([]*http.Cookie{}), authCookies)
105109
}
106110

107111
func TestClient_postLoginForm(t *testing.T) {

0 commit comments

Comments
 (0)