main.go 7.16 KB
Newer Older
1
2
3
package main

import (
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"io/ioutil"
6
	"net"
7
	"os"
8
	"path/filepath"
9
10
	"regexp"
	"strconv"
11
	"strings"
12

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

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

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

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

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

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

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

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

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

74
	flags.StringP("config", "c", "", "Path to configuration file")
75
	flags.StringP("irmaconf", "i", "", "path to irma_configuration")
76
	flags.StringP("privatekeys", "k", "", "path to IRMA private keys")
77
	flags.String("cachepath", cachepath, "Directory for writing cache files to")
78
79
	flags.StringP("jwtissuer", "j", "irmaserver", "JWT issuer")
	flags.StringP("jwtprivatekey", "w", "", "JWT private key or path to it")
80
	flags.StringP("url", "u", defaulturl, "External URL to server to which the IRMA client connects")
81
82
83
84
	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)")

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

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

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

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

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

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

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

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

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

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

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

	return nil
188
}
189

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

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