Skip to content

Commit

Permalink
refactor and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jandelgado committed Apr 20, 2021
1 parent 7697c1f commit 35b8202
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 41 deletions.
17 changes: 14 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main

name: run tests
jobs:
Expand All @@ -27,5 +27,16 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.15.x
- name: Run tests
run: go test -v github.com/jandelgado/rabbitmq-http-auth/pkg
run: make test
- name: Convert coverage.out to coverage.lcov
uses: jandelgado/gcov2lcov-action@v1.0.6
- name: Coveralls
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ secrets.github_token }}
path-to-lcov: coverage.lcov
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
*~
cmd/example/rabbitmq-http-auth
coverage.out

5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ FROM golang:1.16 as builder

RUN mkdir -p /build
COPY . /build
RUN cd /build/cmd && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags '-w -extldflags "-static"' -o rabbitmq-http-auth .
RUN cd /build && make build

FROM scratch

COPY --from=builder /build/cmd/rabbitmq-http-auth /
COPY --from=builder /build/cmd/example/rabbitmq-http-auth /

WORKDIR /app
ENTRYPOINT ["/rabbitmq-http-auth"]
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.PHONY: build test run

build:
cd cmd/example && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -a -tags netgo -ldflags '-w -extldflags "-static"' \
-o rabbitmq-http-auth .
run:
cd cmd/example && ./rabbitmq-http-auth

test:
go test -v -cover -coverprofile=coverage.out github.com/jandelgado/rabbitmq-http-auth/pkg
go tool cover -func=coverage.out

86 changes: 77 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,88 @@
# RabbitMQ HTTP Auth Backend in Go

