main.go 6.31 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.StringP("url", "u", defaulturl, "External URL to server to which the IRMA client connects")
79
	flags.StringP("listenaddr", "l", "0.0.0.0", "Address at which to listen")
80
81
82
83
	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)")

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

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

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

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

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

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

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

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

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

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

	bts, _ := json.MarshalIndent(conf, "", "   ")
Sietse Ringers's avatar
Sietse Ringers committed
175
176
	logger.Debug(string(bts), "\n")
	logger.Debug("Done configuring")
177
178

	return nil
179
}
180

181
func handlePermission(conf []string, typ string) []string {
182
	perms := viper.GetStringSlice(typ)
183
184
185
186
187
188
189
190
	if len(perms) == 0 {
		return conf
	}
	if perms[0] == "" {
		perms = perms[1:]
	}
	if perms[len(perms)-1] == "" {
		perms = perms[:len(perms)-1]
191
	}
192
	return perms
193
}