Skip to content

Commit

Permalink
feat(control server authentication): add basic http auth (#2423)
Browse files Browse the repository at this point in the history
  • Loading branch information
joejose97 authored Sep 11, 2024
1 parent 41cd9ce commit 3191b3c
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 2 deletions.
36 changes: 36 additions & 0 deletions internal/server/middlewares/auth/basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package auth

import (
"crypto/sha256"
"crypto/subtle"
"net/http"
)

type basicAuthMethod struct {
authDigest [32]byte
}

func newBasicAuthMethod(username, password string) *basicAuthMethod {
return &basicAuthMethod{
authDigest: sha256.Sum256([]byte(username + password)),
}
}

// equal returns true if another auth checker is equal.
// This is used to deduplicate checkers for a particular route.
func (a *basicAuthMethod) equal(other authorizationChecker) bool {
otherBasicMethod, ok := other.(*basicAuthMethod)
if !ok {
return false
}
return a.authDigest == otherBasicMethod.authDigest
}

func (a *basicAuthMethod) isAuthorized(request *http.Request) bool {
username, password, ok := request.BasicAuth()
if !ok {
return false
}
requestAuthDigest := sha256.Sum256([]byte(username + password))
return subtle.ConstantTimeCompare(a.authDigest[:], requestAuthDigest[:]) == 1
}
2 changes: 2 additions & 0 deletions internal/server/middlewares/auth/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func settingsToLookupMap(settings Settings) (routeToRoles map[string][]internalR
checker = newNoneMethod()
case AuthAPIKey:
checker = newAPIKeyMethod(role.APIKey)
case AuthBasic:
checker = newBasicAuthMethod(role.Username, role.Password)
default:
return nil, fmt.Errorf("%w: %s", ErrMethodNotSupported, role.Auth)
}
Expand Down
16 changes: 14 additions & 2 deletions internal/server/middlewares/auth/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (s Settings) ToLinesNode() (node *gotree.Node) {
const (
AuthNone = "none"
AuthAPIKey = "apikey"
AuthBasic = "basic"
)

// Role contains the role name, authentication method name and
Expand All @@ -90,6 +91,10 @@ type Role struct {
Auth string
// APIKey is the API key to use when using the 'apikey' authentication.
APIKey string
// Username for HTTP Basic authentication method.
Username string
// Password for HTTP Basic authentication method.
Password string
// Routes is a list of routes that the role can access in the format
// "HTTP_METHOD PATH", for example "GET /v1/vpn/status"
Routes []string
Expand All @@ -98,17 +103,24 @@ type Role struct {
var (
ErrMethodNotSupported = errors.New("authentication method not supported")
ErrAPIKeyEmpty = errors.New("api key is empty")
ErrBasicUsernameEmpty = errors.New("username is empty")
ErrBasicPasswordEmpty = errors.New("password is empty")
ErrRouteNotSupported = errors.New("route not supported by the control server")
)

func (r Role) validate() (err error) {
err = validate.IsOneOf(r.Auth, AuthNone, AuthAPIKey)
err = validate.IsOneOf(r.Auth, AuthNone, AuthAPIKey, AuthBasic)
if err != nil {
return fmt.Errorf("%w: %s", ErrMethodNotSupported, r.Auth)
}

if r.Auth == AuthAPIKey && r.APIKey == "" {
switch {
case r.Auth == AuthAPIKey && r.APIKey == "":
return fmt.Errorf("for role %s: %w", r.Name, ErrAPIKeyEmpty)
case r.Auth == AuthBasic && r.Username == "":
return fmt.Errorf("for role %s: %w", r.Name, ErrBasicUsernameEmpty)
case r.Auth == AuthBasic && r.Password == "":
return fmt.Errorf("for role %s: %w", r.Name, ErrBasicPasswordEmpty)
}

for i, route := range r.Routes {
Expand Down

0 comments on commit 3191b3c

Please sign in to comment.