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

import (
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"io/ioutil"
6
	"os"
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
	"github.com/privacybydesign/irmago/server"
13
	"github.com/privacybydesign/irmago/server/backend"
Sietse Ringers's avatar
Sietse Ringers committed
14
	"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
61
62
63
64
	cachepath, err := backend.CachePath()
	if err != nil {
		return err
	}

65
	flags.StringP("config", "c", "", "Path to configuration file")
66
	flags.StringP("irmaconf", "i", "", "path to irma_configuration")
67
	flags.StringP("privatekeys", "k", "", "path to IRMA private keys")
68
	flags.String("cachepath", cachepath, "Directory for writing cache files to")
69
70
	flags.StringP("jwtissuer", "j", "irmaserver", "JWT issuer")
	flags.StringP("jwtprivatekey", "w", "", "JWT private key or path to it")
71
	flags.StringP("url", "u", "", "External URL to server to which the IRMA client connects")
72
73
74
75
	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)")

76
77
78
79
80
81
82
	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 = "*"

83
	flags.CountP("verbose", "v", "verbose (repeatable)")
Sietse Ringers's avatar
Sietse Ringers committed
84
	flags.BoolP("quiet", "q", false, "quiet")
85
86
87
88

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

Sietse Ringers's avatar
Sietse Ringers committed
90
91
92
93
	return viper.BindPFlags(flags)
}

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

	// Set log level
109
110
	verbosity := viper.GetInt("verbose")
	if verbosity == 1 {
Sietse Ringers's avatar
Sietse Ringers committed
111
112
		logger.Level = logrus.DebugLevel
	}
113
	if verbosity >= 2 {
Sietse Ringers's avatar
Sietse Ringers committed
114
115
116
117
118
119
		logger.Level = logrus.TraceLevel
	}
	if viper.GetBool("quiet") {
		logger.Out = ioutil.Discard
	}

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

	// Read configuration from flags and/or environmental variables
	conf = &irmaserver.Configuration{
Sietse Ringers's avatar
Sietse Ringers committed
134
		Configuration: &server.Configuration{
135
136
			IrmaConfigurationPath: viper.GetString("irmaconf"),
			IssuerPrivateKeysPath: viper.GetString("privatekeys"),
137
138
139
			CachePath:             viper.GetString("cachepath"),
			Url:                   viper.GetString("url"),
			Logger:                logger,
140
		},
141
142
143
144
145
146
		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"),
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
160
161
162
163
	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
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
182
183
184
185
186
187
188
189

func handlePermission(conf *[]string, typ string) {
	if viper.GetString(typ) == "*" {
		*conf = []string{"*"}
	}
	perms := viper.GetStringSlice(typ)
	if len(perms) > 0 {
		*conf = perms
	}
}