mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-20 12:51:17 +00:00
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
f1fe845bc8
commit
20bb1b4745
4 changed files with 117 additions and 65 deletions
11
config.yaml
11
config.yaml
|
@ -44,6 +44,7 @@ mfa:
|
|||
secret_key: "foobar"
|
||||
|
||||
duo:
|
||||
# Get your ikey / skey / host from https://duo.com/docs/duoweb#first-steps
|
||||
ikey: "IKEY"
|
||||
skey: "SKEY"
|
||||
host: "HOST"
|
||||
|
@ -103,9 +104,15 @@ providers:
|
|||
mfa:
|
||||
luzifer:
|
||||
- provider: duo
|
||||
- provider: google
|
||||
|
||||
- provider: totp
|
||||
attributes:
|
||||
secret: MZXW6YTBOIFA
|
||||
secret: MZXW6YTBOIFA # required
|
||||
period: 30 # optional, defaults to 30 (Google Authenticator)
|
||||
skew: 1 # optional, defaults to 1 (Google Authenticator)
|
||||
digits: 8 # optional, defaults to 6 (Google Authenticator)
|
||||
algorithm: sha1 # optional (sha1, sha256, sha512), defaults to sha1 (Google Authenticator)
|
||||
|
||||
- provider: yubikey
|
||||
attributes:
|
||||
device: ccccccfcvuul
|
||||
|
|
10
mfa.go
10
mfa.go
|
@ -26,6 +26,16 @@ func newMFAConfig(provider string, attrs map[string]interface{}) mfaConfig {
|
|||
return mfaConfig{Provider: provider, Attributes: attrs}
|
||||
}
|
||||
|
||||
func (m mfaConfig) AttributeInt(key string) int {
|
||||
if v, ok := m.Attributes[key]; ok && v != "" {
|
||||
if sv, ok := v.(int); ok {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m mfaConfig) AttributeString(key string) string {
|
||||
if v, ok := m.Attributes[key]; ok {
|
||||
if sv, ok := v.(string); ok {
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMFAProvider(&mfaGoogle{})
|
||||
}
|
||||
|
||||
type mfaGoogle struct{}
|
||||
|
||||
// ProviderID needs to return an unique string to identify
|
||||
// this special MFA provider
|
||||
func (m mfaGoogle) ProviderID() (id string) {
|
||||
return "google"
|
||||
}
|
||||
|
||||
// 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 mfaGoogle) Configure(yamlSource []byte) (err error) { return nil }
|
||||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
func (m mfaGoogle) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
||||
// Look for mfaConfigs with own provider name
|
||||
for _, c := range mfaCfgs {
|
||||
if c.Provider != m.ProviderID() {
|
||||
continue
|
||||
}
|
||||
|
||||
token, err := m.exec(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Generating the MFA token failed")
|
||||
}
|
||||
|
||||
for key, values := range r.Form {
|
||||
if strings.HasSuffix(key, mfaLoginFieldName) && values[0] == token {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report this provider was not able to verify the MFA request
|
||||
return errNoValidUserFound
|
||||
}
|
||||
|
||||
func (m mfaGoogle) exec(c mfaConfig) (string, error) {
|
||||
secret := c.AttributeString("secret")
|
||||
|
||||
if n := len(secret) % 8; n != 0 {
|
||||
secret = secret + strings.Repeat("=", 8-n)
|
||||
}
|
||||
|
||||
return totp.GenerateCode(strings.ToUpper(secret), time.Now())
|
||||
}
|
98
mfa_totp.go
Normal file
98
mfa_totp.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMFAProvider(&mfaTOTP{})
|
||||
}
|
||||
|
||||
type mfaTOTP struct{}
|
||||
|
||||
// ProviderID needs to return an unique string to identify
|
||||
// this special MFA provider
|
||||
func (m mfaTOTP) ProviderID() (id string) {
|
||||
return "totp"
|
||||
}
|
||||
|
||||
// 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 mfaTOTP) Configure(yamlSource []byte) (err error) { return nil }
|
||||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
func (m mfaTOTP) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
||||
// Look for mfaConfigs with own provider name
|
||||
for _, c := range mfaCfgs {
|
||||
// Provider has been renamed, keep "google" for backwards compatibility
|
||||
if c.Provider != m.ProviderID() && c.Provider != "google" {
|
||||
continue
|
||||
}
|
||||
|
||||
token, err := m.exec(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Generating the MFA token failed")
|
||||
}
|
||||
|
||||
for key, values := range r.Form {
|
||||
if strings.HasSuffix(key, mfaLoginFieldName) && values[0] == token {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report this provider was not able to verify the MFA request
|
||||
return errNoValidUserFound
|
||||
}
|
||||
|
||||
func (m mfaTOTP) exec(c mfaConfig) (string, error) {
|
||||
secret := c.AttributeString("secret")
|
||||
|
||||
// By default use Google Authenticator compatible settings
|
||||
generatorOpts := totp.ValidateOpts{
|
||||
Period: 30,
|
||||
Skew: 1,
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
}
|
||||
|
||||
if period := c.AttributeInt("period"); period > 0 {
|
||||
generatorOpts.Period = uint(period)
|
||||
}
|
||||
|
||||
if skew := c.AttributeInt("skew"); skew > 0 {
|
||||
generatorOpts.Skew = uint(skew)
|
||||
}
|
||||
|
||||
if digits := c.AttributeInt("digits"); digits > 0 {
|
||||
generatorOpts.Digits = otp.Digits(digits)
|
||||
}
|
||||
|
||||
if algorithm := c.AttributeString("algorithm"); algorithm != "" {
|
||||
switch algorithm {
|
||||
case "sha1":
|
||||
generatorOpts.Algorithm = otp.AlgorithmSHA1
|
||||
case "sha256":
|
||||
generatorOpts.Algorithm = otp.AlgorithmSHA256
|
||||
case "sha512":
|
||||
generatorOpts.Algorithm = otp.AlgorithmSHA512
|
||||
default:
|
||||
return "", errors.Errorf("Unsupported algorithm %q", algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
if n := len(secret) % 8; n != 0 {
|
||||
secret = secret + strings.Repeat("=", 8-n)
|
||||
}
|
||||
|
||||
return totp.GenerateCodeCustom(strings.ToUpper(secret), time.Now(), generatorOpts)
|
||||
}
|
Loading…
Reference in a new issue