main.go 6.66 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
	flags.IntP("port", "p", 8088, "Port at which to listen")
81
82
	flags.Int("clientport", 0, "If specified, start a separate server for the IRMA app at his port")
	flags.String("clientlistenaddr", "0.0.0.0", "Address at which server for IRMA app listens")
83
84
85
	flags.Bool("noauth", false, "Whether or not to authenticate requestors")
	flags.String("requestors", "", "Requestor configuration (in JSON)")

86
87
	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")
88
	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
	logger.Level = server.Verbosity(viper.GetInt("verbose"))
Sietse Ringers's avatar
Sietse Ringers committed
117
118
119
120
	if viper.GetBool("quiet") {
		logger.Out = ioutil.Discard
	}

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

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

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

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

	bts, _ := json.MarshalIndent(conf, "", "   ")
Sietse Ringers's avatar
Sietse Ringers committed
179
180
	logger.Debug(string(bts), "\n")
	logger.Debug("Done configuring")
181
182

	return nil
183
}
184

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