config.go 3.83 KB
Newer Older
1 2 3 4 5
package server

import (
	"log"
	"path/filepath"
6
	"sort"
7 8 9 10

	"git.autistici.org/ai3/go-common/clientutil"
	"gopkg.in/yaml.v2"

ale's avatar
ale committed
11 12
	"git.autistici.org/id/auth/backend"
)
13 14 15

// ServiceConfig defines the authentication backends for a service.
type ServiceConfig struct {
ale's avatar
ale committed
16 17 18 19 20 21
	BackendSpecs             []*backend.Spec `yaml:"backends"`
	ChallengeResponse        bool            `yaml:"challenge_response"`
	Enforce2FA               bool            `yaml:"enforce_2fa"`
	EnableLastLoginReporting bool            `yaml:"enable_last_login_reporting"`
	EnableDeviceTracking     bool            `yaml:"enable_device_tracking"`
	Ratelimits               []string        `yaml:"rate_limits"`
22 23 24 25 26 27 28 29 30 31 32
}

// 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.
ale's avatar
ale committed
33
	Backends map[string]yaml.MapSlice `yaml:"backends"`
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

	// 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 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
ale's avatar
ale committed
55
	if err := backend.LoadYAML(path, &out); err != nil {
56 57 58 59 60 61 62 63 64
		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
ale's avatar
ale committed
65
	if err := backend.LoadYAML(path, &out); err != nil {
66 67 68 69 70
		return nil, err
	}
	return out, nil
}

71 72 73 74 75 76 77
// Sort a string slice.
type filesList []string

func (l filesList) Len() int           { return len(l) }
func (l filesList) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
func (l filesList) Less(i, j int) bool { return l[i] < l[j] }

78
// Apply a function to all files matching a glob pattern (errors are ignored).
79
// Files are visited in sorted order.
80 81 82 83 84
func forAllFiles(pattern string, f func(string)) {
	files, err := filepath.Glob(pattern)
	if err != nil {
		return
	}
85
	sort.Sort(filesList(files))
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
	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,
		},
	}
ale's avatar
ale committed
101
	if err := backend.LoadYAML(path, &config); err != nil {
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
		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
}