Newer
Older
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/zachmann/mytoken/internal/context"
"github.com/zachmann/mytoken/internal/model"
"github.com/zachmann/mytoken/internal/utils/fileutil"
"github.com/zachmann/mytoken/internal/utils/issuerUtils"
"github.com/zachmann/mytoken/pkg/oauth2x"
"gopkg.in/yaml.v3"
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
var defaultConfig = Config{
Server: serverConf{
Port: 443,
},
DB: dbConf{
Host: "localhost",
User: "mytoken",
Password: "mytoken",
DB: "mytoken",
},
Signing: signingConf{
Alg: oidc.ES512,
RSAKeyLen: 2048,
},
Logging: loggingConf{
Access: LoggerConf{
Dir: "/var/log/mytoken",
StdErr: false,
},
Internal: LoggerConf{
Dir: "/var/log/mytoken",
StdErr: false,
Level: "error",
},
},
ServiceDocumentation: "https://github.com/zachmann/mytoken",
Features: featuresConf{
EnabledOIDCFlows: []model.OIDCFlow{
model.OIDCFlowAuthorizationCode,
},
TokenRevocation: onlyEnable{true},
ShortTokens: onlyEnable{true},
TransferCodes: onlyEnable{true},
Polling: pollingConf{
Enabled: true,
PollingCodeExpiresAfter: 300,
PollingInterval: 5,
},
AccessTokenGrant: onlyEnable{true},
SignedJWTGrant: onlyEnable{true},
},
ProviderByIssuer: make(map[string]*ProviderConf),
}
// Config holds the server configuration
type Config struct {
IssuerURL string `yaml:"issuer"`
Server serverConf `yaml:"server"`
DB dbConf `yaml:"database"`
Signing signingConf `yaml:"signing"`
Logging loggingConf `yaml:"logging"`
ServiceDocumentation string `yaml:"service_documentation"`
Features featuresConf `yaml:"features"`
Providers []*ProviderConf `yaml:"providers"`
ProviderByIssuer map[string]*ProviderConf `yaml:"-"`
}
type featuresConf struct {
EnabledOIDCFlows []model.OIDCFlow `yaml:"enabled_oidc_flows"`
TokenRevocation onlyEnable `yaml:"token_revocation"`
ShortTokens onlyEnable `yaml:"short_tokens"`
TransferCodes onlyEnable `yaml:"transfer_codes"`
Polling pollingConf `yaml:"polling_codes"`
AccessTokenGrant onlyEnable `yaml:"access_token_grant"`
SignedJWTGrant onlyEnable `yaml:"signed_jwt_grant"`
}
type onlyEnable struct {
Enabled bool `yaml:"enabled"`
Access LoggerConf `yaml:"access"`
Internal LoggerConf `yaml:"internal"`
Dir string `yaml:"dir"`
StdErr bool `yaml:"stderr"`
PollingCodeExpiresAfter int64 `yaml:"expires_after"`
PollingInterval int64 `yaml:"polling_interval"`
}
type dbConf struct {
Host string `yaml:"host"`
User string `yaml:"user"`
Password string `yaml:"password"`
DB string `yaml:"db"`
}
type serverConf struct {
Hostname string `yaml:"hostname"`
Alg string `yaml:"alg"`
KeyFile string `yaml:"key_file"`
RSAKeyLen int `yaml:"rsa_key_len"`
// ProviderConf holds information about a provider
type ProviderConf struct {
Issuer string `yaml:"issuer"`
ClientID string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
Scopes []string `yaml:"scopes"`
Endpoints *oauth2x.Endpoints `yaml:"-"`
Provider *oidc.Provider `yaml:"-"`
}
var conf *Config
// Get returns the config
func Get() *Config {
return conf
}
return fmt.Errorf("config not set")
}
if conf.Server.Hostname == "" {
return fmt.Errorf("invalid config: server.hostname not set")
}
return fmt.Errorf("invalid config: providers must have at least one entry")
}
for i, p := range conf.Providers {
if p.Issuer == "" {
return fmt.Errorf("invalid config: provider.issuer not set (Index %d)", i)
}
p.Endpoints, err = oauth2x.NewConfig(context.Get(), p.Issuer).Endpoints()
if err != nil {
return fmt.Errorf("Error '%s' for provider.issuer '%s' (Index %d)", err, p.Issuer, i)
}
p.Provider, err = oidc.NewProvider(context.Get(), p.Issuer)
if err != nil {
return fmt.Errorf("Error '%s' for provider.issuer '%s' (Index %d)", err, p.Issuer, i)
}
if p.ClientID == "" {
return fmt.Errorf("invalid config: provider.clientid not set (Index %d)", i)
}
if p.ClientSecret == "" {
return fmt.Errorf("invalid config: provider.clientsecret not set (Index %d)", i)
}
return fmt.Errorf("invalid config: provider.scopes not set (Index %d)", i)
}
iss0, iss1 := issuerUtils.GetIssuerWithAndWithoutSlash(p.Issuer)
conf.ProviderByIssuer[iss0] = p
conf.ProviderByIssuer[iss1] = p
if conf.IssuerURL == "" {
return fmt.Errorf("invalid config: issuerurl not set")
}
return fmt.Errorf("invalid config: signingkeyfile not set")
}
return fmt.Errorf("invalid config: tokensigningalg not set")
}
model.OIDCFlowAuthorizationCode.AddToSliceIfNotFound(conf.Features.EnabledOIDCFlows)
if model.OIDCFlowIsInSlice(model.OIDCFlowDevice, conf.Features.EnabledOIDCFlows) && conf.Features.Polling.Enabled == false {
return fmt.Errorf("oidc flow device flow requires polling_codes to be enabled")
return nil
}
var possibleConfigLocations = []string{
"config",
"/etc/mytoken",
}
// readConfigFile checks if a file exists in one of the configuration
// directories and returns the content. If no file is found, mytoken exists.
func readConfigFile(filename string) []byte {
for _, dir := range possibleConfigLocations {
filep := filepath.Join(dir, filename)
if strings.HasPrefix(filep, "~") {
homeDir := os.Getenv("HOME")
filep = filepath.Join(homeDir, filep[1:])
}
log.WithField("filepath", filep).Debug("Looking for config file")
if fileutil.FileExists(filep) {
return fileutil.MustReadFile(filep)
}
}
log.WithField("filepath", filename).Fatal("Could not find config file in any of the possible directories")
return nil
}
// Load reads the config file and populates the config struct; then validates the config
func Load() {
load()
if err := validate(); err != nil {
}
}
func load() {
}
// LoadForSetup reads the config file and populates the config struct; it does not validate the config, since this is not required for setup
func LoadForSetup() {
load()