-
Notifications
You must be signed in to change notification settings - Fork 3
/
cli.go
259 lines (224 loc) · 8.72 KB
/
cli.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package cli
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"strings"
"github.com/EmbeddedEnterprises/autobahnkreuz/util"
flag "github.com/spf13/pflag"
)
type TLSClientCAInfo struct {
AuthRole string
CACert *x509.Certificate
}
type CertificatePolicy int
const (
DisableClientAuthentication CertificatePolicy = iota
AcceptClientCert
RequireClientCert
)
type AuthorizerMissingPolicy int
const (
RejectAction AuthorizerMissingPolicy = iota
PermitAction
)
type TLSEndpoint struct {
WS WSEndpoint
Certificate tls.Certificate
ClientCertPolicy CertificatePolicy
ValidClientCAs []TLSClientCAInfo
}
type WSEndpoint struct {
Port uint16
Host string
}
type CLIParameters struct {
ListenTLS *TLSEndpoint
ListenWS *WSEndpoint
Realm string
EnableTicketAuth bool
UpstreamAuthFunc string
UpstreamGetAuthRolesFunc string
ReservedAuthRole []string
EnableResumeToken bool
EnableAnonymousAuth bool
AnonymousAuthRole string
// Global Authorization Variables
// Works in both authenticators
TrustedAuthRoles []string
AuthorizeFailed AuthorizerMissingPolicy
// Dynamic Authorizer
// According to wamp-proto
EnableAuthorizer bool
UpstreamAuthorizer string
// Feature Authorizer
// According to my brain and my whiteboard
EnableFeatureAuthorizer bool
UpstreamFeatureAuthorizerMatrix string
UpstreamFeatureAuthorizerMapping string
}
func assertNotEmpty(name, value string) {
if value == "" {
util.Logger.Criticalf("%s must not be empty!", name)
os.Exit(util.ExitArgument)
}
}
func parseClientCA(cca string) TLSClientCAInfo {
x := strings.Split(cca, ";")
if len(x) != 2 {
util.Logger.Criticalf("ClientCA is in invalid format, expected authrole;ca-cert.pem, got: %s", cca)
os.Exit(util.ExitArgument)
}
cert, err := ioutil.ReadFile(x[1])
if err != nil {
util.Logger.Criticalf("Failed to load client CA %s: %v", x[1], err)
os.Exit(util.ExitArgument)
}
pem, _ := pem.Decode(cert)
if pem == nil {
util.Logger.Criticalf("Failed to parse PEM data of client CA %s", x[1])
os.Exit(util.ExitArgument)
}
certObj, err := x509.ParseCertificate(pem.Bytes)
if err != nil {
util.Logger.Criticalf("Failed to parse client CA %s: %v", x[1], err)
os.Exit(util.ExitArgument)
}
return TLSClientCAInfo{
AuthRole: x[0],
CACert: certObj,
}
}
func ParseCLI() CLIParameters {
// Step 1: Build the command line interface.
// we start with some really basic parameters
cliRealm := flag.StringP("realm", "r", "", "The realm to run the router on")
cliAnonEnable := flag.Bool("enable-anonymous", true, "Whether to allow authmethod 'anonymous'")
cliAnonRole := flag.String("anonymous-authrole", "anonymous", "Authentication role to assign to anonymous clients")
cliTicketEnable := flag.Bool("enable-ticket", true, "Whether to allow authmethod 'ticket'")
cliTicketUpstream := flag.String("ticket-check-func", "", "Which WAMP RPC to call when ticket authentication is requested")
cliTicketRoleUpstream := flag.String("ticket-get-role-func", "", "Which WAMP RPC to call to resolve authid to authrole/authextra")
cliExcludeAuthRoles := flag.StringSlice("exclude-auth-role", nil, "Authentication roles to exclude from ticket authentication")
cliEnableResumeToken := flag.Bool("enable-resume-token", true, "Whether to allow ticket authentication to have a keep-me-logged-in token.")
// Unencrypted endpoint
cliEnableWS := flag.Bool("enable-ws", true, "Enable unencrypted WebSocket endpoint")
cliPortWS := flag.Uint16("ws-port", 8001, "Port for the WebSocket endpoint")
cliHostWS := flag.String("ws-host", "", "Listen address for the WebSocket endpoint")
// Encrypted endpoint
cliEnableWSS := flag.Bool("enable-wss", true, "Enable encrypted WebSocket endpoint")
cliPortWSS := flag.Uint16("wss-port", 8000, "Port for the TLS endpoint")
cliHostWSS := flag.String("wss-host", "", "Listen address for the TLS endpoint")
cliKeyWSS := flag.String("wss-key-file", "", "TLS Key file")
cliCertWSS := flag.String("wss-cert-file", "", "TLS Cert file")
cliClientAuth := flag.String("wss-client-auth", "accept", "Use TLS client authentication (values: 'no', 'accept', 'require')")
cliClientCAs := flag.StringSlice("wss-client-ca", nil, "Acceptable client CAs and their authroles (format: 'authrole;ca-cert.pem,authrole;ca-cert.pem,....')")
cliEnableAuthorizer := flag.Bool("enable-authorization", true, "Enable dynamic checking of auth roles")
cliUpstreamAuthorizer := flag.String("authorizer-func", "", "Which WAMP RPC to call when an action has to be authorized")
cliEnableFeatureAuthorizer := flag.Bool("enable-feature-authorization", false, "Enable authorization checking based on a feature matrix")
cliUpstreamFeatureAuthorizerMatrix := flag.String("feature-authorizer-matrix-func", "", "Which WAMP RPC to call to get a feature matrix")
cliUpstreamFeatureAuthorizerMapping := flag.String("feature-authorizer-mapping-func", "", "Which WAMP RPC to call to get a feature mapping")
cliTrustAuthRoles := flag.StringSlice("trusted-authroles", nil, "Authorize any actions of these authentication roles")
cliAuthorizerFailed := flag.String("authorizer-fallback", "reject", "Whether to permit any actions if the authorizer endpoint fails (values: 'permit', 'reject')")
// Call the command line parser
flag.Parse()
config := CLIParameters{
Realm: *cliRealm,
EnableAnonymousAuth: *cliAnonEnable,
AnonymousAuthRole: *cliAnonRole,
EnableTicketAuth: *cliTicketEnable,
UpstreamAuthFunc: *cliTicketUpstream,
UpstreamGetAuthRolesFunc: *cliTicketRoleUpstream,
EnableResumeToken: *cliEnableResumeToken,
ReservedAuthRole: *cliExcludeAuthRoles,
EnableAuthorizer: *cliEnableAuthorizer,
TrustedAuthRoles: *cliTrustAuthRoles,
UpstreamAuthorizer: *cliUpstreamAuthorizer,
EnableFeatureAuthorizer: *cliEnableFeatureAuthorizer,
UpstreamFeatureAuthorizerMatrix: *cliUpstreamFeatureAuthorizerMatrix,
UpstreamFeatureAuthorizerMapping: *cliUpstreamFeatureAuthorizerMapping,
}
assertNotEmpty("Realm", config.Realm)
if config.EnableAnonymousAuth {
assertNotEmpty("Anonymous authentication role", config.AnonymousAuthRole)
}
if config.EnableTicketAuth {
assertNotEmpty("Ticket check function", config.UpstreamAuthFunc)
}
if config.EnableResumeToken || config.EnableTicketAuth {
assertNotEmpty("Auth role getter function", config.UpstreamGetAuthRolesFunc)
}
if config.EnableAuthorizer && config.EnableFeatureAuthorizer {
util.Logger.Criticalf("Can't enable both authorizers. Choose one!")
os.Exit(util.ExitArgument)
}
if config.EnableFeatureAuthorizer {
assertNotEmpty("Feature Authorizer Matrix", config.UpstreamFeatureAuthorizerMatrix)
assertNotEmpty("Feature Authorizer Mapping", config.UpstreamFeatureAuthorizerMapping)
}
if config.EnableAuthorizer {
assertNotEmpty("Authorization function", config.UpstreamAuthorizer)
switch *cliAuthorizerFailed {
case "permit", "accept":
config.AuthorizeFailed = PermitAction
default:
config.AuthorizeFailed = RejectAction
}
}
enabled := false
if *cliEnableWS {
enabled = true
config.ListenWS = &WSEndpoint{
Port: *cliPortWS,
// Host may be empty here, which means 0.0.0.0
Host: *cliHostWS,
}
}
if *cliEnableWSS {
enabled = true
config.ListenTLS = &TLSEndpoint{
WS: WSEndpoint{
Port: *cliPortWSS,
// Host may be empty here, which means 0.0.0.0
Host: *cliHostWSS,
},
}
serverCert, err := tls.LoadX509KeyPair(*cliCertWSS, *cliKeyWSS)
if err != nil {
util.Logger.Criticalf("Failed to load server certificate: %v", err)
os.Exit(util.ExitArgument)
}
config.ListenTLS.Certificate = serverCert
switch *cliClientAuth {
case "no":
config.ListenTLS.ClientCertPolicy = DisableClientAuthentication
case "require":
config.ListenTLS.ClientCertPolicy = RequireClientCert
default:
config.ListenTLS.ClientCertPolicy = AcceptClientCert
}
if config.ListenTLS.ClientCertPolicy != DisableClientAuthentication {
for _, cca := range *cliClientCAs {
config.ListenTLS.ValidClientCAs = append(
config.ListenTLS.ValidClientCAs,
parseClientCA(cca),
)
}
if config.ListenTLS.ValidClientCAs == nil {
util.Logger.Critical("You have to specify at least one client CA to authenticate against.")
os.Exit(util.ExitArgument)
}
}
}
if !enabled {
util.Logger.Critical("At least one transport must be enabled!")
os.Exit(util.ExitArgument)
}
if !config.EnableTicketAuth && !config.EnableAnonymousAuth && (config.ListenTLS == nil || config.ListenTLS.ClientCertPolicy == DisableClientAuthentication) {
util.Logger.Critical("You have to enable at least one authentication method!")
util.Logger.Critical("Otherwise no client will be able to connect!")
os.Exit(util.ExitArgument)
}
return config
}