main.go 6.8 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")
76
77
	flags.StringP("jwtissuer", "j", "irmaserver", "JWT issuer")
	flags.StringP("jwtprivatekey", "w", "", "JWT private key or path to it")
78
	flags.Int("maxrequestage", 300, "Max age in seconds of a session request JWT")
79
	flags.StringP("url", "u", defaulturl, "External URL to server to which the IRMA client connects")
80
	flags.StringP("listenaddr", "l", "0.0.0.0", "Address at which to listen")
81
	flags.IntP("port", "p", 8088, "Port at which to listen")
82
83
	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")
84
85
86
	flags.Bool("noauth", false, "Whether or not to authenticate requestors")
	flags.String("requestors", "", "Requestor configuration (in JSON)")

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

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

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

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

func configure() error {
102
103
104
105
106
107
108
109
110
111
112
113
	// 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
114
	err := viper.ReadInConfig() // Hold error checking until we know how much of it to log
115
116

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

122
	logger.Debug("Configuring")
123
	logger.Debug("Log level: ", logger.Level.String())
Sietse Ringers's avatar
Sietse Ringers committed
124
	if err != nil {
125
126
127
128
129
		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))
		}
130
	} else {
Sietse Ringers's avatar
Sietse Ringers committed
131
		logger.Info("Config file: ", viper.ConfigFileUsed())
132
133
134
135
	}

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

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

	// 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
170
			return errors.WrapPrefix(err, "Failed to unmarshal requestors from config file", 0)
171
172
173
		}
	}
	requestors := viper.GetString("requestors") // Read flag or env var
174
175
	if len(requestors) > 0 {
		if err := json.Unmarshal([]byte(requestors), &conf.Requestors); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
176
			return errors.WrapPrefix(err, "Failed to unmarshal requestors from json", 0)
177
178
179
180
		}
	}

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

	return nil
185
}
186

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