main.go 7.32 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
	"net"
8
	"os"
9
	"path/filepath"
10
11
	"regexp"
	"strconv"
12
	"strings"
13

14
	"github.com/Sirupsen/logrus"
15
	"github.com/go-errors/errors"
Sietse Ringers's avatar
Sietse Ringers committed
16
	"github.com/privacybydesign/irmago/server"
17
	"github.com/privacybydesign/irmago/server/core"
Sietse Ringers's avatar
Sietse Ringers committed
18
	"github.com/privacybydesign/irmago/server/irmaserver"
19
20
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
21
22
)

Sietse Ringers's avatar
Sietse Ringers committed
23
var logger = logrus.StandardLogger()
24
25
var conf *irmaserver.Configuration

26
func main() {
27
28
29
30
31
32
33
34
35
36
37
38
	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))
			}
		},
	}
39

Sietse Ringers's avatar
Sietse Ringers committed
40
41
42
43
44
	logger.Level = logrus.InfoLevel
	logger.SetFormatter(&logrus.TextFormatter{
		FullTimestamp: true,
	})

45
46
	if err := setFlags(cmd); err != nil {
		die(errors.WrapPrefix(err, "Failed to attach flags", 0))
47
48
	}

49
50
	if err := cmd.Execute(); err != nil {
		die(errors.WrapPrefix(err, "Failed to execute command", 0))
51
	}
52
53
54
}

func die(err *errors.Error) {
Sietse Ringers's avatar
Sietse Ringers committed
55
	logger.Error(err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
56
	logger.Trace(string(err.Stack()))
57
58
59
60
61
62
63
	os.Exit(1)
}

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

64
	cachepath, err := core.CachePath()
65
66
67
	if err != nil {
		return err
	}
68
69
70
71
72
73
	defaulturl, err := localIP()
	if err != nil {
		logger.Warn("Could not determine local IP address: ", err.Error())
	} else {
		defaulturl = "http://" + defaulturl + ":port"
	}
74

75
	flags.StringP("config", "c", "", "Path to configuration file")
76
	flags.StringP("irmaconf", "i", "", "path to irma_configuration")
77
	flags.StringP("privatekeys", "k", "", "path to IRMA private keys")
78
	flags.String("cachepath", cachepath, "Directory for writing cache files to")
79
80
	flags.StringP("jwtissuer", "j", "irmaserver", "JWT issuer")
	flags.StringP("jwtprivatekey", "w", "", "JWT private key or path to it")
81
	flags.StringP("url", "u", defaulturl, "External URL to server to which the IRMA client connects")
82
	flags.StringP("listenaddr", "l", "0.0.0.0", "Address at which to listen")
83
84
85
86
	flags.IntP("port", "p", 8088, "Port at which to listen")
	flags.Bool("noauth", false, "Whether or not to authenticate requestors")
	flags.String("requestors", "", "Requestor configuration (in JSON)")

87
88
89
	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("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
118
	verbosity := viper.GetInt("verbose")
	if verbosity == 1 {
Sietse Ringers's avatar
Sietse Ringers committed
119
120
		logger.Level = logrus.DebugLevel
	}
121
	if verbosity >= 2 {
Sietse Ringers's avatar
Sietse Ringers committed
122
123
124
125
126
127
		logger.Level = logrus.TraceLevel
	}
	if viper.GetBool("quiet") {
		logger.Out = ioutil.Discard
	}

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

	// Read configuration from flags and/or environmental variables
	conf = &irmaserver.Configuration{
Sietse Ringers's avatar
Sietse Ringers committed
142
		Configuration: &server.Configuration{
143
144
			IrmaConfigurationPath: viper.GetString("irmaconf"),
			IssuerPrivateKeysPath: viper.GetString("privatekeys"),
145
			CachePath:             viper.GetString("cachepath"),
146
			URL:                   viper.GetString("url"),
147
			Logger:                logger,
148
		},
149
150
		ListenAddress: viper.GetString("listenaddr"),
		Port:          viper.GetInt("port"),
151
152
153
154
155
		DisableRequestorAuthentication: viper.GetBool("noauth"),
		Requestors:                     make(map[string]irmaserver.Requestor),
		GlobalPermissions:              irmaserver.Permissions{},
		JwtIssuer:                      viper.GetString("jwtissuer"),
		JwtPrivateKey:                  viper.GetString("jwtprivatekey"),
156
157
		Verbose:                        viper.GetInt("verbose"),
		Quiet:                          viper.GetBool("quiet"),
158
	}
159
160
	// replace "port" in url with actual port
	replace := "$1:" + strconv.Itoa(conf.Port)
161
	conf.URL = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(conf.URL), []byte(replace)))
162

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

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

	bts, _ := json.MarshalIndent(conf, "", "   ")
Sietse Ringers's avatar
Sietse Ringers committed
187
188
	logger.Debug(string(bts), "\n")
	logger.Debug("Done configuring")
189
190

	return nil
191
}
192

193
func handlePermission(conf []string, typ string) []string {
194
	perms := viper.GetStringSlice(typ)
195
196
197
198
199
200
201
202
	if len(perms) == 0 {
		return conf
	}
	if perms[0] == "" {
		perms = perms[1:]
	}
	if perms[len(perms)-1] == "" {
		perms = perms[:len(perms)-1]
203
	}
204
	return perms
205
}
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

func localIP() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("No IP found")
}