144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
// Package duo provides a Duo MFA handler to authenticate users
|
|
// with Duo. This handler is registered as the "duo" type in
|
|
// mfa_config.
|
|
package duo
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/duosecurity/duo_api_golang/authapi"
|
|
"github.com/hashicorp/vault/logical"
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
)
|
|
|
|
// DuoPaths returns path functions to configure Duo.
|
|
func DuoPaths() []*framework.Path {
|
|
return []*framework.Path{
|
|
pathDuoConfig(),
|
|
pathDuoAccess(),
|
|
}
|
|
}
|
|
|
|
// DuoRootPaths returns the paths that are used to configure Duo.
|
|
func DuoRootPaths() []string {
|
|
return []string {
|
|
"duo/access",
|
|
"duo/config",
|
|
}
|
|
}
|
|
|
|
// DuoHandler interacts with the Duo Auth API to authenticate a user
|
|
// login request. If successful, the original response from the login
|
|
// backend is returned.
|
|
func DuoHandler(req *logical.Request, d *framework.FieldData, resp *logical.Response) (
|
|
*logical.Response, error) {
|
|
duoConfig, err := GetDuoConfig(req)
|
|
if err != nil || duoConfig == nil {
|
|
return logical.ErrorResponse("Could not load Duo configuration"), nil
|
|
}
|
|
|
|
duoAuthClient, err := GetDuoAuthClient(req, duoConfig)
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
username, ok := resp.Auth.Metadata["username"]
|
|
if !ok {
|
|
return logical.ErrorResponse("Could not read username for MFA"), nil
|
|
}
|
|
|
|
var request *duoAuthRequest = &duoAuthRequest{}
|
|
request.successResp = resp
|
|
request.username = username
|
|
request.method = d.Get("method").(string)
|
|
request.passcode = d.Get("passcode").(string)
|
|
request.ipAddr = req.Connection.RemoteAddr
|
|
|
|
return duoHandler(duoConfig, duoAuthClient, request)
|
|
}
|
|
|
|
type duoAuthRequest struct {
|
|
successResp *logical.Response
|
|
username string
|
|
method string
|
|
passcode string
|
|
ipAddr string
|
|
}
|
|
|
|
func duoHandler(duoConfig *DuoConfig, duoAuthClient AuthClient, request *duoAuthRequest) (
|
|
*logical.Response, error) {
|
|
|
|
duoUser := fmt.Sprintf(duoConfig.UsernameFormat, request.username)
|
|
|
|
preauth, err := duoAuthClient.Preauth(
|
|
authapi.PreauthUsername(duoUser),
|
|
authapi.PreauthIpAddr(request.ipAddr),
|
|
)
|
|
|
|
if err != nil || preauth == nil {
|
|
return logical.ErrorResponse("Could not call Duo preauth"), nil
|
|
}
|
|
|
|
if preauth.StatResult.Stat != "OK" {
|
|
errorMsg := "Could not look up Duo user information"
|
|
if preauth.StatResult.Message != nil {
|
|
errorMsg = errorMsg + ": " + *preauth.StatResult.Message
|
|
}
|
|
if preauth.StatResult.Message_Detail != nil {
|
|
errorMsg = errorMsg + " (" + *preauth.StatResult.Message_Detail + ")"
|
|
}
|
|
return logical.ErrorResponse(errorMsg), nil
|
|
}
|
|
|
|
switch preauth.Response.Result {
|
|
case "allow":
|
|
return request.successResp, err
|
|
case "deny":
|
|
return logical.ErrorResponse(preauth.Response.Status_Msg), nil
|
|
case "enroll":
|
|
return logical.ErrorResponse(fmt.Sprintf("%s (%s)",
|
|
preauth.Response.Status_Msg,
|
|
preauth.Response.Enroll_Portal_Url)), nil
|
|
case "auth":
|
|
break
|
|
default:
|
|
return logical.ErrorResponse(fmt.Sprintf("Invalid Duo preauth response: %s",
|
|
preauth.Response.Result)), nil
|
|
}
|
|
|
|
options := []func(*url.Values){authapi.AuthUsername(duoUser)}
|
|
if request.method == "" {
|
|
request.method = "auto"
|
|
}
|
|
if request.passcode != "" {
|
|
request.method = "passcode"
|
|
options = append(options, authapi.AuthPasscode(request.passcode))
|
|
} else {
|
|
options = append(options, authapi.AuthDevice("auto"))
|
|
}
|
|
|
|
result, err := duoAuthClient.Auth(request.method, options...)
|
|
|
|
if err != nil || result == nil {
|
|
return logical.ErrorResponse("Could not call Duo auth"), nil
|
|
}
|
|
|
|
if result.StatResult.Stat != "OK" {
|
|
errorMsg := "Could not authenticate Duo user"
|
|
if result.StatResult.Message != nil {
|
|
errorMsg = errorMsg + ": " + *result.StatResult.Message
|
|
}
|
|
if result.StatResult.Message_Detail != nil {
|
|
errorMsg = errorMsg + " (" + *result.StatResult.Message_Detail + ")"
|
|
}
|
|
return logical.ErrorResponse(errorMsg), nil
|
|
}
|
|
|
|
if result.Response.Result != "allow" {
|
|
return logical.ErrorResponse(result.Response.Status_Msg), nil
|
|
}
|
|
|
|
return request.successResp, nil
|
|
}
|