feat(security): add http basic auth

This commit is contained in:
eternal-flame-AD 2018-09-03 20:27:02 +08:00
parent ac754d93ac
commit 77a8946115
No known key found for this signature in database
GPG Key ID: 4CBBC48C2F2FF36B
6 changed files with 182 additions and 11 deletions

8
.env
View File

@ -37,4 +37,12 @@ APP_HTTP_NOTIFIER_USER=api:key-xxxxxxxxxxxxxxxxxxxxxxxxxx
# Defaults: localhost:25
APP_SMTP_NOTIFIER_HOST=localhost:25
# Authentication Method
# Defaults: none
# Values:
# - "none" : No authentication (defaults).
# - "basic": HTTP Basic authentication.
AUTH=none
# Authentication Parameter
AUTH_PARAM=username:password

View File

@ -1,31 +1,64 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"os"
"strconv"
"github.com/ncarlier/webhookd/pkg/auth"
)
// Config contain global configuration
type Config struct {
ListenAddr *string
NbWorkers *int
Debug *bool
Timeout *int
ScriptDir *string
ListenAddr *string
NbWorkers *int
Debug *bool
Timeout *int
ScriptDir *string
Authentication *string
AuthenticationParam *string
}
type authFlag struct {
selectedMethod auth.Method
}
func (c authFlag) Set(arg string) error {
fmt.Println(arg)
return errors.New("fall")
}
func (c authFlag) String() string {
return "test"
}
var config = &Config{
ListenAddr: flag.String("listen", getEnv("LISTEN_ADDR", ":8080"), "HTTP service address (e.g.address, ':8080')"),
NbWorkers: flag.Int("nb-workers", getIntEnv("NB_WORKERS", 2), "The number of workers to start"),
Debug: flag.Bool("debug", getBoolEnv("DEBUG", false), "Output debug logs"),
Timeout: flag.Int("timeout", getIntEnv("HOOK_TIMEOUT", 10), "Hook maximum delay before timeout (in second)"),
ScriptDir: flag.String("scripts", getEnv("SCRIPTS_DIR", "scripts"), "Scripts directory"),
ListenAddr: flag.String("listen", getEnv("LISTEN_ADDR", ":8080"), "HTTP service address (e.g.address, ':8080')"),
NbWorkers: flag.Int("nb-workers", getIntEnv("NB_WORKERS", 2), "The number of workers to start"),
Debug: flag.Bool("debug", getBoolEnv("DEBUG", false), "Output debug logs"),
Timeout: flag.Int("timeout", getIntEnv("HOOK_TIMEOUT", 10), "Hook maximum delay before timeout (in second)"),
ScriptDir: flag.String("scripts", getEnv("SCRIPTS_DIR", "scripts"), "Scripts directory"),
Authentication: flag.String("auth", getEnv("AUTH", "none"), ""),
AuthenticationParam: flag.String("authparam", getEnv("AUTH_PARAM", ""), func() string {
authdocwriter := bytes.NewBufferString("Authentication method. Available methods: ")
for key, method := range auth.AvailableMethods {
authdocwriter.WriteRune('\n')
authdocwriter.WriteString(key)
authdocwriter.WriteRune(':')
authdocwriter.WriteString(method.Usage())
}
return authdocwriter.String()
}()),
}
func init() {
flag.StringVar(config.ListenAddr, "l", *config.ListenAddr, "HTTP service (e.g address: ':8080')")
flag.BoolVar(config.Debug, "d", *config.Debug, "Output debug logs")
}
func getEnv(key, fallback string) string {

19
main.go
View File

@ -12,6 +12,7 @@ import (
"time"
"github.com/ncarlier/webhookd/pkg/api"
"github.com/ncarlier/webhookd/pkg/auth"
"github.com/ncarlier/webhookd/pkg/logger"
"github.com/ncarlier/webhookd/pkg/worker"
)
@ -34,6 +35,20 @@ func main() {
return
}
var authmethod auth.Method
name := *config.Authentication
if _, ok := auth.AvailableMethods[name]; ok {
authmethod = auth.AvailableMethods[name]
if err := authmethod.ParseParam(*config.AuthenticationParam); err != nil {
fmt.Println("Authentication parameter is not valid:", err.Error())
fmt.Println(authmethod.Usage())
os.Exit(2)
}
} else {
fmt.Println("Authentication name is not valid:", name)
os.Exit(2)
}
level := "info"
if *config.Debug {
level = "debug"
@ -41,6 +56,8 @@ func main() {
logger.Init(level)
logger.Debug.Println("Starting webhookd server...")
logger.Info.Println("Using Authentication:", name)
authmethod.Init(*config.Debug)
router := http.NewServeMux()
router.Handle("/", api.Index(*config.Timeout, *config.ScriptDir))
@ -52,7 +69,7 @@ func main() {
server := &http.Server{
Addr: *config.ListenAddr,
Handler: tracing(nextRequestID)(logging(logger.Debug)(router)),
Handler: authmethod.Middleware()(tracing(nextRequestID)(logging(logger.Debug)(router))),
ErrorLog: logger.Error,
}

19
pkg/auth/authmethod.go Normal file
View File

@ -0,0 +1,19 @@
package auth
import "net/http"
// Method an interface describing an authentication method
type Method interface {
Init(debug bool)
Usage() string
ParseParam(string) error
Middleware() func(http.Handler) http.Handler
}
var (
// AvailableMethods Returns a map of available auth methods
AvailableMethods = map[string]Method{
"none": new(noAuth),
"basic": new(basicAuth),
}
)

69
pkg/auth/basic.go Normal file
View File

@ -0,0 +1,69 @@
package auth
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/ncarlier/webhookd/pkg/logger"
)
type basicAuth struct {
username string
password string
debug bool
authheader string
}
func (c *basicAuth) Init(debug bool) {
c.debug = debug
logger.Warning.Println("\u001B[33mBasic Auth: Debug mode enabled. Might Leak sentitive information in log output.\u001B[0m")
}
func (c *basicAuth) Usage() string {
return "HTTP Basic Auth. Usage: -auth basic -authparam <username>:<password>[:<realm>] (example: -auth basic -authparam foo:bar)"
}
func (c *basicAuth) ParseParam(param string) error {
res := strings.Split(param, ":")
realm := "Authentication required."
switch len(res) {
case 3:
realm = res[2]
fallthrough
case 2:
c.username, c.password = res[0], res[1]
c.authheader = fmt.Sprintf("Basic realm=\"%s\"", realm)
return nil
}
return errors.New("Invalid Auth param")
}
// BasicAuth HTTP Basic Auth implementation
func (c *basicAuth) Middleware() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if username, password, ok := r.BasicAuth(); ok && username == c.username && password == c.password {
if c.debug {
logger.Debug.Printf("HTTP Basic Auth: %s:%s PASSED\n", username, password)
}
next.ServeHTTP(w, r)
} else if !ok {
if c.debug {
logger.Debug.Println("HTTP Basic Auth: Auth header not present.")
}
w.Header().Add("WWW-Authenticate", c.authheader)
w.WriteHeader(401)
w.Write([]byte("Authentication required."))
} else {
if c.debug {
logger.Debug.Printf("HTTP Basic Auth: Invalid credentials: %s:%s \n", username, password)
}
w.WriteHeader(403)
w.Write([]byte("Forbidden."))
}
})
}
}

25
pkg/auth/none.go Normal file
View File

@ -0,0 +1,25 @@
package auth
import (
"net/http"
)
type noAuth struct {
}
func (c *noAuth) Usage() string {
return "No Auth. Usage: --auth none"
}
func (c *noAuth) Init(_ bool) {}
func (c *noAuth) ParseParam(_ string) error {
return nil
}
// NoAuth A Nop Auth middleware
func (c *noAuth) Middleware() func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return h
}
}