main.go 5.79 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
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) {
Sietse Ringers's avatar
Sietse Ringers committed
50
	logger.Error(err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
51
	logger.Trace(string(err.Stack()))
52
53
54
55
56
57
58
	os.Exit(1)
}

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

59
	flags.StringP("config", "c", "", "Path to configuration file")
60
61
62
63
	flags.StringP("irmaconf", "i", "./irma_configuration", "path to irma_configuration")
	flags.StringP("privatekeys", "k", "", "path to IRMA private keys")
	flags.StringP("jwtissuer", "j", "irmaserver", "JWT issuer")
	flags.StringP("jwtprivatekey", "w", "", "JWT private key or path to it")
64
	flags.StringP("url", "u", "", "External URL to server to which the IRMA client connects")
65
66
67
68
	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)")

69
70
71
72
73
74
75
	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 = "*"

76
	flags.CountP("verbose", "v", "verbose (repeatable)")
Sietse Ringers's avatar
Sietse Ringers committed
77
	flags.BoolP("quiet", "q", false, "quiet")
78
79
80
81

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

Sietse Ringers's avatar
Sietse Ringers committed
83
84
85
86
	return viper.BindPFlags(flags)
}

func configure() error {
87
88
89
90
91
92
93
94
95
96
97
98
	// 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
99
	err := viper.ReadInConfig() // Hold error checking until we know how much of it to log
100
101

	// Set log level
102
103
	verbosity := viper.GetInt("verbose")
	if verbosity == 1 {
Sietse Ringers's avatar
Sietse Ringers committed
104
105
		logger.Level = logrus.DebugLevel
	}
106
	if verbosity >= 2 {
Sietse Ringers's avatar
Sietse Ringers committed
107
108
109
110
111
112
		logger.Level = logrus.TraceLevel
	}
	if viper.GetBool("quiet") {
		logger.Out = ioutil.Discard
	}

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

	// Read configuration from flags and/or environmental variables
	conf = &irmaserver.Configuration{
Sietse Ringers's avatar
Sietse Ringers committed
127
		Configuration: &server.Configuration{
128
129
			IrmaConfigurationPath: viper.GetString("irmaconf"),
			IssuerPrivateKeysPath: viper.GetString("privatekeys"),
130
131
			Url:    viper.GetString("url"),
			Logger: logger,
132
		},
133
134
135
136
137
138
		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"),
139
140
		Verbose:                        viper.GetInt("verbose"),
		Quiet:                          viper.GetBool("quiet"),
141
142
	}

143
144
	// Handle global permissions
	if len(viper.GetStringMap("permissions")) > 0 { // First read config file
145
		if err := viper.UnmarshalKey("permissions", &conf.GlobalPermissions); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
146
			return errors.WrapPrefix(err, "Failed to unmarshal permissions from config file", 0)
147
148
		}
	}
149
150
151
152
153
154
155
	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
156
			return errors.WrapPrefix(err, "Failed to unmarshal requestors from config file", 0)
157
158
159
		}
	}
	requestors := viper.GetString("requestors") // Read flag or env var
160
161
	if len(requestors) > 0 {
		if err := json.Unmarshal([]byte(requestors), &conf.Requestors); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
162
			return errors.WrapPrefix(err, "Failed to unmarshal requestors from json", 0)
163
164
165
166
		}
	}

	bts, _ := json.MarshalIndent(conf, "", "   ")
Sietse Ringers's avatar
Sietse Ringers committed
167
168
	logger.Debug(string(bts), "\n")
	logger.Debug("Done configuring")
169
170

	return nil
171
}
172
173
174
175
176
177
178
179
180
181

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