Skip to content

Commit

Permalink
add code.
Browse files Browse the repository at this point in the history
  • Loading branch information
trusch committed Oct 16, 2020
1 parent 25baf3c commit 7678984
Show file tree
Hide file tree
Showing 9 changed files with 1,626 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.build-image
.dummy-auth-image
.http-logger-image
.image
bin
*~
18 changes: 18 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
(auth) {
extauth {
endpoint http://auth:8000
copy-request-header Authorization
copy-response-header X-Token
set-header X-Original-Uri {uri}
set-header X-Original-Method {method}
}
}

http://localhost:2015 {
route {
import auth
reverse_proxy logger:8001
}
}


84 changes: 84 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
IMAGE=container.trusch.io/caddy-extauth/caddy:latest
DUMMY_AUTH_IMAGE=container.trusch.io/caddy-extauth/dummy-auth:latest
HTTP_LOGGER_IMAGE=container.trusch.io/caddy-extauth/http-logger:latest
BASE_IMAGE=gcr.io/distroless/base-debian10:latest
BUILD_IMAGE=container.trusch.io/caddy-extauth/builder
BUILD_BASE_IMAGE=golang:1.15

all: image dummy-auth-image http-logger-image

bin/caddy: extauth.go .build-image
mkdir -p bin
podman run \
--rm \
-v ./:/app \
-w /app \
-e GOOS=${GOOS} \
-e GOARCH=${GOARCH} \
-e GOARM=${GOARM} \
-v go-build-cache:/root/.cache/go-build \
-v go-mod-cache:/go/pkg/mod $(BUILD_IMAGE) bash -c \
"xcaddy build master --with github.com/trusch/caddy-extauth/pkg/extauth=/app && mv caddy bin/caddy"

bin/http-logger: cmd/http-logger/main.go
mkdir -p bin
podman run \
--rm \
-v ./:/app \
-w /app \
-e GOOS=${GOOS} \
-e GOARCH=${GOARCH} \
-e GOARM=${GOARM} \
-v go-build-cache:/root/.cache/go-build \
-v go-mod-cache:/go/pkg/mod $(BUILD_IMAGE) \
go build -o $@ ./cmd/http-logger

bin/dummy-auth: cmd/dummy-auth/main.go
mkdir -p bin
podman run \
--rm \
-v ./:/app \
-w /app \
-e GOOS=${GOOS} \
-e GOARCH=${GOARCH} \
-e GOARM=${GOARM} \
-v go-build-cache:/root/.cache/go-build \
-v go-mod-cache:/go/pkg/mod $(BUILD_IMAGE) \
go build -o $@ ./cmd/dummy-auth

build-image: .build-image
.build-image:
$(eval ID=$(shell buildah from $(BUILD_BASE_IMAGE)))
buildah run $(ID) go get -u github.com/caddyserver/xcaddy/cmd/xcaddy
buildah commit $(ID) $(BUILD_IMAGE)
buildah rm $(ID)
touch .build-image

image: .image
.image: bin/caddy
$(eval ID=$(shell buildah from $(BASE_IMAGE)))
buildah copy $(ID) bin/caddy /bin/
buildah commit $(ID) $(IMAGE)
buildah rm $(ID)
touch .image

dummy-auth-image: .dummy-auth-image
.dummy-auth-image: bin/dummy-auth
$(eval ID=$(shell buildah from $(BASE_IMAGE)))
buildah copy $(ID) bin/dummy-auth /bin/
buildah config --cmd dummy-auth $(ID)
buildah commit $(ID) $(DUMMY_AUTH_IMAGE)
buildah rm $(ID)
touch .dummy-auth-image

http-logger-image: .http-logger-image
.http-logger-image: bin/http-logger
$(eval ID=$(shell buildah from $(BASE_IMAGE)))
buildah copy $(ID) bin/http-logger /bin/
buildah config --cmd http-logger $(ID)
buildah commit $(ID) $(HTTP_LOGGER_IMAGE)
buildah rm $(ID)
touch .http-logger-image

clean:
-rm -r .build-image .image .http-logger-image .dummy-auth-image bin
22 changes: 22 additions & 0 deletions cmd/dummy-auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"net/http"

"github.com/rs/zerolog/log"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
val := r.Header.Get("Authorization")
if val == "secret" {
log.Info().Str("ip", r.RemoteAddr).Msg("success")
w.Header().Add("X-Token", "token")
w.WriteHeader(http.StatusOK)
return
}
log.Info().Str("ip", r.RemoteAddr).Msg("fail")
w.WriteHeader(http.StatusUnauthorized)
})
http.ListenAndServe(":8000", nil)
}
16 changes: 16 additions & 0 deletions cmd/http-logger/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"fmt"
"net/http"
"os"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("---")
r.Write(os.Stdout)
w.Write([]byte("You are authenticated!"))
})
http.ListenAndServe(":8001", nil)
}
27 changes: 27 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
version: "3.8"
configs:
caddyfile:
file: ./Caddyfile
services:
caddy:
image: container.trusch.io/caddy-extauth/caddy:latest
command:
- caddy
- run
- -config=/Caddyfile
configs:
- source: caddyfile
target: /Caddyfile
ports:
- "0.0.0.0:2015:2015"
links:
- auth
- logger

auth:
image: container.trusch.io/caddy-extauth/dummy-auth:latest

