Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jandelgado committed Apr 18, 2021
0 parents commit 7697c1f
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 0 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
on:
push:
branches:
- master
pull_request:
branches:
- master

name: run tests
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.15.x
- name: Checkout code
uses: actions/checkout@v2
- name: Run linters
uses: golangci/golangci-lint-action@v2
with:
version: v1.29

test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run tests
run: go test -v github.com/jandelgado/rabbitmq-http-auth/pkg
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*~
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ARG registry
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 .

FROM scratch

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

WORKDIR /app
ENTRYPOINT ["/rabbitmq-http-auth"]
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# RabbitMQ HTTP Auth Backend in Go

Package and example service to build a RabbitMQ HTTP 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

## Build your own service

TODO

## Testing 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:

```sh
$ curl -XPOST localhost:8000/auth/user -d "username=guest&pasword=test"
allow [management administrator demo]
$ 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.

## Test with RabbitMQ

A docker-compose file is provided which sets up a RabbitMQ broker with the
authentication service configured. To test it, run:

```sh
$ cd demo && docker-compose up
```

Then in another console, try to publish a message using [rabtap](TODO)
```sh
$ echo "hello" | rabtap pub --uri amqp://guest:123@localhost:5672 --exchange amq.topic --routingkey "#"
```

In the docker-compose log, should see the authenticator logging the request:
```
auth-http_1 | 2021/04/18 21:28:01 auth user(u=guest) -> allow [management administrator demo]
```

As the `DemoAuthenticator` allows any password for the guest user, you can
try to change the password in the `rabtap` command or try to login on the
[management console](http://localhost:15672) with any password.

## Author & License

(c) Copyright 2021 by Jan Delgado. Licence: MIT

31 changes: 31 additions & 0 deletions cmd/demo_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// rabbitmq-http-auth - demo implementation of the Authenticator interface,
// which allows the user guest with ANY password to be authorized for
// everything.
// (c) copyright 2021 by Jan Delgado
package main

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

type DemoAuthenticator struct{}

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

func (s DemoAuthenticator) Resource(username, vhost, resource, name, permission string) auth.Decision {
return true
}

func (s DemoAuthenticator) User(username, password string) (auth.Decision, string) {
return username == "guest", "management administrator demo"
}

func (s DemoAuthenticator) VHost(username, vhost, ip string) auth.Decision {
return true
}

func (s DemoAuthenticator) Topic(username, vhost, resource, name, permission, routing_key string) auth.Decision {
return true
}
33 changes: 33 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// rabbitmq-http-auth - exmple authentication service using a demo authenticator
// (c) copyright 2021 by Jan Delgadoauthenticator}
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() {
a := DemoAuthenticator{}
authenticator := auth.NewLogInterceptingAuthenticator(a)
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)
}
}
16 changes: 16 additions & 0 deletions demo/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3'
services:
auth-http:
build:
context: ../
ports:
- "8000:8000"

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
- 15672:15672
14 changes: 14 additions & 0 deletions demo/rabbitmq.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
log.default.level = debug
loopback_users = none

listeners.tcp.default = 5672
management.tcp.port = 15672


auth_backends.1 = http
auth_http.http_method = post
auth_http.user_path = http://auth-http:8000/auth/user
auth_http.vhost_path = http://auth-http:8000/auth/vhost
auth_http.resource_path = http://auth-http:8000/auth/resource
auth_http.topic_path = http://auth-http:8000/auth/topic

5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/jandelgado/rabbitmq-http-auth

go 1.15

require github.com/gorilla/mux v1.8.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
30 changes: 30 additions & 0 deletions pkg/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// rabbitmq-http-auth - Authenicator interface
// (c) copyright 2021 by Jan Delgado
package rabbitmqauth

type Decision bool

const (
Allow Decision = true
Deny Decision = false
)

// Authenticator instances make the actual decisions on authentication
// requests by RabboitMQ. Every function returns the authentication decision,
// which is always Allow or Deny.
//
// See https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/rabbitmq_auth_backend_http
// for a detailed description.
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
}
45 changes: 45 additions & 0 deletions pkg/log_intercepting_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// rabbitmq-http-auth - log authentication requests
// (c) copyright 2021 by Jan Delgado
package rabbitmqauth

import (
"log"
)

type LogInterceptingAuthenticator struct {
authenticator Authenticator
}

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

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

func (s LogInterceptingAuthenticator) User(username, password string) (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 {
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 {
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 {
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)
return res
}
76 changes: 76 additions & 0 deletions pkg/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// rabbitmq-http-auth - http router
// (c) copyright 2021 by Jan Delgado
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
}

func NewAuthServer(authenticator Authenticator) AuthServer {
return AuthServer{authenticator}
}

func (s Decision) String() string {
if s {
return "allow"
}
return "deny"
}

func validatePostArgs(args []string, r *http.Request) map[string]string {
result := map[string]string{}
for _, s := range args {
result[s] = r.PostFormValue(s)
}
return result
}

func (s *AuthServer) userHandler(w http.ResponseWriter, r *http.Request) {
args := validatePostArgs([]string{"username", "password"}, r)
res, tags := s.authenticator.User(args["username"], args["password"])
if res {
fmt.Fprintf(w, "%s [%s]", res, tags)
} else {
fmt.Fprintf(w, "%s", res)
}
}

func (s *AuthServer) vhostHandler(w http.ResponseWriter, r *http.Request) {
args := validatePostArgs([]string{"username", "vhost", "ip"}, r)
res := s.authenticator.VHost(args["username"], args["vhost"], args["ip"])
fmt.Fprintf(w, "%s", res)
}

func (s *AuthServer) topicHandler(w http.ResponseWriter, r *http.Request) {
args := validatePostArgs([]string{"username", "vhost", "resource", "name", "permission", "routing_key"}, r)
res := s.authenticator.Topic(args["username"], args["vhost"],
args["resource"], args["name"], args["permission"], args["routing_key"])
fmt.Fprintf(w, "%s", res)
}

func (s *AuthServer) resourceHandler(w http.ResponseWriter, r *http.Request) {
args := validatePostArgs([]string{"username", "vhost", "resource", "name", "permission"}, r)
res := s.authenticator.Resource(args["username"], args["vhost"],
args["resource"], args["name"], args["permission"])
fmt.Fprintf(w, "%s", res)
}

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")
return router
}
6 changes: 6 additions & 0 deletions pkg/serve_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package rabbitmqauth

import "testing"

func TestUserRequestIsServerd(t *testing.T) {
}

0 comments on commit 7697c1f

Please sign in to comment.