Commit ce17e1a0 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Validate configuration when starting server

parent d11d0139
......@@ -80,14 +80,26 @@ func (hauth *HmacAuthenticator) Authenticate(
func (hauth *HmacAuthenticator) Initialize(name string, requestor Requestor) error {
if requestor.AuthenticationKey == "" {
return errors.Errorf("Requestor %s had no authentication key")
return errors.Errorf("Requestor %s has no authentication key", name)
}
if bts, err := base64.StdEncoding.DecodeString(requestor.AuthenticationKey); err != nil {
return err
} else {
hauth.hmackeys[name] = bts
return nil
var err error
var bts []byte
// We accept any of the base64 encodings
encodings := []*base64.Encoding{base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding}
for _, encoding := range encodings {
err = nil
if bts, err = encoding.DecodeString(requestor.AuthenticationKey); err == nil {
break
}
}
if err != nil {
return errors.WrapPrefix(err, "Failed to base64 decode hmac key of requestor "+name, 0)
}
hauth.hmackeys[name] = bts
return nil
}
func (pkauth *PublicKeyAuthenticator) Authenticate(
......@@ -140,7 +152,7 @@ func (pskauth *PresharedKeyAuthenticator) Authenticate(
func (pskauth *PresharedKeyAuthenticator) Initialize(name string, requestor Requestor) error {
if requestor.AuthenticationKey == "" {
return errors.Errorf("Requestor %s had no authentication key")
return errors.Errorf("Requestor %s has no authentication key", name)
}
pskauth.presharedkeys[requestor.AuthenticationKey] = name
return nil
......
......@@ -23,7 +23,7 @@ type Configuration struct {
// server configuration before the server accepts it.
DisableRequestorAuthentication bool `json:"noauth" mapstructure:"noauth"`
// Address to listen at. May include port (e.g. 0.0.0.0:1234) but then Port must be 0.
// Address to listen at
ListenAddress string `json:"listenaddr" mapstructure:"listenaddr"`
// Port to listen at
Port int `json:"port" mapstructure:"port"`
......@@ -89,13 +89,6 @@ func (conf *Configuration) CanIssue(requestor string, creds []*irma.CredentialRe
return true, ""
}
func (conf *Configuration) listenAddress() string {
if conf.Port == 0 {
return conf.ListenAddress
}
return fmt.Sprintf("%s:%d", conf.ListenAddress, conf.Port)
}
// CanVerifyOrSign returns whether or not the specified requestor may use the selected attributes
// in any of the supported session types.
func (conf *Configuration) CanVerifyOrSign(requestor string, action irma.Action, disjunctions irma.AttributeDisjunctionList) (bool, string) {
......@@ -148,7 +141,7 @@ func (conf *Configuration) initialize() error {
for name, requestor := range conf.Requestors {
authenticator, ok := authenticators[requestor.AuthenticationMethod]
if !ok {
return errors.Errorf("Requestor %s has unsupported authentication type")
return errors.Errorf("Requestor %s has unsupported authentication type", name)
}
if err := authenticator.Initialize(name, requestor); err != nil {
return err
......@@ -156,6 +149,14 @@ func (conf *Configuration) initialize() error {
}
}
if conf.Port <= 0 || conf.Port > 65535 {
return errors.Errorf("Port must be between 1 and 65535 (was %d)", conf.Port)
}
if err := conf.validatePermissions(); err != nil {
return err
}
if conf.URL != "" {
if !strings.HasSuffix(conf.URL, "/") {
conf.URL = conf.URL + "/"
......@@ -169,6 +170,75 @@ func (conf *Configuration) initialize() error {
return nil
}
func (conf *Configuration) validatePermissions() error {
if conf.DisableRequestorAuthentication && len(conf.Requestors) != 0 {
return errors.New("Requestors must not be configured when requestor authentication is disabled")
}
errs := conf.validatePermissionSet("Global", conf.GlobalPermissions)
for name, requestor := range conf.Requestors {
errs = append(errs, conf.validatePermissionSet("Requestor "+name, requestor.Permissions)...)
}
if len(errs) != 0 {
return errors.New("Errors encountered in permissions:\n" + strings.Join(errs, "\n"))
}
return nil
}
func (conf *Configuration) validatePermissionSet(requestor string, requestorperms Permissions) []string {
var errs []string
perms := map[string][]string{
"issuing": requestorperms.Issuing,
"signing": requestorperms.Signing,
"disclosing": requestorperms.Disclosing,
}
permissionlength := map[string]int{"issuing": 3, "signing": 4, "disclosing": 4}
for typ, typeperms := range perms {
for _, permission := range typeperms {
parts := strings.Split(permission, ".")
if parts[len(parts)-1] == "*" {
if len(parts) > permissionlength[typ] {
errs = append(errs, fmt.Sprintf("%s %s permission '%s' should have at most %d parts", requestor, typ, permission, permissionlength[typ]))
}
} else {
if len(parts) != permissionlength[typ] {
errs = append(errs, fmt.Sprintf("%s %s permission '%s' should have %d parts", requestor, typ, permission, permissionlength[typ]))
}
}
if len(parts) > 0 && parts[0] != "*" {
if conf.IrmaConfiguration.SchemeManagers[irma.NewSchemeManagerIdentifier(parts[0])] == nil {
errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown scheme", requestor, typ, permission))
continue // no sense in checking if issuer, credtype or attr type are known; they won't be
}
}
if len(parts) > 1 && parts[1] != "*" {
id := irma.NewIssuerIdentifier(strings.Join(parts[:2], "."))
if conf.IrmaConfiguration.Issuers[id] == nil {
errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown issuer", requestor, typ, permission))
continue
}
}
if len(parts) > 2 && parts[2] != "*" {
id := irma.NewCredentialTypeIdentifier(strings.Join(parts[:3], "."))
if conf.IrmaConfiguration.CredentialTypes[id] == nil {
errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown credential type", requestor, typ, permission))
continue
}
}
if len(parts) > 3 && parts[3] != "*" {
id := irma.NewAttributeTypeIdentifier(strings.Join(parts[:4], "."))
if conf.IrmaConfiguration.AttributeTypes[id] == nil {
errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown attribute type", requestor, typ, permission))
continue
}
}
}
}
return errs
}
func (conf *Configuration) readPrivateKey() error {
if conf.JwtPrivateKey == "" {
return nil
......
......@@ -4,7 +4,6 @@ package main
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
......@@ -48,9 +47,11 @@ func main() {
}
func die(err *errors.Error) {
logger.Error(err.Error())
logger.Trace(string(err.Stack()))
os.Exit(1)
msg := err.Error()
if logger.IsLevelEnabled(logrus.TraceLevel) {
msg += "\nStack trace:\n" + string(err.Stack())
}
logger.Fatal(msg)
}
func setFlags(cmd *cobra.Command) error {
......@@ -80,8 +81,8 @@ func setFlags(cmd *cobra.Command) error {
flags.Bool("noauth", false, "Whether or not to authenticate requestors")
flags.String("requestors", "", "Requestor configuration (in JSON)")
flags.StringSlice("disclose", []string{"*"}, "Comma-separated list of attributes that all requestors may verify")
flags.StringSlice("sign", []string{"*"}, "Comma-separated list of attributes that all requestors may request in signatures")
flags.StringSlice("disclose", nil, "Comma-separated list of attributes that all requestors may verify")
flags.StringSlice("sign", nil, "Comma-separated list of attributes that all requestors may request in signatures")
flags.StringSlice("issue", nil, "Comma-separated list of attributes that all requestors may issue")
flags.CountP("verbose", "v", "verbose (repeatable)")
......
......@@ -2,6 +2,7 @@
package irmaserver
import (
"fmt"
"io/ioutil"
"net/http"
"time"
......@@ -27,7 +28,7 @@ func Start(config *Configuration) error {
}
// Start server
addr := config.listenAddress()
addr := fmt.Sprintf("%s:%d", conf.ListenAddress, conf.Port)
config.Logger.Info("Listening at ", addr)
s = &http.Server{Addr: addr, Handler: handler}
err = s.ListenAndServe()
......@@ -46,12 +47,12 @@ func Stop() {
// and IRMA client messages.
func Handler(config *Configuration) (http.Handler, error) {
conf = config
if err := conf.initialize(); err != nil {
return nil, server.LogError(err)
}
if err := irmarequestor.Initialize(conf.Configuration); err != nil {
return nil, err
}
if err := conf.initialize(); err != nil {
return nil, err
}
router := chi.NewRouter()
......
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