Skip to content

Commit 4dea112

Browse files
authored
Merge pull request #1396 from catalyst-ca/master
added kc broker flag
2 parents d3a3486 + 32800b4 commit 4dea112

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
@@ -98,7 +98,7 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error)
9898
}
9999

100100
func (kc *Client) doAuthenticate(authCtx *authContext, loginDetails *creds.LoginDetails) (string, error) {
101-
authSubmitURL, authForm, err := kc.getLoginForm(loginDetails)
101+
authSubmitURL, authForm, authCookies, err := kc.getLoginForm(loginDetails)
102102
if err != nil {
103103
return "", errors.Wrap(err, "error retrieving login form from idp")
104104
}
@@ -131,18 +131,16 @@ func (kc *Client) doAuthenticate(authCtx *authContext, loginDetails *creds.Login
131131
if err != nil {
132132
return "", errors.Wrap(err, "could not extract Webauthn parameters")
133133
}
134-
135134
webauthnSubmitURL, err := extractSubmitURL(doc)
136135
if err != nil {
137136
return "", errors.Wrap(err, "unable to locate IDP Webauthn form submit URL")
138137
}
139138

140-
doc, err = kc.postWebauthnForm(webauthnSubmitURL, credentialIDs, challenge, rpId)
139+
doc, err = kc.postWebauthnForm(webauthnSubmitURL, credentialIDs, challenge, rpId, authCookies)
141140
if err != nil {
142141
return "", errors.Wrap(err, "error posting Webauthn form")
143142
}
144143
}
145-
146144
samlResponse, err := extractSamlResponse(doc)
147145
if err != nil && authCtx.authenticatorIndexValid && passwordValid(doc, kc.authErrorValidator) {
148146
return kc.doAuthenticate(authCtx, loginDetails)
@@ -187,22 +185,58 @@ func extractWebauthnParameters(doc *goquery.Document) (credentialIDs []string, c
187185
return credentialIDs, challenge, rpID, nil
188186
}
189187

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

192190
res, err := kc.client.Get(loginDetails.URL)
191+
193192
if err != nil {
194-
return "", nil, errors.Wrap(err, "error retrieving form")
193+
return "", nil, nil, errors.Wrap(err, "error retrieving form")
195194
}
196195

197196
doc, err := goquery.NewDocumentFromReader(res.Body)
198197
if err != nil {
199-
return "", nil, errors.Wrap(err, "failed to build document from response")
198+
return "", nil, nil, errors.Wrap(err, "failed to build document from response")
200199
}
201200

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

222-
return authSubmitURL, authForm, nil
256+
return authSubmitURL, authForm, authCookies, nil
223257
}
224258

225259
func (kc *Client) postLoginForm(authSubmitURL string, authForm url.Values) ([]byte, error) {
@@ -281,9 +315,8 @@ func (kc *Client) postTotpForm(authCtx *authContext, totpSubmitURL string, doc *
281315
return doc, nil
282316
}
283317

284-
func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []string, challenge, rpId string) (*goquery.Document, error) {
318+
func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []string, challenge, rpId string, cookies []*http.Cookie) (*goquery.Document, error) {
285319
webauthnForm := url.Values{}
286-
287320
var assertion *okta.SignedAssertion
288321
var pickedCredentialID string
289322
for i, credentialID := range credentialIDs {
@@ -328,14 +361,14 @@ func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []str
328361
webauthnForm.Set("credentialId", pickedCredentialID)
329362
webauthnForm.Set("userHandle", "")
330363
webauthnForm.Set("error", "")
331-
332364
req, err := http.NewRequest("POST", webauthnSubmitURL, strings.NewReader(webauthnForm.Encode()))
333365
if err != nil {
334366
return nil, errors.Wrap(err, "error building MFA request")
335367
}
336-
337368
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
338-
369+
for _, cookie := range cookies {
370+
req.AddCookie(cookie)
371+
}
339372
res, err := kc.client.Do(req)
340373
if err != nil {
341374
return nil, errors.Wrap(err, "error retrieving content")
@@ -345,7 +378,6 @@ func (kc *Client) postWebauthnForm(webauthnSubmitURL string, credentialIDs []str
345378
if err != nil {
346379
return nil, errors.Wrap(err, "error reading webauthn form response")
347380
}
348-
349381
return doc, nil
350382
}
351383

@@ -368,7 +400,6 @@ func extractSubmitURL(doc *goquery.Document) (string, error) {
368400
}
369401
submitURL = action
370402
})
371-
372403
if submitURL == "" {
373404
return "", fmt.Errorf("unable to locate form submit URL")
374405
}
@@ -379,7 +410,6 @@ func extractSubmitURL(doc *goquery.Document) (string, error) {
379410
func extractSamlResponse(doc *goquery.Document) (string, error) {
380411
var samlAssertion = ""
381412
var err = fmt.Errorf("unable to locate saml response field")
382-
383413
doc.Find("input").Each(func(i int, s *goquery.Selection) {
384414
name, ok := s.Attr("name")
385415
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)