118 lines
3.1 KiB
Go
118 lines
3.1 KiB
Go
package netauth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/foxcpp/maddy/framework/config"
|
|
"github.com/foxcpp/maddy/framework/log"
|
|
"github.com/foxcpp/maddy/framework/module"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/netauth/netauth/pkg/netauth"
|
|
)
|
|
|
|
const modName = "auth.netauth"
|
|
|
|
func init() {
|
|
var _ module.PlainAuth = &Auth{}
|
|
var _ module.Table = &Auth{}
|
|
module.Register(modName, New)
|
|
module.Register("table.netauth", New)
|
|
}
|
|
|
|
// Auth binds all methods related to the NetAuth client library.
|
|
type Auth struct {
|
|
instName string
|
|
mustGroup string
|
|
|
|
nacl *netauth.Client
|
|
|
|
log log.Logger
|
|
}
|
|
|
|
// New creates a new instance of the NetAuth module.
|
|
func New(modName, instName string, _, inlineArgs []string) (module.Module, error) {
|
|
return &Auth{
|
|
instName: instName,
|
|
log: log.Logger{Name: modName},
|
|
}, nil
|
|
}
|
|
|
|
// Init performs deferred initialization actions.
|
|
func (a *Auth) Init(cfg *config.Map) error {
|
|
l := hclog.New(&hclog.LoggerOptions{Output: a.log})
|
|
n, err := netauth.NewWithLog(l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.nacl = n
|
|
a.nacl.SetServiceName("maddy")
|
|
cfg.String("require_group", false, false, "", &a.mustGroup)
|
|
cfg.Bool("debug", true, false, &a.log.Debug)
|
|
if _, err := cfg.Process(); err != nil {
|
|
return err
|
|
}
|
|
|
|
a.log.Debugln("Debug logging enabled")
|
|
a.log.Debugf("mustGroups status: %s", a.mustGroup)
|
|
return nil
|
|
}
|
|
|
|
// Name returns "auth.netauth" as the fixed module name.
|
|
func (a *Auth) Name() string {
|
|
return modName
|
|
}
|
|
|
|
// InstanceName returns the configured name for this instance of the
|
|
// plugin. Given the way that NetAuth works it doesn't really make
|
|
// sense to have more than one instance, but this is part of the API.
|
|
func (a *Auth) InstanceName() string {
|
|
return a.instName
|
|
}
|
|
|
|
// Lookup requests the entity from the remote NetAuth server,
|
|
// potentially returning that the user does not exist at all.
|
|
func (a *Auth) Lookup(ctx context.Context, username string) (string, bool, error) {
|
|
e, err := a.nacl.EntityInfo(ctx, username)
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("%s: search: %w", modName, err)
|
|
}
|
|
|
|
if a.mustGroup != "" {
|
|
if err := a.checkMustGroup(username); err != nil {
|
|
return "", false, err
|
|
}
|
|
}
|
|
return e.GetID(), true, nil
|
|
}
|
|
|
|
// AuthPlain attempts straightforward authentication of the entity on
|
|
// the remote NetAuth server.
|
|
func (a *Auth) AuthPlain(username, password string) error {
|
|
a.log.Debugf("attempting to auth user: %s", username)
|
|
if err := a.nacl.AuthEntity(context.Background(), username, password); err != nil {
|
|
return module.ErrUnknownCredentials
|
|
}
|
|
a.log.Debugln("netauth returns successful auth")
|
|
if a.mustGroup != "" {
|
|
if err := a.checkMustGroup(username); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Auth) checkMustGroup(username string) error {
|
|
a.log.Debugf("Performing require_group check: must=%s", a.mustGroup)
|
|
groups, err := a.nacl.EntityGroups(context.Background(), username)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: groups: %w", modName, err)
|
|
}
|
|
for _, g := range groups {
|
|
if g.GetName() == a.mustGroup {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("%s: missing required group (%s not in %s)", modName, username, a.mustGroup)
|
|
}
|