main.go 7.14 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
88
89
90
91
	flags.StringSlice("disclosing", nil, "Comma-separated list of attributes that all requestors may verify")
	flags.StringSlice("signing", nil, "Comma-separated list of attributes that all requestors may request in signatures")
	flags.StringSlice("issuing", nil, "Comma-separated list of attributes that all requestors may issue")
	flags.Lookup("disclosing").NoOptDefVal = "*"
	flags.Lookup("signing").NoOptDefVal = "*"
	flags.Lookup("issuing").NoOptDefVal = "*"

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

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

	// Read configuration from flags and/or environmental variables
	conf = &irmaserver.Configuration{
Sietse Ringers's avatar
Sietse Ringers committed
143
		Configuration: &server.Configuration{
144
145
			IrmaConfigurationPath: viper.GetString("irmaconf"),
			IssuerPrivateKeysPath: viper.GetString("privatekeys"),
146
			CachePath:             viper.GetString("cachepath"),
147
			URL:                   viper.GetString("url"),
148
			Logger:                logger,
149
		},
150
151
152
153
154
155
		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"),
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
172
173
174
175
	handlePermission(&conf.GlobalPermissions.Disclosing, "disclosing") // Read flag or env var
	handlePermission(&conf.GlobalPermissions.Signing, "signing")
	handlePermission(&conf.GlobalPermissions.Issuing, "issuing")

	// 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
194
195
196
197
198
199
200
201

func handlePermission(conf *[]string, typ string) {
	if viper.GetString(typ) == "*" {
		*conf = []string{"*"}
	}
	perms := viper.GetStringSlice(typ)
	if len(perms) > 0 {
		*conf = 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

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