Skip to content

Commit 68f8aee

Browse files
committed
Added basic authentication to reconfigure request (closes #61)
1 parent 9705805 commit 68f8aee

8 files changed

+200
-27
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ The following query arguments can be used to send as a *reconfigure* request to
8484
|serviceName |The name of the service. It must match the name stored in Consul. |Yes | |go-demo |
8585
|servicePath |The URL path of the service. Multiple values should be separated by a comma (,).|Yes (unless consulTemplatePath is present)||/api/v1/books|
8686
|skipCheck |Whether to skip adding proxy checks. This option is used only in the *default* mode.|No |false |true |
87+
|users |A comma-separated list of credentials(<user>:<pass>) for HTTP basic auth, which applies only to the service that will be reconfigured.|No||user1:pass1,user2:pass2|
8788

8889
### Remove
8990

TODO.md

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
# TODO
22

3-
## Certs
3+
## Authentication
44

5-
* Remove cert API (file and name)
6-
* Load existing certs from labels on init
7-
* Fix TODOs
8-
* Document
5+
[X] Make global authentication from env variables
6+
[X] Add per-service authentication
7+
[X] Propagate users from reconfigure server request
8+
[X] Add integration tests
9+
[ ] Test manually
10+
[X] Update README with env. var. *USERS*
11+
[X] Update README with reconfigure param *users*
12+
[ ] Update README with an article about authentication with the proxy and the listener
913

10-
* API: PUT /v1/docker-flow-proxy/cert
14+
## Videos
1115

12-
13-
```bash
14-
curl -i -XPUT \
15-
-d 'Content of my certificate PEM file' \
16-
$(docker-machine ip docker-flow-proxy-tests):8080/v1/docker-flow-proxy/cert?certName=viktor.pem
17-
```
18-
19-
## ACL Ordering
20-
21-
[X] Use ACL instead service name with templates
22-
[X] Save templates as ACL names
23-
[X] Load ACL templates in alphabetic order
24-
[X] Use ACL name to remove a template
25-
[ ] Confirm that server invokes reconfigure and remove with AclName param
26-
27-
## Content
28-
29-
* https://www.youtube.com/watch?v=oP0_H_UkkGA
16+
[ ] Wilde: https://www.youtube.com/watch?v=oP0_H_UkkGA

docker-compose-test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ services:
1515
environment:
1616
- CONSUL_ADDRESS=${HOST_IP}:8500
1717
- PROXY_INSTANCE_NAME=proxy-test-instance
18+
- USERS=user1:pass1,user2:pass2
19+
- STATS_USER=stats
20+
- STATS_PASS=pass
1821
ports:
1922
- 80:80
2023
- 443:443

integration_tests/integration_test.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,83 @@ func (s IntegrationTestSuite) Test_Reconfigure_MultiplePaths() {
8888
s.verifyReconfigure(2)
8989
}
9090

91+
func (s IntegrationTestSuite) Test_Global_Auth() {
92+
s.reconfigure("", "", "", "/v1/test")
93+
94+
// Returns status 401 if no auth is provided
95+
96+
testAddr := fmt.Sprintf("http://%s/v1/test", os.Getenv("DOCKER_IP"))
97+
log.Printf(">> Sending verify request to %s", testAddr)
98+
client := &http.Client{}
99+
request, _ := http.NewRequest("GET", testAddr, nil)
100+
resp, err := client.Do(request)
101+
102+
s.NoError(err)
103+
s.Equal(401, resp.StatusCode)
104+
105+
// Returns status 200 if auth is provided
106+
107+
request.SetBasicAuth("user1", "pass1")
108+
resp, err = client.Do(request)
109+
110+
s.NoError(err)
111+
s.Equal(200, resp.StatusCode)
112+
}
113+
114+
func (s IntegrationTestSuite) Test_Reconfigure_Auth() {
115+
var address string
116+
address = fmt.Sprintf(
117+
"http://%s:8080/v1/docker-flow-proxy/reconfigure?serviceName=%s&servicePath=%s&users=%s",
118+
os.Getenv("DOCKER_IP"),
119+
s.serviceName,
120+
"/v1/test",
121+
"serv-user-1:serv-pass-1",
122+
)
123+
log.Printf(">> Sending reconfigure request to %s", address)
124+
_, err := http.Get(address)
125+
s.NoError(err)
126+
127+
// Returns status 401 if no auth is provided
128+
129+
testAddr := fmt.Sprintf("http://%s/v1/test", os.Getenv("DOCKER_IP"))
130+
log.Printf(">> Sending verify request to %s", testAddr)
131+
client := &http.Client{}
132+
request, _ := http.NewRequest("GET", testAddr, nil)
133+
resp, err := client.Do(request)
134+
135+
s.NoError(err)
136+
s.Equal(401, resp.StatusCode)
137+
138+
// Returns status 200 if auth is provided
139+
140+
request.SetBasicAuth("serv-user-1", "serv-pass-1")
141+
resp, err = client.Do(request)
142+
143+
s.NoError(err)
144+
s.Equal(200, resp.StatusCode)
145+
}
146+
147+
func (s IntegrationTestSuite) Test_Stats_Auth() {
148+
// Returns status 401 if no auth is provided
149+
150+
testAddr := fmt.Sprintf("http://%s/admin?stats", os.Getenv("DOCKER_IP"))
151+
log.Printf(">> Sending verify request to %s", testAddr)
152+
client := &http.Client{}
153+
request, _ := http.NewRequest("GET", testAddr, nil)
154+
resp, err := client.Do(request)
155+
156+
s.NoError(err)
157+
s.Equal(401, resp.StatusCode)
158+
159+
// Returns status 200 if auth is provided
160+
161+
request.SetBasicAuth("stats", "pass")
162+
resp, err = client.Do(request)
163+
164+
s.NoError(err)
165+
s.Equal(200, resp.StatusCode)
166+
}
167+
91168
func (s IntegrationTestSuite) Test_Remove() {
92169
aclName := "my-acl"
93170
// curl "http://$(docker-machine tests):8080/v1/docker-flow-proxy/reconfigure?serviceName=test-service&servicePath=%s&aclName=%s"
@@ -216,7 +293,10 @@ func (s IntegrationTestSuite) Test_Certs() {
216293
func (s IntegrationTestSuite) verifyReconfigure(version int) {
217294
address := fmt.Sprintf("http://%s/v%d/test", os.Getenv("DOCKER_IP"), version)
218295
log.Printf(">> Sending verify request to %s", address)
219-
resp, err := http.Get(address)
296+
client := &http.Client{}
297+
request, _ := http.NewRequest("GET", address, nil)
298+
request.SetBasicAuth("user1", "pass1")
299+
resp, err := client.Do(request)
220300

221301
s.NoError(err)
222302
s.Equal(200, resp.StatusCode)
@@ -246,6 +326,17 @@ func (s IntegrationTestSuite) reconfigure(pathType, consulTemplateFePath, consul
246326
s.NoError(err)
247327
}
248328

329+
func (s IntegrationTestSuite) printConf() {
330+
configAddr := fmt.Sprintf(
331+
"http://%s:8080/v1/docker-flow-proxy/config",
332+
os.Getenv("DOCKER_IP"),
333+
)
334+
resp, _ := http.Get(configAddr)
335+
defer resp.Body.Close()
336+
body, _ := ioutil.ReadAll(resp.Body)
337+
println(string(body))
338+
}
339+
249340
// Suite
250341

251342
func TestGeneralIntegrationTestSuite(t *testing.T) {

reconfigure.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ type Reconfigure struct {
3232
ServiceReconfigure
3333
}
3434

35+
type User struct {
36+
Username string
37+
Password string
38+
}
39+
3540
type ServiceReconfigure struct {
3641
ServiceName string `short:"s" long:"service-name" required:"true" description:"The name of the service that should be reconfigured (e.g. my-service)."`
3742
ServiceColor string `short:"C" long:"service-color" description:"The color of the service release in case blue-green deployment is performed (e.g. blue)."`
@@ -47,6 +52,7 @@ type ServiceReconfigure struct {
4752
Acl string
4853
AclName string
4954
AclCondition string
55+
Users []User
5056
FullServiceName string
5157
Distribute bool
5258
LookupRetry int
@@ -303,7 +309,14 @@ func (m *Reconfigure) getTemplateFromGo(sr ServiceReconfigure) (frontend, backen
303309
srcFront := `
304310
acl url_{{.ServiceName}}{{range .ServicePath}} {{$.PathType}} {{.}}{{end}}{{.Acl}}
305311
use_backend {{.AclName}}-be if url_{{.ServiceName}}{{.AclCondition}}`
306-
srcBack := `backend {{.AclName}}-be
312+
srcBack := ""
313+
if len(sr.Users) > 0 {
314+
srcBack += `userlist {{.ServiceName}}Users{{range .Users}}
315+
user {{.Username}} insecure-password {{.Password}}{{end}}
316+
317+
`
318+
}
319+
srcBack += `backend {{.AclName}}-be
307320
mode http`
308321
if strings.EqualFold(sr.Mode, "service") || strings.EqualFold(sr.Mode, "swarm") {
309322
srcBack += `
@@ -314,7 +327,11 @@ func (m *Reconfigure) getTemplateFromGo(sr ServiceReconfigure) (frontend, backen
314327
server {{"{{$e.Node}}_{{$i}}_{{$e.Port}} {{$e.Address}}:{{$e.Port}}"}}{{if eq .SkipCheck false}} check{{end}}
315328
{{"{{end}}"}}`
316329
}
317-
if len(os.Getenv("USERS")) > 0 {
330+
if len(sr.Users) > 0 {
331+
srcBack += `
332+
acl {{.ServiceName}}UsersAcl http_auth({{.ServiceName}}Users)
333+
http-request auth realm {{.ServiceName}}Realm if !{{.ServiceName}}UsersAcl`
334+
} else if len(os.Getenv("USERS")) > 0 {
318335
srcBack += `
319336
acl defaultUsersAcl http_auth(defaultUsers)
320337
http-request auth realm defaultRealm if !defaultUsersAcl`

reconfigure_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,28 @@ func (s ReconfigureTestSuite) Test_GetTemplate_AddsHttpAuth_WhenUsersEnvIsPresen
9090
s.Equal(expected, back)
9191
}
9292

93+
func (s ReconfigureTestSuite) Test_GetTemplate_AddsHttpAuth_WhenUsersIsPresent() {
94+
s.reconfigure.Users = []User{
95+
{ Username: "user-1", Password: "pass-1" },
96+
{ Username: "user-2", Password: "pass-2" },
97+
}
98+
expected := `userlist myServiceUsers
99+
user user-1 insecure-password pass-1
100+
user user-2 insecure-password pass-2
101+
102+
backend myService-be
103+
mode http
104+
{{range $i, $e := service "myService" "any"}}
105+
server {{$e.Node}}_{{$i}}_{{$e.Port}} {{$e.Address}}:{{$e.Port}} check
106+
{{end}}
107+
acl myServiceUsersAcl http_auth(myServiceUsers)
108+
http-request auth realm myServiceRealm if !myServiceUsersAcl`
109+
110+
_, back, _ := s.reconfigure.GetTemplates(s.reconfigure.ServiceReconfigure)
111+
112+
s.Equal(expected, back)
113+
}
114+
93115
func (s ReconfigureTestSuite) Test_GetTemplate_ReturnsFormattedContent_WhenModeIsSwarm() {
94116
modes := []string{"service", "sWARm"}
95117
for _, mode := range modes {
@@ -122,6 +144,28 @@ func (s ReconfigureTestSuite) Test_GetTemplate_AddsHttpAuth_WhenModeIsSwarmAndUs
122144
s.Equal(expected, actual)
123145
}
124146

147+
func (s ReconfigureTestSuite) Test_GetTemplate_AddsHttpAuth_WhenModeIsSwarmAndUsersIsPresent() {
148+
s.reconfigure.Users = []User{
149+
{ Username: "user-1", Password: "pass-1" },
150+
{ Username: "user-2", Password: "pass-2" },
151+
}
152+
s.reconfigure.ServiceReconfigure.Mode = "swarm"
153+
s.reconfigure.ServiceReconfigure.Port = "1234"
154+
expected := `userlist myServiceUsers
155+
user user-1 insecure-password pass-1
156+
user user-2 insecure-password pass-2
157+
158+
backend myService-be
159+
mode http
160+
server myService myService:1234
161+
acl myServiceUsersAcl http_auth(myServiceUsers)
162+
http-request auth realm myServiceRealm if !myServiceUsersAcl`
163+
164+
_, actual, _ := s.reconfigure.GetTemplates(s.reconfigure.ServiceReconfigure)
165+
166+
s.Equal(expected, actual)
167+
}
168+
125169
func (s ReconfigureTestSuite) Test_GetTemplate_AddsHost() {
126170
s.ConsulTemplateFe = `
127171
acl url_myService path_beg path/to/my/service/api path_beg path/to/my/other/service/api

server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type Response struct {
4747
Mode string
4848
Port string
4949
Distribute bool
50+
Users []User
5051
}
5152

5253
func (m *Serve) Execute(args []string) error {
@@ -140,6 +141,13 @@ func (m *Serve) reconfigure(w http.ResponseWriter, req *http.Request) {
140141
if len(req.URL.Query().Get("distribute")) > 0 {
141142
sr.Distribute, _ = strconv.ParseBool(req.URL.Query().Get("distribute"))
142143
}
144+
if len(req.URL.Query().Get("users")) > 0 {
145+
users := strings.Split(req.URL.Query().Get("users"), ",")
146+
for _, user := range users {
147+
userPass := strings.Split(user, ":")
148+
sr.Users = append(sr.Users, User{Username: userPass[0], Password: userPass[1]})
149+
}
150+
}
143151
response := Response{
144152
Status: "OK",
145153
ServiceName: sr.ServiceName,
@@ -154,6 +162,7 @@ func (m *Serve) reconfigure(w http.ResponseWriter, req *http.Request) {
154162
Mode: sr.Mode,
155163
Port: sr.Port,
156164
Distribute: sr.Distribute,
165+
Users: sr.Users,
157166
}
158167
if m.isValidReconf(sr.ServiceName, sr.ServicePath, sr.ConsulTemplateFePath) {
159168
if (strings.EqualFold("service", m.Mode) || strings.EqualFold("swarm", m.Mode)) && len(sr.Port) == 0 {

server_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,27 @@ func (s *ServerTestSuite) Test_ServeHTTP_ReturnsJsonWithPathType_WhenPresent() {
415415
s.ResponseWriter.AssertCalled(s.T(), "Write", []byte(expected))
416416
}
417417

418+
func (s *ServerTestSuite) Test_ServeHTTP_ReturnsJsonWithUsers_WhenPresent() {
419+
users := []User {
420+
{ Username: "user1", Password: "pass1" },
421+
{ Username: "user2", Password: "pass2" },
422+
}
423+
req, _ := http.NewRequest("GET", s.ReconfigureUrl+"&users=user1:pass1,user2:pass2", nil)
424+
expected, _ := json.Marshal(Response{
425+
Status: "OK",
426+
ServiceName: s.ServiceName,
427+
ServiceColor: s.ServiceColor,
428+
ServicePath: s.ServicePath,
429+
ServiceDomain: s.ServiceDomain,
430+
Users: users,
431+
})
432+
433+
srv := Serve{}
434+
srv.ServeHTTP(s.ResponseWriter, req)
435+
436+
s.ResponseWriter.AssertCalled(s.T(), "Write", []byte(expected))
437+
}
438+
418439
func (s *ServerTestSuite) Test_ServeHTTP_ReturnsJsonWithPort_WhenPresent() {
419440
port := "1234"
420441
mode := "swaRM"

0 commit comments

Comments
 (0)