main.go 6.27 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
	"os"
8
9
	"path/filepath"
	"strings"
10

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

Sietse Ringers's avatar
Sietse Ringers committed
19
var logger = logrus.StandardLogger()
20
21
var conf *irmaserver.Configuration

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

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

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

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

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

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

60
	cachepath, err := server.CachePath()
61
62
63
	if err != nil {
		return err
	}
64
	defaulturl, err := server.LocalIP()
65
66
67
68
69
	if err != nil {
		logger.Warn("Could not determine local IP address: ", err.Error())
	} else {
		defaulturl = "http://" + defaulturl + ":port"
	}
70

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

83
84
85
	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")
86

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

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

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

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

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

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

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

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

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

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

	return nil
178
}
179

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