Newer
Older
"strings"
"golang.org/x/crypto/ssh"
model2 "github.com/oidc-mytoken/server/internal/model"
"github.com/oidc-mytoken/server/internal/utils/errorfmt"
"github.com/oidc-mytoken/server/pkg/oauth2x"
"github.com/oidc-mytoken/server/shared/context"
"github.com/oidc-mytoken/server/shared/utils"
"github.com/oidc-mytoken/server/shared/utils/fileutil"
"github.com/oidc-mytoken/server/shared/utils/issuerUtils"
Enabled: true, // The default is that TLS is enabled if cert and key are given, this is checked later;
// we must set true here, because otherwise we cannot distinct this from a false set by the user
Secure: true,
Limiter: limiterConf{
Enabled: true,
Max: 100,
Window: 300,
AlwaysAllow: []string{"127.0.0.1"},
},
Hosts: []string{"localhost"},
User: "mytoken",
DB: "mytoken",
ReconnectInterval: 60,
},
Signing: signingConf{
Alg: oidc.ES512,
RSAKeyLen: 2048,
},
Logging: loggingConf{
Access: LoggerConf{
Dir: "/var/log/mytoken",
StdErr: false,
},
Internal: internalLoggerConf{
LoggerConf: LoggerConf{
Dir: "/var/log/mytoken",
StdErr: false,
Level: "error",
},
Smart: smartLoggerConf{
Enabled: true,
Dir: "", // if empty equal to normal logging dir
},
ServiceDocumentation: "https://docs-sdm.scc.kit.edu/mytoken/",
Features: featuresConf{
EnabledOIDCFlows: []model.OIDCFlow{
model.OIDCFlowAuthorizationCode,
},
TokenRevocation: onlyEnable{true},
ShortTokens: shortTokenConfig{
Enabled: true,
Len: 64,
},
Polling: pollingConf{
Enabled: true,
PollingCodeExpiresAfter: 300,
PollingInterval: 5,
},
TokenInfo: tokeninfoConfig{
Introspect: onlyEnable{true},
History: onlyEnable{true},
Tree: onlyEnable{true},
List: onlyEnable{true},
},
WebInterface: onlyEnable{true},
SSH: sshConf{
Enabled: false,
},
},
ProviderByIssuer: make(map[string]*ProviderConf),
API: apiConf{
MinVersion: 0,
},
// Config holds the server configuration
type Config struct {
Host string // Extracted from the IssuerURL
GeoIPDBFile string `yaml:"geo_ip_db_file"`
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:"-"`
ServiceOperator ServiceOperatorConf `yaml:"service_operator"`
type apiConf struct {
MinVersion int `yaml:"min_supported_version"`
}
EnabledOIDCFlows []model.OIDCFlow `yaml:"enabled_oidc_flows"`
TokenRevocation onlyEnable `yaml:"token_revocation"`
ShortTokens shortTokenConfig `yaml:"short_tokens"`
TransferCodes onlyEnable `yaml:"transfer_codes"`
Polling pollingConf `yaml:"polling_codes"`
TokenRotation onlyEnable `yaml:"token_rotation"`
TokenInfo tokeninfoConfig `yaml:"tokeninfo"`
WebInterface onlyEnable `yaml:"web_interface"`
DisabledRestrictionKeys model2.RestrictionClaims `yaml:"unsupported_restrictions"`
SSH sshConf `yaml:"ssh"`
Enabled bool `yaml:"enabled"`
UseProxyProtocol bool `yaml:"use_proxy_protocol"`
KeyFiles []string `yaml:"keys"`
PrivateKeys []ssh.Signer `yaml:"-"`
}
type tokeninfoConfig struct {
Enabled bool `yaml:"-"`
Introspect onlyEnable `yaml:"introspect"`
History onlyEnable `yaml:"event_history"`
Tree onlyEnable `yaml:"subtoken_tree"`
List onlyEnable `yaml:"list_mytokens"`
}
type shortTokenConfig struct {
Enabled bool `yaml:"enabled"`
Len int `yaml:"len"`
}
type onlyEnable struct {
Enabled bool `yaml:"enabled"`
Access LoggerConf `yaml:"access"`
Internal internalLoggerConf `yaml:"internal"`
}
type internalLoggerConf struct {
LoggerConf `yaml:",inline"`
Smart smartLoggerConf `yaml:"smart"`
// LoggerConf holds configuration related to logging
Dir string `yaml:"dir"`
StdErr bool `yaml:"stderr"`
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
type smartLoggerConf struct {
Enabled bool `yaml:"enabled"`
Dir string `yaml:"dir"`
}
func checkLoggingDirExists(dir string) error {
if dir != "" && !fileutil.FileExists(dir) {
return errors.Errorf("logging directory '%s' does not exist", dir)
}
return nil
}
func (log *loggingConf) validate() error {
if err := checkLoggingDirExists(log.Access.Dir); err != nil {
return err
}
if err := checkLoggingDirExists(log.Internal.Dir); err != nil {
return err
}
if log.Internal.Smart.Enabled {
if log.Internal.Smart.Dir == "" {
log.Internal.Smart.Dir = log.Internal.Dir
}
if err := checkLoggingDirExists(log.Internal.Smart.Dir); err != nil {
return err
}
}
return nil
}
PollingCodeExpiresAfter int64 `yaml:"expires_after"`
PollingInterval int64 `yaml:"polling_interval"`
type DBConf struct {

Gabriel Zachmann
committed
Hosts []string `yaml:"hosts"`
User string `yaml:"user"`
Password string `yaml:"password"`
PasswordFile string `yaml:"password_file"`
DB string `yaml:"db"`
ReconnectInterval int64 `yaml:"try_reconnect_interval"`
EnableScheduledCleanup bool `yaml:"schedule_cleanup"`
Port int `yaml:"port"`
TLS tlsConf `yaml:"tls"`
Secure bool `yaml:"-"` // Secure indicates if the connection to the mytoken server is secure. This is
// independent of TLS, e.g. a Proxy can be used.
ProxyHeader string `yaml:"proxy_header"`
Limiter limiterConf `yaml:"request_limits"`
}
type limiterConf struct {
Enabled bool `yaml:"enabled"`
Max int `yaml:"max_requests"`
Window int `yaml:"window"`
AlwaysAllow []string `yaml:"always_allow"`
}
type tlsConf struct {
Enabled bool `yaml:"enabled"`
RedirectHTTP bool `yaml:"redirect_http"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
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"`
MytokensMaxLifetime int64 `yaml:"mytokens_max_lifetime"`
Endpoints *oauth2x.Endpoints `yaml:"-"`
Provider *oidc.Provider `yaml:"-"`
Name string `yaml:"name"`
AudienceRequestParameter string `yaml:"audience_request_parameter"`
// ServiceOperatorConf is type holding the configuration for the service operator of this mytoken instance
type ServiceOperatorConf struct {
Name string `yaml:"name"`
Homepage string `yaml:"homepage"`
Contact string `yaml:"mail_contact"`
Privacy string `yaml:"mail_privacy"`
}
// GetPassword returns the password for this database config. If necessary it reads it from the password file.
func (conf *DBConf) GetPassword() string {
if conf.Password != "" {
return conf.Password
}
if conf.PasswordFile == "" {
}
content, err := ioutil.ReadFile(conf.PasswordFile)
if err != nil {
log.WithError(err).Error()
return ""
}
conf.Password = strings.Split(string(content), "\n")[0]
return conf.Password
}
func (so *ServiceOperatorConf) validate() error {
if so.Name == "" {
return errors.New("invalid config: service_operator.name not set")
return errors.New("invalid config: service_operator.mail_contact not set")
}
if so.Privacy == "" {
so.Privacy = so.Contact
}
return nil
}
// Get returns the Config
func Get() *Config {
return errors.New("config not set")
if conf.IssuerURL == "" {
return errors.New("invalid config: issuer_url not set")
}
if strings.HasPrefix(conf.IssuerURL, "http://") {
conf.Server.Secure = false
}
u, err := url.Parse(conf.IssuerURL)
if err != nil {
return errors.Wrap(err, "invalid config: issuer_url not valid")
}
conf.Host = u.Hostname()
if conf.Server.TLS.Key != "" && conf.Server.TLS.Cert != "" {
conf.Server.Port = 443
} else {
conf.Server.TLS.Enabled = false
}
}
if err = conf.Logging.validate(); err != nil {
return err
}
if err = conf.ServiceOperator.validate(); err != nil {
return errors.New("invalid config: providers must have at least one entry")
}
for i, p := range conf.Providers {
if p.Issuer == "" {
return errors.Errorf("invalid config: provider.issuer not set (Index %d)", i)
return errors.Errorf("error '%s' for provider.issuer '%s' (Index %d)", err, p.Issuer, i)
// Endpoints only returns an error if it does discovery but this was already done in NewConfig, so we can ignore
// the error value
p.Endpoints, _ = oc.Endpoints()
p.Provider, err = oidc.NewProvider(context.Get(), p.Issuer)
return errors.Errorf("error '%s' for provider.issuer '%s' (Index %d)", err, p.Issuer, i)
return errors.Errorf("invalid config: provider.clientid not set (Index %d)", i)
return errors.Errorf("invalid config: provider.clientsecret not set (Index %d)", i)
return errors.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 p.AudienceRequestParameter == "" {
p.AudienceRequestParameter = "resource"
}
return errors.New("invalid config: signing keyfile not set")
return errors.New("invalid config: token signing alg not set")
model.OIDCFlowAuthorizationCode.AddToSliceIfNotFound(&conf.Features.EnabledOIDCFlows)
if conf.Features.SSH.Enabled {
if len(conf.Features.SSH.KeyFiles) == 0 {
return errors.New("invalid config: ssh feature enabled, but no ssh private key set")
}
for _, pkf := range conf.Features.SSH.KeyFiles {
pemBytes, err := ioutil.ReadFile(pkf)
if err != nil {
return errors.Wrap(err, "reading ssh private key")
}
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
return errors.Wrap(err, "parsing ssh private key")
}
conf.Features.SSH.PrivateKeys = append(conf.Features.SSH.PrivateKeys, signer)
}
}
if !conf.Features.TokenInfo.Introspect.Enabled && conf.Features.WebInterface.Enabled {
return errors.New("web interface requires tokeninfo.introspect to be enabled")
}
conf.Features.TokenInfo.Enabled = utils.OR(
conf.Features.TokenInfo.Introspect.Enabled,
conf.Features.TokenInfo.History.Enabled,
conf.Features.TokenInfo.Tree.Enabled,
conf.Features.TokenInfo.List.Enabled,
)
return nil
}
var possibleConfigLocations = []string{
"config",
"/etc/mytoken",
}
// Load reads the config file and populates the Config struct; then validates the Config
load()
if err := validate(); err != nil {
log.Fatalf("%s", errorfmt.Full(err))
}
}
func load() {
data, _ := fileutil.ReadConfigFile("config.yaml", possibleConfigLocations)
// 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()