in pkg/provider/akamai/akamai.go [256:674]
func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetails, xsrfToken string) error {
/* Get supported MFA for this login */
mfaConfigURL := fmt.Sprintf("https://%s/api/v1/config/mfa", akamaiOrgHost)
mfaConfigReq, err := http.NewRequest("GET", mfaConfigURL, nil)
if err != nil {
return errors.Wrap(err, "error building mfa config request")
}
mfaConfigReq.Header.Add("Accept", "application/json")
mfaConfigReq.Header.Add("xsrf", string(xsrfToken))
res, err := oc.client.Do(mfaConfigReq)
if err != nil {
return errors.Wrap(err, "error mfa config request to EAA ")
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving mfa config request")
}
mfaConfigData := gjson.GetBytes(body, "mfa.config.options")
if mfaConfigData.Index == 0 {
log.Println("Mfa Config option not found")
return errors.Wrap(err, "Mfa not configured ")
}
/* Mfa config data present or not check otherwise return directly */
mfaOption := 0
var mfaOptions []MfaUserOption
for _, name := range mfaConfigData.Array() {
identifier := name.String()
if val, ok := supportedMfaOptions[identifier]; ok {
mfaOptions = append(mfaOptions, val)
}
}
/* Get MFA token settings from IDP */
mfaSettingURL := fmt.Sprintf("https://%s/api/v1/mfa/token/settings", akamaiOrgHost)
mfaSettingReq, err := http.NewRequest("GET", mfaSettingURL, nil)
if err != nil {
return errors.Wrap(err, "error building mfa setting request")
}
mfaSettingReq.Header.Add("Accept", "application/json")
mfaSettingReq.Header.Add("xsrf", string(xsrfToken))
res, err = oc.client.Do(mfaSettingReq)
if err != nil {
return errors.Wrap(err, "error mfa setting request to EAA ")
}
mfaSettingData, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving body from response")
}
var mfaDisplayOptions []string
var mfaUserOption string
var mfaConfiguredSupported int
if oc.mfa != "Auto" {
mfaUserOption = strings.ToLower(oc.mfa)
for _, val := range mfaOptions {
if mfaUserOption == val.UserMfaOption {
mfaDisplayOptions = nil
mfaConfiguredSupported = 1
break
}
mfaDisplayOptions = append(mfaDisplayOptions, val.UserDisplayString)
}
mfaDisplayNum := len(mfaDisplayOptions)
if mfaDisplayNum > 1 {
mfaOption = prompter.Choose("Select which MFA option to use", mfaDisplayOptions)
mfaUserOption = mfaOptions[mfaOption].UserMfaOption
} else if mfaDisplayNum == 1 {
mfaUserOption = mfaOptions[1].UserMfaOption
} else if mfaDisplayNum == 0 && mfaConfiguredSupported != 1 {
return errors.New("unsupported mfa provider")
}
} else {
mfaUserOption = gjson.GetBytes(mfaSettingData, "mfa.settings.preferred.option").String()
}
if _, ok := supportedMfaOptions[mfaUserOption]; !ok {
return errors.New("unsupported mfa provider")
}
/* specific mfa */
switch mfa := mfaUserOption; mfa {
case IdentifierSmsMfa, IdentifierEmailMfa, IdentifierTotpMfa:
/* 1. Get MFA UUID */
mfaUuid := fmt.Sprintf("mfa.settings.%s.0.uuid", mfa)
uuidMfa := gjson.GetBytes(mfaSettingData, mfaUuid).String()
/* 2.Push MFA */
var mfaApi = mfa
if mfa == IdentifierSmsMfa {
mfaApi = "phone"
}
var mfaResStatus string
if mfa == IdentifierSmsMfa || mfa == IdentifierEmailMfa {
mfaPushURL := fmt.Sprintf("https://%s/api/v1/mfa/user/%s/token/push", akamaiOrgHost, mfaApi)
mfaPushData := MfaPushRequest{Force: false, Uuid: uuidMfa}
mfaPushBody := new(bytes.Buffer)
err = json.NewEncoder(mfaPushBody).Encode(mfaPushData)
if err != nil {
return errors.Wrap(err, "error encoding mfa push data ")
}
mfaPushReq, err := http.NewRequest("POST", mfaPushURL, mfaPushBody)
if err != nil {
return errors.Wrap(err, "error building mfa push request")
}
mfaPushReq.Header.Add("Content-Type", "application/json")
mfaPushReq.Header.Add("Accept", "application/json")
mfaPushReq.Header.Add("xsrf", string(xsrfToken))
res, err = oc.client.Do(mfaPushReq)
if err != nil {
return errors.Wrap(err, "error while sending MFA push code ")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving MFA push response ")
}
mfaResStatus := gjson.GetBytes(body, "status").String()
if mfaResStatus != "200" {
return errors.Wrap(err, "Unable to send mfa token")
}
}
/* 3. Verify MFA */
verifyCode := prompter.StringRequired("Enter MFA verification code")
mfaVerifyURL := fmt.Sprintf("https://%s/api/v1/mfa/user/%s/token/verify", akamaiOrgHost, mfaApi)
mfaVerifyData := MfaTokenVerify{Category: mfa, Token: verifyCode, Uuid: uuidMfa}
mfaVerifyBody := new(bytes.Buffer)
err = json.NewEncoder(mfaVerifyBody).Encode(mfaVerifyData)
if err != nil {
return errors.Wrap(err, "error encoding mfa verify req")
}
mfaVerifyReq, err := http.NewRequest("POST", mfaVerifyURL, mfaVerifyBody)
if err != nil {
return errors.Wrap(err, "error creating mfa verification request")
}
mfaVerifyReq.Header.Add("Content-Type", "application/json")
mfaVerifyReq.Header.Add("Accept", "application/json")
mfaVerifyReq.Header.Add("xsrf", string(xsrfToken))
res, err = oc.client.Do(mfaVerifyReq)
if err != nil {
return errors.Wrap(err, "error verifying mfa to EAA ")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving mfa verify response")
}
mfaResStatus = gjson.GetBytes(body, "status").String()
if mfaResStatus != "200" {
return errors.Wrap(err, "Unable to verify mfa token")
}
return nil
case IdentifierDuoMfa:
duoSettings := fmt.Sprintf("mfa.settings.%s.0", mfa)
duoHost := gjson.GetBytes(mfaSettingData, duoSettings).Get("duo_host").String()
duoSignature := gjson.GetBytes(mfaSettingData, duoSettings).Get("token").String()
duoSignatures := strings.Split(duoSignature, ":")
//duoSignatures[0] = TX
//duoSignatures[1] = APP
// initiate duo mfa to get sid
duoSubmitURL := fmt.Sprintf("https://%s/frame/web/v1/auth", duoHost)
duoForm := url.Values{}
duoForm.Add("parent", fmt.Sprintf("https://%s/#/token", akamaiOrgHost))
duoForm.Add("java_version", "")
duoForm.Add("java_version", "")
duoForm.Add("flash_version", "")
duoForm.Add("screen_resolution_width", "1440")
duoForm.Add("screen_resolution_height", "900")
duoForm.Add("color_depth", "24")
req, err := http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
if err != nil {
return errors.Wrap(err, "error building duo request")
}
q := req.URL.Query()
q.Add("tx", duoSignatures[0])
req.URL.RawQuery = q.Encode()
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err = oc.client.Do(req)
if err != nil {
return errors.Wrap(err, "error sending duo request")
}
//try to extract sid
doc, err := goquery.NewDocumentFromResponse(res)
if err != nil {
return errors.Wrap(err, "error parsing document from duo")
}
duoSID, ok := doc.Find("input[name=\"sid\"]").Attr("value")
if !ok {
return errors.Wrap(err, "unable to locate sid in duo response")
}
duoSID = html.UnescapeString(duoSID)
//prompt for mfa type
//only supporting push or passcode for now
var token string
var duoMfaOptions = []string{
"Duo Push",
"Passcode",
}
duoMfaOption := 0
if loginDetails.DuoMFAOption == "Duo Push" {
duoMfaOption = 0
} else if loginDetails.DuoMFAOption == "Passcode" {
duoMfaOption = 1
} else {
duoMfaOption = prompter.Choose("Select a DUO MFA Option", duoMfaOptions)
}
if duoMfaOptions[duoMfaOption] == "Passcode" {
//get users DUO MFA Token
token = prompter.StringRequired("Enter passcode")
}
// send mfa auth request
duoSubmitURL = fmt.Sprintf("https://%s/frame/prompt", duoHost)
duoForm = url.Values{}
duoForm.Add("sid", duoSID)
duoForm.Add("device", "phone1")
duoForm.Add("factor", duoMfaOptions[duoMfaOption])
duoForm.Add("out_of_date", "false")
if duoMfaOptions[duoMfaOption] == "Passcode" {
duoForm.Add("passcode", token)
}
req, err = http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
if err != nil {
return errors.Wrap(err, "error building duo prompt request")
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err = oc.client.Do(req)
if err != nil {
return errors.Wrap(err, "error retrieving duo prompt request")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving duo prompt response")
}
resp := string(body)
duoTxStat := gjson.Get(resp, "stat").String()
duoTxID := gjson.Get(resp, "response.txid").String()
if duoTxStat != "OK" {
return errors.Wrap(err, "error authenticating duo mfa device")
}
// get duo cookie
duoSubmitURL = fmt.Sprintf("https://%s/frame/status", duoHost)
duoForm = url.Values{}
duoForm.Add("sid", duoSID)
duoForm.Add("txid", duoTxID)
req, err = http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
if err != nil {
return errors.Wrap(err, "error building duo status request")
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err = oc.client.Do(req)
if err != nil {
return errors.Wrap(err, "error sending duo status request")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving duo status response")
}
resp = string(body)
duoTxResult := gjson.Get(resp, "response.result").String()
duoResultURL := gjson.Get(resp, "response.result_url").String()
log.Println(gjson.Get(resp, "response.status").String())
if duoTxResult != "SUCCESS" {
//poll as this is likely a push request
for {
time.Sleep(3 * time.Second)
req, err = http.NewRequest("POST", duoSubmitURL, strings.NewReader(duoForm.Encode()))
if err != nil {
return errors.Wrap(err, "error building authentication request")
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err = oc.client.Do(req)
if err != nil {
return errors.Wrap(err, "error retrieving verify response")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving body from response")
}
resp := string(body)
duoTxResult = gjson.Get(resp, "response.result").String()
duoResultURL = gjson.Get(resp, "response.result_url").String()
log.Println(gjson.Get(resp, "response.status").String())
if duoTxResult == "FAILURE" {
return errors.Wrap(err, "failed to authenticate device")
}
if duoTxResult == "SUCCESS" {
break
}
}
}
duoRequestURL := fmt.Sprintf("https://%s%s", duoHost, duoResultURL)
req, err = http.NewRequest("POST", duoRequestURL, strings.NewReader(duoForm.Encode()))
if err != nil {
return errors.Wrap(err, "error constructing request object to result url")
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err = oc.client.Do(req)
if err != nil {
return errors.Wrap(err, "error retrieving duo result response")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "duoResultSubmit: error retrieving body from response")
}
resp = string(body)
duoTxCookie := gjson.Get(resp, "response.cookie").String()
if duoTxCookie == "" {
return errors.Wrap(err, "duoResultSubmit: Unable to get response.cookie")
}
// callback to Akamai to verify
mfaVerifyURL := fmt.Sprintf("https://%s/api/v1/mfa/user/%s/token/verify", akamaiOrgHost, mfa)
mfaDuoSigResponse := fmt.Sprintf("%s:%s", duoTxCookie, duoSignatures[1])
mfaVerifyData := MfaTokenVerify{Category: mfa, Uuid: mfa,
DuoSigRequest: duoSignature, DuoSigResponse: mfaDuoSigResponse}
mfaVerifyBody := new(bytes.Buffer)
err = json.NewEncoder(mfaVerifyBody).Encode(mfaVerifyData)
if err != nil {
return errors.Wrap(err, "error encoding duo mfa verify req")
}
mfaVerifyReq, err := http.NewRequest("POST", mfaVerifyURL, mfaVerifyBody)
if err != nil {
return errors.Wrap(err, "error creating duo mfa verification request")
}
mfaVerifyReq.Header.Add("Content-Type", "application/json")
mfaVerifyReq.Header.Add("Accept", "application/json")
mfaVerifyReq.Header.Add("xsrf", string(xsrfToken))
res, err = oc.client.Do(mfaVerifyReq)
if err != nil {
return errors.Wrap(err, "error sending duo mfa request to EAA ")
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "error retrieving duo mfa response ")
}
mfaResStatus := gjson.GetBytes(body, "status").String()
if mfaResStatus != "200" {
return errors.Wrap(err, "Unable to verify mfa token")
}
return nil
}
return errors.New("no mfa options provided")
}