Package and example service to build a RabbitMQ HTTP service for use with
[![run tests](https://github.com/jandelgado/rabbitmq-http-auth/actions/workflows/test.yml/badge.svg)](https://github.com/jandelgado/rabbitmq-http-auth/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/jandelgado/rabbitmq-http-auth/badge.svg?branch=main)](https://coveralls.io/github/jandelgado/rabbitmq-http-auth?branch=main)

Package and example service to build a RabbitMQ HTTP Auth service for use with
the RabbitMQ "HTTP Auth Backend" (actually it is an AuthN/AuthZ backend).

For details see https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/rabbitmq_auth_backend_http

<!-- vim-markdown-toc GFM -->

* [Build your own service](#build-your-own-service)
* [Test it](#test-it)
* [Test with RabbitMQ](#test-with-rabbitmq)
* [Author & License](#author--license)

<!-- vim-markdown-toc -->

## Build your own service

TODO
To build an RabbitMQ HTTP Auth Backend, you just need to implement the provided
`Authenticator` interface, which will be called by `POST` requests to the paths
`/auth/user`, `/auth/vhost`, `/auth/topic` and `/auth/resource`:

```go
type Decision bool

type Authenticator interface {
// User authenticates the given user. In addition to the decision, the tags
// associated with the user are returned.
User(username, password string) (Decision, string)
// VHost checks if the given user/ip combination is allowed to access the
// vhosts
VHost(username, vhost, ip string) Decision
// Resource checks if the given user has access to the presented resource
Resource(username, vhost, resource, name, permission string) Decision
// Topic checks if the given user has access to the presented topic when
// using topic authorization (https://www.rabbitmq.com/access-control.html#topic-authorisation)
Topic(username, vhost, resource, name, permission, routing_key string) Decision
}
```

Start a web server using your authenticator and the http router provided
by the `rabbitmqauth.AuthServer.NewRouter()` function like:

```go
package main

import (
"fmt"
"net/http"
"time"

auth "github.com/jandelgado/rabbitmq-http-auth/pkg"
)

const httpReadTimeout = 10 * time.Second
const httpWriteTimeout = 10 * time.Second

func main() {
authenticator := NewLogInterceptingAuthenticator(DemoAuthenticator{})
s := auth.NewAuthServer(authenticator)

srv := &http.Server{
Handler: s.NewRouter(),
Addr: fmt.Sprintf(":%d", 8000),
WriteTimeout: httpWriteTimeout,
ReadTimeout: httpReadTimeout,
}

err := srv.ListenAndServe()

if err != nil {
panic(err)
}
}
```

Have a look at the [example](cmd/example) for a complete example.

## Testing it
## Test it

After starting the demo app either manually or by running
`docker docker run --rm -ti -p8000:8000 rabbitmq-http-auth:latest`, we
can test the service by issueing POST requests to the `User` endpoint ,
for example:
Start the example by running `make build && make run` and then test the service
by issueing POST requests to the `User` endpoint , for example:

```sh
$ curl -XPOST localhost:8000/auth/user -d "username=guest&pasword=test"
Expand All @@ -23,8 +91,8 @@ $ curl -XPOST localhost:8000/auth/user -d "username=john&pasword=test"
deny
```

Since the `DemoAuthenticator` only allows the `guest` user, this is the
expected result.
Since the `DemoAuthenticator` only allows the `guest` user (but with any
password), this is the expected result.

## Test with RabbitMQ

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
// rabbitmq-http-auth - log authentication requests
// (c) copyright 2021 by Jan Delgado
package rabbitmqauth
package main

import (
"log"

auth "github.com/jandelgado/rabbitmq-http-auth/pkg"
)

type LogInterceptingAuthenticator struct {
authenticator Authenticator
authenticator auth.Authenticator
}

func NewLogInterceptingAuthenticator(authenticator Authenticator) Authenticator {
func NewLogInterceptingAuthenticator(authenticator auth.Authenticator) auth.Authenticator {
return LogInterceptingAuthenticator{authenticator}
}

func (s LogInterceptingAuthenticator) String() string {
return "LogInterceptingAuthenticator"
}

func (s LogInterceptingAuthenticator) User(username, password string) (Decision, string) {
func (s LogInterceptingAuthenticator) User(username, password string) (auth.Decision, string) {
res, tags := s.authenticator.User(username, password)
log.Printf("auth user(u=%s) -> %v [%s]", username, res, tags)
return res, tags
}

func (s LogInterceptingAuthenticator) VHost(username, vhost, ip string) Decision {
func (s LogInterceptingAuthenticator) VHost(username, vhost, ip string) auth.Decision {
res := s.authenticator.VHost(username, vhost, ip)
log.Printf("auth vhost(u=%s,v=%s,i=%s) -> %v", username, vhost, ip, res)
return res
}

func (s LogInterceptingAuthenticator) Resource(username, vhost, resource, name, permission string) Decision {
func (s LogInterceptingAuthenticator) Resource(username, vhost, resource, name, permission string) auth.Decision {
res := s.authenticator.Resource(username, vhost, resource, name, permission)
log.Printf("auth resource(u=%s,v=%s,r=%s,n=%s,p=%s) -> %v",
username, vhost, resource, name, permission, res)
return res
}

func (s LogInterceptingAuthenticator) Topic(username, vhost, resource, name, permission, routing_key string) Decision {
func (s LogInterceptingAuthenticator) Topic(username, vhost, resource, name, permission, routing_key string) auth.Decision {
res := s.authenticator.Topic(username, vhost, resource, name, permission, routing_key)
log.Printf("auth topic(u=%s,v=%s,r=%s,n=%s,p=%s,k=%s) -> %v",
username, vhost, resource, name, permission, routing_key, res)
Expand Down
3 changes: 1 addition & 2 deletions cmd/main.go → cmd/example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ const httpReadTimeout = 10 * time.Second
const httpWriteTimeout = 10 * time.Second

func main() {
a := DemoAuthenticator{}
authenticator := auth.NewLogInterceptingAuthenticator(a)
authenticator := NewLogInterceptingAuthenticator(DemoAuthenticator{})
s := auth.NewAuthServer(authenticator)

srv := &http.Server{
Expand Down
1 change: 0 additions & 1 deletion demo/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ services:
rabbitmq:
image: rabbitmq:3.8-management-alpine
volumes:
- ./definitions.json:/etc/rabbitmq/definitions.json:z
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:z
ports:
- 5672:5672
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/jandelgado/rabbitmq-http-auth

go 1.15

require github.com/gorilla/mux v1.8.0
require github.com/stretchr/testify v1.7.0
13 changes: 11 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
27 changes: 16 additions & 11 deletions pkg/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,8 @@ package rabbitmqauth
import (
"fmt"
"net/http"
"time"

"github.com/gorilla/mux"
)

const httpReadTimeout = 15 * time.Second
const httpWriteTimeout = 45 * time.Second

type AuthServer struct {
authenticator Authenticator
}
Expand Down Expand Up @@ -66,11 +60,22 @@ func (s *AuthServer) resourceHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", res)
}

func postHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
handler(w, r)
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
_, _ = w.Write([]byte("405 method not allowed"))
}
}
}

func (s *AuthServer) NewRouter() http.Handler {
router := mux.NewRouter()
router.HandleFunc("/auth/user", s.userHandler).Methods("POST")
router.HandleFunc("/auth/vhost", s.vhostHandler).Methods("POST")
router.HandleFunc("/auth/resource", s.resourceHandler).Methods("POST")
router.HandleFunc("/auth/topic", s.topicHandler).Methods("POST")
router := http.NewServeMux()
router.HandleFunc("/auth/user", postHandler(s.userHandler))
router.HandleFunc("/auth/vhost", postHandler(s.vhostHandler))
router.HandleFunc("/auth/resource", postHandler(s.resourceHandler))
router.HandleFunc("/auth/topic", postHandler(s.topicHandler))
return router
}

0 comments on commit 35b8202

Please sign in to comment.