-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathelastic_guardian.go
182 lines (147 loc) · 5.43 KB
/
elastic_guardian.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
/*
Elastic Guardian is a tiny reverse proxy that can offer authentication (using HTTP Basic Auth)
as well as authorization.
While it was originally meant as a thin layer between Elasticsearch (which has no builtin
authentication/authorization) and the World, there is nothing specific to Elasticsearch (other
than a few defaults which can be changed via command line flags).
The generic use case for Elastic Guardian is to restrict access to a HTTP API with HTTP
Basic Auth and authorization rules.
It currently offers:
authentication (using HTTP Basic Auth);
authorization (based on the {user, HTTP verb, HTTP path}).
It currently supports loading the authentication and authorization data from two different backends:
inline variables (see settings.go) or
external files (filenames passed via commandline flags)
Whether the external files are used or not can be controled (at compile time) via AllowAuthFromFiles
constant. See that constant definition for further details.
Please see authentication and authorization packages for further details.
Commandline help can be accessed with:
elastic_guardian -h
That will also display the default values for all flags. Log output will go to console (stdout)
by default.
*/
package main
import (
"flag"
"fmt"
aa "github.com/alexaandru/elastic_guardian/authentication"
az "github.com/alexaandru/elastic_guardian/authorization"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"runtime"
"strings"
)
// AllowAuthFromFiles controls whether the files specified via command lien flags for
// authentication and authorization will actually be used. Can be used to lock down
// access to only the credentials stored at compile time (effectively disallow overriding
// them at runtime). May come in handy in some scenarios.
const AllowAuthFromFiles = true
// handlerWrapper captures the signature of a http.Handler wrapper function.
type handlerWrapper func(http.Handler) http.Handler
// BackendURL points to the target of the reverse proxy.
var BackendURL string
// FrontendURL points to the URL the proxy will accept incoming requests on.
var FrontendURL string
// Realm holds the Basic Auth realm.
var Realm string
// LogPath holds the path to the logfile.
var LogPath string
// CredentialsPath holds the path to the credentials file.
var CredentialsPath string
// AuthorizationsPath holds the path to the authorizations file.
var AuthorizationsPath string
func initReverseProxy(uri *url.URL, handlers ...handlerWrapper) (rp http.Handler) {
rp = httputil.NewSingleHostReverseProxy(uri)
for _, handler := range handlers {
rp = handler(rp)
}
return
}
func wrapAuthentication(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
status, user := aa.BasicAuthPassed(r)
if status == aa.Passed {
r.Header.Set("X-Authenticated-User", user)
h.ServeHTTP(w, r)
} else if status == aa.NotAttempted {
go logPrint(r, "401 Unauthorized")
w.Header().Set("WWW-Authenticate", "Basic realm=\""+Realm+"\"")
http.Error(w, "401 Unauthorized", http.StatusUnauthorized)
} else {
go logPrint(r, "403 Forbidden (authentication)")
http.Error(w, "403 Forbidden (authentication)", http.StatusForbidden)
}
})
}
func wrapAuthorization(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if az.AuthorizationPassed(r.Header.Get("X-Authenticated-User"), r.Method, r.URL.Path) {
go logPrint(r, "202 Accepted")
h.ServeHTTP(w, r)
} else {
go logPrint(r, "403 Forbidden (authorization)")
http.Error(w, "403 Forbidden (authorization)", http.StatusForbidden)
}
})
}
func processCmdLineFlags() {
flag.StringVar(&BackendURL, "backend", "http://localhost:9200", "Backend URL (where to proxy requests to)")
flag.StringVar(&FrontendURL, "frontend", ":9600", "Frontend URL (where to expose the proxied backend)")
flag.StringVar(&Realm, "realm", "Elasticsearch", "HTTP Basic Auth realm")
flag.StringVar(&LogPath, "logpath", "", "Path to the logfile (if not set, will dump to stdout)")
flag.StringVar(&CredentialsPath, "cpath", "", "Path to the credentials file")
flag.StringVar(&AuthorizationsPath, "apath", "", "Path to the authorizations file")
flag.Parse()
}
func redirectLogsToFile(path string) (f *os.File, err error) {
if path == "" {
return
}
f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0660)
if err != nil {
return
}
log.SetOutput(f)
return
}
func logPrint(r *http.Request, msg string) {
tokens := strings.Split(r.RemoteAddr, ":")
log.Println(fmt.Sprintf("%s \"%s %s %s\" %s", tokens[0], r.Method, r.URL.Path, r.Proto, msg))
}
func setup() (uri *url.URL, f *os.File, err error) {
runtime.GOMAXPROCS(runtime.NumCPU())
if !AllowAuthFromFiles || CredentialsPath == "" {
aa.LoadCredentials(inlineCredentials)
} else if err = aa.LoadCredentials(CredentialsPath); err != nil {
return
}
if !AllowAuthFromFiles || AuthorizationsPath == "" {
az.LoadAuthorizations(inlineAuthorizations)
} else if err = az.LoadAuthorizations(AuthorizationsPath); err != nil {
return
}
uri, err = url.Parse(BackendURL)
if err != nil {
return
}
f, err = redirectLogsToFile(LogPath)
return
}
func main() {
processCmdLineFlags()
uri, f, err := setup()
if f != nil {
defer f.Close()
}
if err != nil {
log.Fatal(err)
}
reverseProxy := initReverseProxy(uri, wrapAuthorization, wrapAuthentication)
http.Handle("/", reverseProxy)
if err := http.ListenAndServe(FrontendURL, nil); err != nil {
log.Fatal(err)
}
}