main.go 7.19 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
83
84
85
	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)")

86
87
88
	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")
89

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

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

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

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

	// Set log level
116
117
	verbosity := viper.GetInt("verbose")
	if verbosity == 1 {
Sietse Ringers's avatar
Sietse Ringers committed
118
119
		logger.Level = logrus.DebugLevel
	}
120
	if verbosity >= 2 {
Sietse Ringers's avatar
Sietse Ringers committed
121
122
123
124
125
126
		logger.Level = logrus.TraceLevel
	}
	if viper.GetBool("quiet") {
		logger.Out = ioutil.Discard
	}

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

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

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

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

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

	return nil
189
}
190

191
func handlePermission(conf []string, typ string) []string {
192
	perms := viper.GetStringSlice(typ)
193
194
195
196
197
198
199
200
	if len(perms) == 0 {
		return conf
	}
	if perms[0] == "" {
		perms = perms[1:]
	}
	if perms[len(perms)-1] == "" {
		perms = perms[:len(perms)-1]
201
	}
202
	return perms
203
}
204
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

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")
}