logger:
image: container.trusch.io/caddy-extauth/http-logger:latest

158 changes: 158 additions & 0 deletions extauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package extauth

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

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/rs/zerolog/log"
)

func init() {
caddy.RegisterModule(Middleware{})
httpcaddyfile.RegisterHandlerDirective("extauth", parseCaddyfile)
}

// Middleware implements an HTTP handler that calls an external service for authentication
type Middleware struct {
Endpoint string // http endpoint endpoint for the auth request
Timeout time.Duration // timeout for the auth request
CopyRequestHeaders []string // headers to copy from the incoming request to the auth request
CopyResponseHeaders []string // headers to copy from the auth response to the incoming request
SetHeaders map[string]string // headers to set in the auth request
httpClient *http.Client
}

// CaddyModule returns the Caddy module information.
func (Middleware) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.extauth",
New: func() caddy.Module { return new(Middleware) },
}
}

// Provision implements caddy.Provisioner.
func (m *Middleware) Provision(ctx caddy.Context) error {
m.httpClient = &http.Client{
Timeout: m.Timeout,
}
return nil
}

// Validate implements caddy.Validator.
func (m *Middleware) Validate() error {
if m.Endpoint == "" {
return errors.New("'endpoint' is required")
}
return nil
}

// ServeHTTP implements caddyhttp.MiddlewareHandler.
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
// create auth request
authReq, err := http.NewRequest(http.MethodGet, m.Endpoint, nil)
if err != nil {
return err
}

// copy headers from the incoming request
for _, name := range m.CopyRequestHeaders {
authReq.Header.Set(name, r.Header.Get(name))
}

// set additional headers
for key, val := range m.SetHeaders {
if val == "{http.request.uri}" {
val = r.URL.RequestURI()
}
if val == "{http.request.method}" {
val = r.Method
}
authReq.Header.Set(key, val)
}

// perform the request
resp, err := m.httpClient.Do(authReq)
if err != nil || resp.StatusCode != http.StatusOK {
// something went wrong or the server responded with something != 200 -> reject request with 401
log.Error().Str("url", r.URL.RequestURI()).Msg("failed to authenticate")
w.WriteHeader(http.StatusUnauthorized)
return nil
}
log.Info().Str("url", r.URL.RequestURI()).Msg("successfully authenticated")

// copy the user defined response headers to the incoming request before going on to the next handler in the chain
for _, name := range m.CopyResponseHeaders {
val := resp.Header.Get(name)
if val != "" {
r.Header.Set(name, resp.Header.Get(name))
}
}

// call next handler
return next.ServeHTTP(w, r)
}

// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) (err error) {
m.SetHeaders = make(map[string]string)
d.NextArg()
for d.NextBlock(0) {
switch d.Val() {
case "endpoint":
if !d.AllArgs(&m.Endpoint) {
return d.ArgErr()
}
case "timeout":
timeoutStr := ""
if !d.AllArgs(&timeoutStr) {
return d.ArgErr()
}
m.Timeout, err = time.ParseDuration(timeoutStr)
if err != nil {
return fmt.Errorf("can't parse timeout: %w", err)
}
case "copy-request-header":
name := ""
for d.Args(&name) {
m.CopyRequestHeaders = append(m.CopyRequestHeaders, name)
}
case "copy-response-header":
name := ""
for d.Args(&name) {
m.CopyResponseHeaders = append(m.CopyResponseHeaders, name)
}
case "set-header":
var key, val string
for d.AllArgs(&key, &val) {
m.SetHeaders[key] = val
}
default:
return d.Errf("%s not a valid extauth option", d.Val())
}
}
if m.Timeout == 0 {
m.Timeout = 1 * time.Second
}
return nil
}

// parseCaddyfile unmarshals tokens from h into a new Middleware.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var m Middleware
err := m.UnmarshalCaddyfile(h.Dispenser)
return m, err
}

// Interface guards
var (
_ caddy.Provisioner = (*Middleware)(nil)
_ caddy.Validator = (*Middleware)(nil)
_ caddyhttp.MiddlewareHandler = (*Middleware)(nil)
_ caddyfile.Unmarshaler = (*Middleware)(nil)
)
37 changes: 37 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module github.com/trusch/caddy-extauth

go 1.15

require (
cloud.google.com/go v0.54.0 // indirect
github.com/antlr/antlr4 v0.0.0-20201010232522-9e64dfc6e99f // indirect
github.com/caddyserver/caddy/v2 v2.2.1
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/google/cel-go v0.6.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/lucas-clemente/quic-go v0.18.1 // indirect
github.com/manifoldco/promptui v0.8.0 // indirect
github.com/marten-seemann/qtls-go1-15 v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/miekg/dns v1.1.33 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/prometheus/common v0.14.0 // indirect
github.com/prometheus/procfs v0.2.0 // indirect
github.com/rs/zerolog v1.20.0
github.com/smallstep/nosql v0.3.2 // indirect
github.com/urfave/cli v1.22.4 // indirect
go.step.sm/crypto v0.6.1 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee // indirect
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb // indirect
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 // indirect
google.golang.org/genproto v0.0.0-20201014134559-03b6142f0dc9 // indirect
google.golang.org/grpc v1.33.0 // indirect
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 // indirect
)
Loading

0 comments on commit 7678984

Please sign in to comment.