Skip to content

Commit 7c15cb8

Browse files
authored
Merge pull request #85 from TIBCOSoftware/feature-basic-auth-on-gw
Added ability to perform basic auth against the gw
2 parents 30af3fd + 1bd2426 commit 7c15cb8

File tree

5 files changed

+361
-1
lines changed

5 files changed

+361
-1
lines changed

ext/flogo/trigger/gorillamuxtrigger/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ settings, outputs and handler:
6060
{
6161
"name": "trustStore",
6262
"type": "string"
63+
},
64+
{
65+
"name": "basicAuthFile",
66+
"type": "string"
6367
}
6468
],
6569
"outputs": [
@@ -128,6 +132,7 @@ settings, outputs and handler:
128132
| serverKey | Server private key file in PEM format. Need to provide file name along with path. Path can be relative to gateway binary location. |
129133
| enableClientAuth | true - To enable client AUTH, false - Client AUTH is not enabled |
130134
| trustStore | Trust dir containing clinet CAs |
135+
| basicAuthFile | Path to a password file with username/passwords. An environment variable can be used here. |
131136

132137
### Outputs
133138
| Key | Description |
@@ -293,3 +298,45 @@ Follwing is the sample payload. Try changing the value of name ("CAT" to some ot
293298
]
294299
}
295300
```
301+
302+
#### Basic Authentication
303+
304+
To use basic authentication, the necessary descriptors must be in place. For example:
305+
306+
```json
307+
"configurations": [
308+
{
309+
"name": "restConfig",
310+
"type": "github.com/TIBCOSoftware/mashling/ext/flogo/trigger/gorillamuxtrigger",
311+
"description": "Configuration for rest trigger",
312+
"settings": {
313+
"port": "9096",
314+
"basicAuthFile": "${env.BASIC_AUTH_FILE}"
315+
}
316+
}
317+
],
318+
```
319+
320+
This specifies that BASIC_AUTH_FILE is an environment variable whose value will be read into "basicAuthFile" when the gateway starts. This value should be a path to a password file.
321+
322+
Plain username/password file: /home/test/password.txt
323+
```
324+
foo:bar
325+
moo:poo
326+
```
327+
328+
Alternatively, you can also use a salted password file where the format is: username:salt:sha256(salt + password)
329+
```
330+
foo:5VvmQnTXZ10wGZu_Gkjb8umfUPIOQTQ3p1YFadAWTl8=:6267beb3f851b7fee14011f6aa236556f35b186a6791b80b48341e990c367643
331+
```
332+
333+
Start the gateway:
334+
```
335+
BASIC_AUTH_FILE=/home/test/password.txt myApp
336+
```
337+
338+
**NOTE**: It is important to limit access to the password.txt on your environment.
339+
340+
341+
342+
+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Copyright © 2017. TIBCO Software Inc.
3+
* This file is subject to the license terms contained
4+
* in the license file that is distributed with this file.
5+
*/
6+
package gorillamuxtrigger
7+
8+
import (
9+
"bufio"
10+
"crypto/sha256"
11+
"encoding/base64"
12+
"encoding/csv"
13+
"encoding/hex"
14+
"io"
15+
"net/http"
16+
"os"
17+
"regexp"
18+
"strings"
19+
"sync"
20+
)
21+
22+
type hashedCred struct {
23+
salt string
24+
username string
25+
password string
26+
}
27+
28+
type plainCred struct {
29+
username string
30+
password string
31+
}
32+
33+
type Auth interface {
34+
authenticate(clientCred string) bool
35+
}
36+
37+
type basic struct {
38+
}
39+
40+
const (
41+
basicAuthFile = "basicAuthFile"
42+
)
43+
44+
var mu sync.Mutex
45+
var credMap map[string]hashedCred
46+
47+
func basicAuth() Auth {
48+
return &basic{}
49+
}
50+
51+
// isAuthEnabled check if authentication is enabled
52+
func isAuthEnabled(settings map[string]interface{}) bool {
53+
// Check if basic auth is in use
54+
if _, ok := settings[basicAuthFile]; !ok {
55+
return false
56+
}
57+
58+
return true
59+
}
60+
61+
// setupAuth setups up authentication.
62+
// This should be called to load the creds into a map once.
63+
func setupAuth(settings map[string]interface{}) {
64+
err := loadCreds(settings[basicAuthFile].(string))
65+
if err != nil {
66+
log.Error(err)
67+
panic("Unable to load creds file")
68+
}
69+
}
70+
71+
// loadCreds loads the file with the credentials.
72+
// The file may contain lines of the form username:password
73+
// or username:salt:sha256(salt + password)
74+
func loadCreds(pathToFile string) error {
75+
mu.Lock()
76+
if credMap != nil {
77+
mu.Unlock()
78+
return nil
79+
}
80+
81+
credMap = make(map[string]hashedCred)
82+
83+
csvFile, err := os.Open(pathToFile)
84+
if err != nil {
85+
mu.Unlock()
86+
return err
87+
}
88+
defer csvFile.Close()
89+
90+
reader := csv.NewReader(bufio.NewReader(csvFile))
91+
reader.Comma = ':'
92+
93+
for {
94+
line, error := reader.Read()
95+
if error == io.EOF {
96+
break
97+
} else if error != nil {
98+
log.Error(error)
99+
}
100+
101+
if len(line) == 3 {
102+
// hashed password
103+
credMap[line[0]] = hashedCred{
104+
salt: line[1],
105+
password: line[2],
106+
}
107+
} else if len(line) == 2 {
108+
// plan text password
109+
credMap[line[0]] = hashedCred{
110+
password: line[1],
111+
}
112+
}
113+
}
114+
mu.Unlock()
115+
116+
return nil
117+
}
118+
119+
// Authenticate check if the request is allowed
120+
func authenticate(r *http.Request, settings map[string]interface{}) bool {
121+
// Authenticate using basic auth
122+
clientCred := r.Header.Get("Authorization")
123+
re, err := regexp.Compile(`(?i)Basic (.*)`)
124+
if err != nil {
125+
log.Error(err)
126+
return false
127+
}
128+
result := re.FindStringSubmatch(clientCred)
129+
130+
auth := basicAuth()
131+
if len(result) == 2 {
132+
// Now verify the client creds
133+
return auth.authenticate(result[1])
134+
}
135+
136+
return false
137+
}
138+
139+
// authenticate performs basic authentication against provided clientCred
140+
func (a *basic) authenticate(clientCred string) bool {
141+
username, passwd := base64decode(clientCred)
142+
143+
credStruct, ok := credMap[username]
144+
if !ok {
145+
return false
146+
}
147+
148+
if credStruct.salt == "" {
149+
if credStruct.password == passwd {
150+
return true
151+
}
152+
} else {
153+
if sha(credStruct.salt+passwd) == credStruct.password {
154+
return true
155+
}
156+
}
157+
158+
return false
159+
}
160+
161+
// base64encode base64 encode the username and password
162+
func base64encode(username string, password string) string {
163+
auth := username + ":" + password
164+
encoded := base64.StdEncoding.EncodeToString([]byte(auth))
165+
return encoded
166+
}
167+
168+
// base64decode base64 decodes the creds
169+
// returns the username, password
170+
func base64decode(creds string) (string, string) {
171+
decoded, _ := base64.StdEncoding.DecodeString(creds)
172+
173+
s := strings.Split(string(decoded), ":")
174+
return s[0], s[1]
175+
}
176+
177+
// sha applies sha256 to the string
178+
func sha(str string) string {
179+
bytes := []byte(str)
180+
181+
h := sha256.New()
182+
h.Write(bytes)
183+
code := h.Sum(nil)
184+
codestr := hex.EncodeToString(code)
185+
186+
return codestr
187+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright © 2017. TIBCO Software Inc.
3+
* This file is subject to the license terms contained
4+
* in the license file that is distributed with this file.
5+
*/
6+
package gorillamuxtrigger
7+
8+
import (
9+
"io/ioutil"
10+
"os"
11+
"testing"
12+
)
13+
14+
const dataPlain string = `
15+
foo:bar
16+
moo:mar
17+
care:bear
18+
`
19+
20+
const dataHashed string = `
21+
foo:5VvmQnTXZ10wGZu_Gkjb8umfUPIOQTQ3p1YFadAWTl8=:6267beb3f851b7fee14011f6aa236556f35b186a6791b80b48341e990c367643
22+
foobar:5VvmQnTXZ10wGZu_Gkjb8umfUPIOQTQ3p1YFadAWTl8=:6267beb3f851b7fee14011f6aa236556f35b186a6791b80b48341e990c367643
23+
`
24+
25+
func TestBasicAuthVerify(t *testing.T) {
26+
credMap = nil
27+
b := basicAuth()
28+
29+
file, err := ioutil.TempFile(os.TempDir(), "prefix")
30+
if err != nil {
31+
panic(err)
32+
}
33+
defer os.Remove(file.Name())
34+
35+
if _, err := file.Write([]byte(dataPlain)); err != nil {
36+
panic(err)
37+
}
38+
if err := file.Close(); err != nil {
39+
panic(err)
40+
}
41+
42+
path := file.Name()
43+
loadCreds(path)
44+
45+
if !b.authenticate(base64encode("foo", "bar")) {
46+
t.Error("Should authenticate the user.")
47+
}
48+
49+
if b.authenticate(base64encode("foo", "badpass")) {
50+
t.Error("Should not authenticate the user.")
51+
}
52+
53+
if !b.authenticate(base64encode("care", "bear")) {
54+
t.Error("Should authenticate the user.")
55+
}
56+
57+
if b.authenticate(base64encode("foo2", "bar")) {
58+
t.Error("User doesn't exist.")
59+
}
60+
61+
}
62+
63+
func TestBasicAuthHashVerify(t *testing.T) {
64+
credMap = nil
65+
b := basicAuth()
66+
67+
file, err := ioutil.TempFile(os.TempDir(), "prefix")
68+
if err != nil {
69+
panic(err)
70+
}
71+
defer os.Remove(file.Name())
72+
73+
if _, err := file.Write([]byte(dataHashed)); err != nil {
74+
panic(err)
75+
}
76+
if err := file.Close(); err != nil {
77+
panic(err)
78+
}
79+
80+
path := file.Name()
81+
loadCreds(path)
82+
83+
if !b.authenticate(base64encode("foo", "bar")) {
84+
t.Error("Should authenticate the user.")
85+
}
86+
87+
if !b.authenticate(base64encode("foobar", "bar")) {
88+
t.Error("Should authenticate the user.")
89+
}
90+
91+
if !b.authenticate(base64encode("foo", "bar")) {
92+
t.Error("Should authenticate the user.")
93+
}
94+
95+
if b.authenticate(base64encode("foo", "badpass")) {
96+
t.Error("Should not authenticate the user.")
97+
}
98+
99+
if b.authenticate(base64encode("foo2", "bar")) {
100+
t.Error("User doesn't exist")
101+
}
102+
103+
}

ext/flogo/trigger/gorillamuxtrigger/trigger.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ func (t *RestTrigger) Init(runner action.Runner) {
140140

141141
t.configureTracer()
142142

143+
if isAuthEnabled(t.config.Settings) {
144+
setupAuth(t.config.Settings)
145+
}
146+
143147
//Check whether TLS (Transport Layer Security) is enabled for the trigger
144148
enableTLS := false
145149
serverCert := ""
@@ -511,7 +515,22 @@ func newActionHandler(rt *RestTrigger, handler *OptimizedHandler, method, url st
511515
log.Debugf("Found action' %+x'", action)
512516

513517
context := trigger.NewContextWithData(context.Background(), &trigger.ContextData{Attrs: startAttrs, HandlerCfg: handlerCfg})
514-
replyCode, replyData, err := rt.runner.Run(context, action, actionId, nil)
518+
519+
var replyCode int
520+
var replyData interface{}
521+
522+
allowed := true
523+
if isAuthEnabled(rt.config.Settings) {
524+
log.Debugf("Authenticating the request.")
525+
if !authenticate(r, rt.config.Settings) {
526+
replyCode = http.StatusForbidden
527+
allowed = false
528+
}
529+
}
530+
531+
if allowed {
532+
replyCode, replyData, err = rt.runner.Run(context, action, actionId, nil)
533+
}
515534

516535
if err != nil {
517536
serverSpan.SetTag("error", err.Error())

0 commit comments

Comments
 (0)