mirror of
https://github.com/prymitive/karma
synced 2026-02-13 20:59:53 +00:00
feat(auth): allow reading user groups from headers
Closes: #3361 Signed-off-by: Taavi Väänänen <hi@taavi.wtf>
This commit is contained in:
committed by
Łukasz Mierzwa
parent
bda2fcbc29
commit
9b89b4ee2a
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/config"
|
||||
"github.com/prymitive/karma/internal/regex"
|
||||
@@ -21,7 +22,7 @@ func userGroups(username string) []string {
|
||||
return groups
|
||||
}
|
||||
|
||||
func headerAuth(name, valueRegex string, allowBypass []string) func(next http.Handler) http.Handler {
|
||||
func headerAuth(name, valueRegex, groupName, groupValueRegex, groupValueSeparator string, allowBypass []string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if slices.StringInSlice(allowBypass, r.URL.Path) {
|
||||
@@ -38,16 +39,33 @@ func headerAuth(name, valueRegex string, allowBypass []string) func(next http.Ha
|
||||
|
||||
reg := regex.MustCompileAnchored(valueRegex)
|
||||
matches := reg.FindAllStringSubmatch(user, 1)
|
||||
if len(matches) > 0 && len(matches[0]) > 1 {
|
||||
userName := matches[0][1]
|
||||
ctx := context.WithValue(r.Context(), authUserKey("user"), userName)
|
||||
ctx = context.WithValue(ctx, authUserKey("groups"), userGroups(userName))
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
if len(matches) == 0 || len(matches[0]) <= 1 {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte("Access denied\n"))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte("Access denied\n"))
|
||||
|
||||
userName := matches[0][1]
|
||||
groups := userGroups(userName)
|
||||
|
||||
if groupName != "" {
|
||||
rawGroups := []string{r.Header.Get(groupName)}
|
||||
if groupValueSeparator != "" {
|
||||
rawGroups = strings.Split(rawGroups[0], groupValueSeparator)
|
||||
}
|
||||
|
||||
groupRegex := regex.MustCompileAnchored(groupValueRegex)
|
||||
for _, group := range rawGroups {
|
||||
groupMatches := groupRegex.FindAllStringSubmatch(group, 1)
|
||||
if len(groupMatches) != 0 && len(groupMatches[0]) > 1 {
|
||||
groups = append(groups, groupMatches[0][1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), authUserKey("user"), userName)
|
||||
ctx = context.WithValue(ctx, authUserKey("groups"), groups)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,14 @@ func setupRouter(router *chi.Mux, historyPoller *historyPoller) {
|
||||
}
|
||||
if config.Config.Authentication.Header.Name != "" {
|
||||
config.Config.Authentication.Enabled = true
|
||||
router.Use(headerAuth(config.Config.Authentication.Header.Name, config.Config.Authentication.Header.ValueRegex, allowAuthBypass))
|
||||
router.Use(headerAuth(
|
||||
config.Config.Authentication.Header.Name,
|
||||
config.Config.Authentication.Header.ValueRegex,
|
||||
config.Config.Authentication.Header.GroupName,
|
||||
config.Config.Authentication.Header.GroupValueRegex,
|
||||
config.Config.Authentication.Header.GroupValueSeparator,
|
||||
allowAuthBypass,
|
||||
))
|
||||
} else if len(config.Config.Authentication.BasicAuth.Users) > 0 {
|
||||
config.Config.Authentication.Enabled = true
|
||||
users := map[string]string{}
|
||||
|
||||
@@ -83,6 +83,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: X-Auth"
|
||||
level=info msg=" value_re: ^(.+)$"
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -11,6 +11,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users:"
|
||||
level=info msg=" - username: number"
|
||||
|
||||
@@ -11,6 +11,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -11,6 +11,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -11,6 +11,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -13,6 +13,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -11,6 +11,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -11,6 +11,9 @@ level=info msg="authentication:"
|
||||
level=info msg=" header:"
|
||||
level=info msg=" name: \"\""
|
||||
level=info msg=" value_re: \"\""
|
||||
level=info msg=" group_name: \"\""
|
||||
level=info msg=" group_value_re: \"\""
|
||||
level=info msg=" group_value_separator: \"\""
|
||||
level=info msg=" basicAuth:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="authorization:"
|
||||
|
||||
@@ -61,6 +61,14 @@ authentication:
|
||||
group will be used as the silence form author field.
|
||||
All regexes are anchored.
|
||||
This option must be set when `authentication:users:header:name` is set.
|
||||
- `authentication:users:header:group_name` - name of the header that will
|
||||
contain any groups the user has.
|
||||
- `authentication:users:header:group_value_re` - Similar to
|
||||
`authentication:users:header:value_re`, but for groups instead of usernames.
|
||||
Must be set when `authentication:users:header:group_name` is set.
|
||||
- `authentication:users:header:group_value_separator` - If set, this will be
|
||||
used to split the group header to multiple group names. The split is done
|
||||
before evaluating the value regex. Optional.
|
||||
- `authentication:users` - list of users (username & password) allowed to login.
|
||||
Passwords are stored plain without any encryption.
|
||||
When set HTTP basic authentication will be used.
|
||||
|
||||
@@ -26,6 +26,9 @@ func testReadConfig(t *testing.T) {
|
||||
header:
|
||||
name: ""
|
||||
value_re: ""
|
||||
group_name: ""
|
||||
group_value_re: ""
|
||||
group_value_separator: ""
|
||||
basicAuth:
|
||||
users: []
|
||||
authorization:
|
||||
|
||||
@@ -68,8 +68,11 @@ type configSchema struct {
|
||||
Authentication struct {
|
||||
Enabled bool `yaml:"-" koanf:"-"`
|
||||
Header struct {
|
||||
Name string
|
||||
ValueRegex string `yaml:"value_re" koanf:"value_re"`
|
||||
Name string
|
||||
ValueRegex string `yaml:"value_re" koanf:"value_re"`
|
||||
GroupName string `yaml:"group_name" koanf:"group_name"`
|
||||
GroupValueRegex string `yaml:"group_value_re" koanf:"group_value_re"`
|
||||
GroupValueSeparator string `yaml:"group_value_separator" koanf:"group_value_separator"`
|
||||
}
|
||||
BasicAuth struct {
|
||||
Users []AuthenticationUser
|
||||
|
||||
Reference in New Issue
Block a user