main.go 6.94 KB
Newer Older
1
// Executable for the irmaserver.
2
3
4
package main

import (
5
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
6
	"io/ioutil"
7
8
	"path/filepath"
	"strings"
9

10
	"github.com/Sirupsen/logrus"
11
	"github.com/go-errors/errors"
Sietse Ringers's avatar
Sietse Ringers committed
12
13
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmaserver"
14
15
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
16
17
)

Sietse Ringers's avatar
Sietse Ringers committed
18
var logger = logrus.StandardLogger()
19
20
var conf *irmaserver.Configuration

21
func main() {
22
23
24
25
26
27
28
29
30
31
32
33
	var cmd = &cobra.Command{
		Use:   "irmaserver",
		Short: "IRMA server for verifying and issuing attributes",
		Run: func(command *cobra.Command, args []string) {
			if err := configure(); err != nil {
				die(errors.WrapPrefix(err, "Failed to configure server", 0))
			}
			if err := irmaserver.Start(conf); err != nil {
				die(errors.WrapPrefix(err, "Failed to start server", 0))
			}
		},
	}
34

Sietse Ringers's avatar
Sietse Ringers committed
35
36
37
38
39
	logger.Level = logrus.InfoLevel
	logger.SetFormatter(&logrus.TextFormatter{
		FullTimestamp: true,
	})

40
41
	if err := setFlags(cmd); err != nil {
		die(errors.WrapPrefix(err, "Failed to attach flags", 0))
42
43
	}

44
45
	if err := cmd.Execute(); err != nil {
		die(errors.WrapPrefix(err, "Failed to execute command", 0))
46
	}
47
48
49
}

func die(err *errors.Error) {
50
51
52
53
54
	msg := err.Error()
	if logger.IsLevelEnabled(logrus.TraceLevel) {
		msg += "\nStack trace:\n" + string(err.Stack())
	}
	logger.Fatal(msg)
55
56
57
58
59
60
}

func setFlags(cmd *cobra.Command) error {
	flags := cmd.Flags()
	flags.SortFlags = false

61
	cachepath, err := server.CachePath()
62
63
64
	if err != nil {
		return err
	}
65
	defaulturl, err := server.LocalIP()
66
67
68
69
70
	if err != nil {
		logger.Warn("Could not determine local IP address: ", err.Error())
	} else {
		defaulturl = "http://" + defaulturl + ":port"
	}
71

72
	flags.StringP("config", "c", "", "Path to configuration file")
73
	flags.StringP("irmaconf", "i", "", "path to irma_configuration")
74
	flags.StringP("privatekeys", "k", "", "path to IRMA private keys")
75
	flags.String("cachepath", cachepath, "Directory for writing cache files to")
Sietse Ringers's avatar
Sietse Ringers committed
76
	flags.Uint("schemeupdate", 60, "Update IRMA schemes every x minutes (0 to disable)")
77
78
	flags.StringP("jwtissuer", "j", "irmaserver", "JWT issuer")
	flags.StringP("jwtprivatekey", "w", "", "JWT private key or path to it")
79
	flags.Int("maxrequestage", 300, "Max age in seconds of a session request JWT")
80
	flags.StringP("url", "u", defaulturl, "External URL to server to which the IRMA client connects")
81
	flags.StringP("listenaddr", "l", "0.0.0.0", "Address at which to listen")
82
	flags.IntP("port", "p", 8088, "Port at which to listen")
83
84
	flags.Int("clientport", 0, "If specified, start a separate server for the IRMA app at his port")
	flags.String("clientlistenaddr", "0.0.0.0", "Address at which server for IRMA app listens")
85
86
87
	flags.Bool("noauth", false, "Whether or not to authenticate requestors")
	flags.String("requestors", "", "Requestor configuration (in JSON)")

88
89
	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")
90
	flags.StringSlice("issue", nil, "Comma-separated list of attributes that all requestors may issue")
91

92
	flags.CountP("verbose", "v", "verbose (repeatable)")
Sietse Ringers's avatar
Sietse Ringers committed
93
	flags.BoolP("quiet", "q", false, "quiet")
94
95
96
97

	// Environment variables
	viper.SetEnvPrefix("IRMASERVER")
	viper.AutomaticEnv()
98

Sietse Ringers's avatar
Sietse Ringers committed
99
100
101
102
	return viper.BindPFlags(flags)
}

func configure() error {
103
104
105
106
107
108
109
110
111
112
113
114
	// Locate and read configuration file
	confpath := viper.GetString("config")
	if confpath != "" {
		dir, file := filepath.Dir(confpath), filepath.Base(confpath)
		viper.SetConfigName(strings.TrimSuffix(file, filepath.Ext(file)))
		viper.AddConfigPath(dir)
	} else {
		viper.SetConfigName("irmaserver")
		viper.AddConfigPath(".")
		viper.AddConfigPath("/etc/irmaserver/")
		viper.AddConfigPath("$HOME/.irmaserver")
	}
Sietse Ringers's avatar
Sietse Ringers committed
115
	err := viper.ReadInConfig() // Hold error checking until we know how much of it to log
116
117

	// Set log level
118
	logger.Level = server.Verbosity(viper.GetInt("verbose"))
Sietse Ringers's avatar
Sietse Ringers committed
119
120
121
122
	if viper.GetBool("quiet") {
		logger.Out = ioutil.Discard
	}

123
	logger.Debug("Configuring")
124
	logger.Debug("Log level: ", logger.Level.String())
Sietse Ringers's avatar
Sietse Ringers committed
125
	if err != nil {
126
127
128
129
130
		if _, notfound := err.(viper.ConfigFileNotFoundError); notfound {
			logger.Info("No configuration file found")
		} else {
			die(errors.WrapPrefix(err, "Failed to unmarshal configuration file at "+viper.ConfigFileUsed(), 0))
		}
131
	} else {
Sietse Ringers's avatar
Sietse Ringers committed
132
		logger.Info("Config file: ", viper.ConfigFileUsed())
133
134
135
136
	}

	// Read configuration from flags and/or environmental variables
	conf = &irmaserver.Configuration{
Sietse Ringers's avatar
Sietse Ringers committed
137
		Configuration: &server.Configuration{
138
139
			IrmaConfigurationPath: viper.GetString("irmaconf"),
			IssuerPrivateKeysPath: viper.GetString("privatekeys"),
140
			CachePath:             viper.GetString("cachepath"),
141
			URL:                   viper.GetString("url"),
Sietse Ringers's avatar
Sietse Ringers committed
142
			SchemeUpdateInterval:  viper.GetInt("schemeupdate"),
143
			Logger:                logger,
144
		},
145
146
147
148
		ListenAddress:                  viper.GetString("listenaddr"),
		Port:                           viper.GetInt("port"),
		ClientListenAddress:            viper.GetString("clientlistenaddr"),
		ClientPort:                     viper.GetInt("clientport"),
149
150
151
152
153
		DisableRequestorAuthentication: viper.GetBool("noauth"),
		Requestors:                     make(map[string]irmaserver.Requestor),
		GlobalPermissions:              irmaserver.Permissions{},
		JwtIssuer:                      viper.GetString("jwtissuer"),
		JwtPrivateKey:                  viper.GetString("jwtprivatekey"),
154
		MaxRequestAge:                  viper.GetInt("maxrequestage"),
155
156
		Verbose:                        viper.GetInt("verbose"),
		Quiet:                          viper.GetBool("quiet"),
157
158
	}

159
160
	// Handle global permissions
	if len(viper.GetStringMap("permissions")) > 0 { // First read config file
161
		if err := viper.UnmarshalKey("permissions", &conf.GlobalPermissions); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
162
			return errors.WrapPrefix(err, "Failed to unmarshal permissions from config file", 0)
163
164
		}
	}
165
166
167
	conf.GlobalPermissions.Disclosing = handlePermission(conf.GlobalPermissions.Disclosing, "disclose")
	conf.GlobalPermissions.Signing = handlePermission(conf.GlobalPermissions.Signing, "sign")
	conf.GlobalPermissions.Issuing = handlePermission(conf.GlobalPermissions.Issuing, "issue")
168
169
170
171

	// Handle requestors
	if len(viper.GetStringMap("requestors")) > 0 { // First read config file
		if err := viper.UnmarshalKey("requestors", &conf.Requestors); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
172
			return errors.WrapPrefix(err, "Failed to unmarshal requestors from config file", 0)
173
174
175
		}
	}
	requestors := viper.GetString("requestors") // Read flag or env var
176
177
	if len(requestors) > 0 {
		if err := json.Unmarshal([]byte(requestors), &conf.Requestors); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
178
			return errors.WrapPrefix(err, "Failed to unmarshal requestors from json", 0)
179
180
181
182
		}
	}

	bts, _ := json.MarshalIndent(conf, "", "   ")
Sietse Ringers's avatar
Sietse Ringers committed
183
184
	logger.Debug(string(bts), "\n")
	logger.Debug("Done configuring")
185
186

	return nil
187
}
188

189
func handlePermission(conf []string, typ string) []string {
190
	perms := viper.GetStringSlice(typ)
191
192
193
194
195
196
197
198
	if len(perms) == 0 {
		return conf
	}
	if perms[0] == "" {
		perms = perms[1:]
	}
	if perms[len(perms)-1] == "" {
		perms = perms[:len(perms)-1]
199
	}
200
	return perms
201
}