mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-20 12:51:17 +00:00
Knut Ahlers
9e0907f494
Squashed commit of the following: commit f748cc99802dc2a1c2f1b805a280c56fc6cf4123 Author: Knut Ahlers <knut@ahlers.me> Date: Fri Dec 28 23:39:07 2018 +0100 Implement fetching remote IP from mutliple sources Signed-off-by: Knut Ahlers <knut@ahlers.me> commit416af9eed8
Author: Ben Edmunds <bensammy2@yahoo.co.uk> Date: Fri Dec 28 19:35:26 2018 +0000 Duo MFA, clean up & documentation commit0e511023f0
Author: Ben Edmunds <bensammy2@yahoo.co.uk> Date: Fri Dec 28 16:34:55 2018 +0000 Add support for Duo MFA closes #28 closes #24 Signed-off-by: Knut Ahlers <knut@ahlers.me>
131 lines
3.3 KiB
Go
131 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
duoapi "github.com/duosecurity/duo_api_golang"
|
|
"github.com/duosecurity/duo_api_golang/authapi"
|
|
"github.com/pkg/errors"
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
const (
|
|
mfaDuoResponseAllow = "allow"
|
|
mfaDuoRequestTimeout = 10 * time.Second
|
|
)
|
|
|
|
var mfaDuoTrustedIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"}
|
|
|
|
func init() {
|
|
registerMFAProvider(&mfaDuo{})
|
|
}
|
|
|
|
type mfaDuo struct {
|
|
IKey string `yaml:"ikey"`
|
|
SKey string `yaml:"skey"`
|
|
Host string `yaml:"host"`
|
|
UserAgent string `yaml:"user_agent"`
|
|
}
|
|
|
|
// ProviderID needs to return an unique string to identify
|
|
// this special MFA provider
|
|
func (m mfaDuo) ProviderID() (id string) { return "duo" }
|
|
|
|
// Configure loads the configuration for the Authenticator from the
|
|
// global config.yaml file which is passed as a byte-slice.
|
|
// If no configuration for the Authenticator is supplied the function
|
|
// needs to return the errProviderUnconfigured
|
|
func (m *mfaDuo) Configure(yamlSource []byte) (err error) {
|
|
envelope := struct {
|
|
MFA struct {
|
|
Duo *mfaDuo `yaml:"duo"`
|
|
} `yaml:"mfa"`
|
|
}{}
|
|
|
|
if err := yaml.Unmarshal(yamlSource, &envelope); err != nil {
|
|
return err
|
|
}
|
|
|
|
if envelope.MFA.Duo == nil {
|
|
return errProviderUnconfigured
|
|
}
|
|
|
|
m.IKey = envelope.MFA.Duo.IKey
|
|
m.SKey = envelope.MFA.Duo.SKey
|
|
m.Host = envelope.MFA.Duo.Host
|
|
m.UserAgent = envelope.MFA.Duo.UserAgent
|
|
return nil
|
|
}
|
|
|
|
// ValidateMFA takes the user from the login cookie and performs a
|
|
// validation against the provided MFA configuration for this user
|
|
func (m mfaDuo) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
|
var keyInput string
|
|
|
|
// Look for mfaConfigs with own provider name
|
|
for _, c := range mfaCfgs {
|
|
if c.Provider != m.ProviderID() {
|
|
continue
|
|
}
|
|
|
|
remoteIP, err := m.findIP(r)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Unable to determine remote IP")
|
|
}
|
|
|
|
duo := authapi.NewAuthApi(*duoapi.NewDuoApi(m.IKey, m.SKey, m.Host, m.UserAgent, duoapi.SetTimeout(mfaDuoRequestTimeout)))
|
|
|
|
for key, values := range r.Form {
|
|
if strings.HasSuffix(key, mfaLoginFieldName) && len(values[0]) > 0 {
|
|
keyInput = values[0]
|
|
}
|
|
}
|
|
|
|
// Check if MFA token provided and fallover to push if not supplied
|
|
var auth *authapi.AuthResult
|
|
|
|
if keyInput != "" {
|
|
if auth, err = duo.Auth("passcode", authapi.AuthUsername(user), authapi.AuthPasscode(keyInput), authapi.AuthIpAddr(remoteIP)); err != nil {
|
|
return errors.Wrap(err, "Unable to authenticate with Duo using 'passcode' method")
|
|
}
|
|
} else {
|
|
if auth, err = duo.Auth("auto", authapi.AuthUsername(user), authapi.AuthDevice("auto"), authapi.AuthIpAddr(remoteIP)); err != nil {
|
|
return errors.Wrap(err, "Unable to authenticate with Duo using 'auto' method")
|
|
}
|
|
}
|
|
|
|
if auth.Response.Result == mfaDuoResponseAllow {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Report this provider was not able to verify the MFA request
|
|
return errNoValidUserFound
|
|
}
|
|
|
|
func (m mfaDuo) findIP(r *http.Request) (string, error) {
|
|
for _, hdr := range mfaDuoTrustedIPHeaders {
|
|
if value := r.Header.Get(hdr); value != "" {
|
|
return m.parseIP(value)
|
|
}
|
|
}
|
|
|
|
return m.parseIP(r.RemoteAddr)
|
|
}
|
|
|
|
func (m mfaDuo) parseIP(s string) (string, error) {
|
|
ip, _, err := net.SplitHostPort(s)
|
|
if err == nil {
|
|
return ip, nil
|
|
}
|
|
|
|
ip2 := net.ParseIP(s)
|
|
if ip2 == nil {
|
|
return "", errors.New("invalid IP")
|
|
}
|
|
|
|
return ip2.String(), nil
|
|
}
|