Commit 53e98a2c authored by ale's avatar ale

Move config-related code to a separate file

parent 672c7808
......@@ -4,16 +4,12 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"github.com/pquerna/otp/totp"
"github.com/prometheus/client_golang/prometheus"
"github.com/tstranex/u2f"
"gopkg.in/yaml.v2"
"git.autistici.org/ai3/go-common/clientutil"
"git.autistici.org/ai3/go-common/pwhash"
"git.autistici.org/id/auth"
)
......@@ -63,12 +59,16 @@ func (u *User) UserInfo() *auth.UserInfo {
}
}
// UserBackend provides us with per-service user information.
type UserBackend interface {
// userBackend provides us with per-service user information.
type userBackend interface {
Close()
NewServiceBackend(*BackendSpec) (serviceBackend, error)
}
type serviceBackend interface {
GetUser(context.Context, string) (*User, bool)
}
// OTPShortTermStorage stores short-term otp tokens for replay
// protection purposes.
type OTPShortTermStorage interface {
......@@ -87,26 +87,6 @@ type requestFilter interface {
Filter(*User, *auth.Request, *auth.Response) *auth.Response
}
// BackendSpec specifies backend-specific configuration for a service.
type BackendSpec struct {
BackendName string `yaml:"backend"`
Params yaml.MapSlice `yaml:"params"`
StaticGroups []string `yaml:"static_groups"`
}
// ServiceConfig defines the authentication backends for a service.
type ServiceConfig struct {
BackendSpecs []*BackendSpec `yaml:"backends"`
ChallengeResponse bool `yaml:"challenge_response"`
Enforce2FA bool `yaml:"enforce_2fa"`
EnableDeviceTracking bool `yaml:"enable_device_tracking"`
Ratelimits []string `yaml:"rate_limits"`
}
type serviceBackend interface {
GetUser(context.Context, string) (*User, bool)
}
// Authenticate users for a specific service.
type service struct {
rl []*authRatelimiter
......@@ -158,38 +138,11 @@ func (b *staticGroupsServiceBackend) GetUser(ctx context.Context, name string) (
return user, ok
}
// Config for the authentication server.
type Config struct {
// Service-specific configuration.
Services map[string]*ServiceConfig `yaml:"services"`
// If set, load more service definitions from *.yml files in this directory.
ServicesDir string `yaml:"services_dir"`
// Enabled backends.
Backends map[string]yaml.MapSlice `yaml:"enabled_backends"`
// If set, load more backend definitions from *.yml files in this directory.
BackendsDir string `yaml:"backends_dir"`
// Named rate limiter configurations.
RateLimiters map[string]*authRatelimiterConfig `yaml:"rate_limits"`
// Configuration for the user-meta-server backend.
UserMetaDBConfig *clientutil.BackendConfig `yaml:"user_meta_server"`
// Memcache servers used for short-term storage.
MemcacheServers []string `yaml:"memcache_servers"`
// Full path to the configuration file, used to resolve relative paths.
path string
}
// Create backend implementations from the given Config.
func createBackends(config *Config) (map[string]UserBackend, error) {
backends := make(map[string]UserBackend)
func createBackends(config *Config) (map[string]userBackend, error) {
backends := make(map[string]userBackend)
for name, params := range config.Backends {
var b UserBackend
var b userBackend
var err error
switch name {
case "file":
......@@ -208,7 +161,7 @@ func createBackends(config *Config) (map[string]UserBackend, error) {
}
// Create a service backend from the given BackendSpec.
func createServiceBackend(spec *BackendSpec, backends map[string]UserBackend) (serviceBackend, error) {
func createServiceBackend(spec *BackendSpec, backends map[string]userBackend) (serviceBackend, error) {
b, ok := backends[spec.BackendName]
if !ok {
return nil, fmt.Errorf("unknown backend %s", spec.BackendName)
......@@ -224,7 +177,7 @@ func createServiceBackend(spec *BackendSpec, backends map[string]UserBackend) (s
}
// Create a service from the given ServiceConfig.
func createService(config *Config, sc *ServiceConfig, backends map[string]UserBackend, ratelimiters map[string]*authRatelimiter, blacklists map[string]*authBlacklist) (*service, error) {
func createService(config *Config, sc *ServiceConfig, backends map[string]userBackend, ratelimiters map[string]*authRatelimiter, blacklists map[string]*authBlacklist) (*service, error) {
s := &service{
enforce2FA: sc.Enforce2FA,
challengeResponse: sc.ChallengeResponse,
......@@ -295,96 +248,10 @@ func createRatelimiters(config *Config) (map[string]*authRatelimiter, map[string
return ratelimiters, blacklists, nil
}
// Load and unmarshal a YAML file.
func loadYAML(path string, obj interface{}) error {
data, err := ioutil.ReadFile(path) // #nosec
if err != nil {
return err
}
return yaml.Unmarshal(data, obj)
}
// Load a standalone service configuration: a YAML-encoded file that
// may contain one or more ServiceConfig definitions.
func loadStandaloneServiceConfig(path string) (map[string]*ServiceConfig, error) {
var out map[string]*ServiceConfig
if err := loadYAML(path, &out); err != nil {
return nil, err
}
return out, nil
}
// Load a standalone service configuration: a YAML-encoded file that
// may contain one or more ServiceConfig definitions.
func loadStandaloneBackendConfig(path string) (map[string]yaml.MapSlice, error) {
var out map[string]yaml.MapSlice
if err := loadYAML(path, &out); err != nil {
return nil, err
}
return out, nil
}
// Apply a function to all files matching a glob pattern (errors are ignored).
func forAllFiles(pattern string, f func(string)) {
files, err := filepath.Glob(pattern)
if err != nil {
return
}
for _, file := range files {
f(file)
}
}
// LoadConfig loads the configuration from a YAML-encoded file.
func LoadConfig(path string) (*Config, error) {
config := Config{
path: path,
// The 'file' backend is always enabled.
Backends: map[string]yaml.MapSlice{
"file": nil,
},
}
if err := loadYAML(path, &config); err != nil {
return nil, err
}
// Load backend definitions from BackendsDir.
if config.BackendsDir != "" {
forAllFiles(filepath.Join(config.BackendsDir, "*.yml"), func(f string) {
tmp, ferr := loadStandaloneBackendConfig(f)
if ferr != nil {
log.Printf("configuration error: %s: %v", f, ferr)
return
}
for name, b := range tmp {
config.Backends[name] = b
}
})
}
// Load service definitions from a directory if necessary, and
// merge them into config.Services.
if config.ServicesDir != "" {
forAllFiles(filepath.Join(config.ServicesDir, "*.yml"), func(f string) {
tmp, ferr := loadStandaloneServiceConfig(f)
if ferr != nil {
log.Printf("configuration error: %s: %v", f, ferr)
return
}
for name, svc := range tmp {
config.Services[name] = svc
}
})
}
return &config, nil
}
// Server is the main authentication server object.
type Server struct {
services map[string]*service
backends map[string]UserBackend
backends map[string]userBackend
u2fShortTerm U2FShortTermStorage
otpShortTerm OTPShortTermStorage
}
......@@ -650,15 +517,6 @@ func (s *Server) checkOTP(user *User, otp, secret string) bool {
return totp.Validate(otp, secret)
}
// Unmarshal a partially-parsed yaml.MapSlice.
func unmarshalMapSlice(raw yaml.MapSlice, obj interface{}) error {
b, err := yaml.Marshal(raw)
if err != nil {
return err
}
return yaml.Unmarshal(b, obj)
}
func newError() *auth.Response {
return &auth.Response{Status: auth.StatusError}
}
......
package server
import (
"io/ioutil"
"log"
"path/filepath"
"git.autistici.org/ai3/go-common/clientutil"
"gopkg.in/yaml.v2"
)
// BackendSpec specifies backend-specific configuration for a service.
type BackendSpec struct {
BackendName string `yaml:"backend"`
Params yaml.MapSlice `yaml:"params"`
StaticGroups []string `yaml:"static_groups"`
}
// ServiceConfig defines the authentication backends for a service.
type ServiceConfig struct {
BackendSpecs []*BackendSpec `yaml:"backends"`
ChallengeResponse bool `yaml:"challenge_response"`
Enforce2FA bool `yaml:"enforce_2fa"`
EnableDeviceTracking bool `yaml:"enable_device_tracking"`
Ratelimits []string `yaml:"rate_limits"`
}
// Config for the authentication server.
type Config struct {
// Service-specific configuration.
Services map[string]*ServiceConfig `yaml:"services"`
// If set, load more service definitions from *.yml files in this directory.
ServicesDir string `yaml:"services_dir"`
// Enabled backends.
Backends map[string]yaml.MapSlice `yaml:"enabled_backends"`
// If set, load more backend definitions from *.yml files in this directory.
BackendsDir string `yaml:"backends_dir"`
// Named rate limiter configurations.
RateLimiters map[string]*authRatelimiterConfig `yaml:"rate_limits"`
// Configuration for the user-meta-server backend.
UserMetaDBConfig *clientutil.BackendConfig `yaml:"user_meta_server"`
// Memcache servers used for short-term storage.
MemcacheServers []string `yaml:"memcache_servers"`
// Full path to the configuration file, used to resolve relative paths.
path string
}
// Load and unmarshal a YAML file.
func loadYAML(path string, obj interface{}) error {
data, err := ioutil.ReadFile(path) // #nosec
if err != nil {
return err
}
return yaml.Unmarshal(data, obj)
}
// Load a standalone service configuration: a YAML-encoded file that
// may contain one or more ServiceConfig definitions.
func loadStandaloneServiceConfig(path string) (map[string]*ServiceConfig, error) {
var out map[string]*ServiceConfig
if err := loadYAML(path, &out); err != nil {
return nil, err
}
return out, nil
}
// Load a standalone service configuration: a YAML-encoded file that
// may contain one or more ServiceConfig definitions.
func loadStandaloneBackendConfig(path string) (map[string]yaml.MapSlice, error) {
var out map[string]yaml.MapSlice
if err := loadYAML(path, &out); err != nil {
return nil, err
}
return out, nil
}
// Apply a function to all files matching a glob pattern (errors are ignored).
func forAllFiles(pattern string, f func(string)) {
files, err := filepath.Glob(pattern)
if err != nil {
return
}
for _, file := range files {
f(file)
}
}
// LoadConfig loads the configuration from a YAML-encoded file.
func LoadConfig(path string) (*Config, error) {
config := Config{
path: path,
// The 'file' backend is always enabled.
Backends: map[string]yaml.MapSlice{
"file": nil,
},
}
if err := loadYAML(path, &config); err != nil {
return nil, err
}
// Load backend definitions from BackendsDir.
if config.BackendsDir != "" {
forAllFiles(filepath.Join(config.BackendsDir, "*.yml"), func(f string) {
tmp, ferr := loadStandaloneBackendConfig(f)
if ferr != nil {
log.Printf("configuration error: %s: %v", f, ferr)
return
}
for name, b := range tmp {
config.Backends[name] = b
}
})
}
// Load service definitions from a directory if necessary, and
// merge them into config.Services.
if config.ServicesDir != "" {
forAllFiles(filepath.Join(config.ServicesDir, "*.yml"), func(f string) {
tmp, ferr := loadStandaloneServiceConfig(f)
if ferr != nil {
log.Printf("configuration error: %s: %v", f, ferr)
return
}
for name, svc := range tmp {
config.Services[name] = svc
}
})
}
return &config, nil
}
// Unmarshal a partially-parsed yaml.MapSlice.
func unmarshalMapSlice(raw yaml.MapSlice, obj interface{}) error {
b, err := yaml.Marshal(raw)
if err != nil {
return err
}
return yaml.Unmarshal(b, obj)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment