Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added wildcard groups matching #111

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ settings are related to authorization:

| Setting | Default | Description |
| - | - | - |
| `GROUPS_ALLOWLIST` | "*" | List of groups that are allowed to pass authorization. By default, all groups are allowed. If you change this option, you may want to include the `system:serviceaccounts` group explicitly, if you need the AuthService to accept ServiceAccountTokens. |
| `GROUPS_ALLOWLIST` | "*" | List of groups that are allowed to pass authorization. By default, all groups are allowed. If you change this option, you may want to include the `system:serviceaccounts` group explicitly, if you need the AuthService to accept ServiceAccountTokens. Supports wildcard matching. |
| `EXTERNAL_AUTHZ_URL` | "" | Use an external authorization service. This option is disabled by default, to enable set the value to the target external authorization service (e.g. `EXTERNAL_AUTHZ_URL=http://authorizer/auth`). If you have enabled this option then for a request to be authorized, **both** the group and the external authorization service will have to allow the request. |

## Usage
Expand Down
49 changes: 40 additions & 9 deletions authorizer_groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"net/http"
"regexp"
"strings"

"k8s.io/apiserver/pkg/authentication/user"
Expand All @@ -20,30 +21,60 @@ type Authorizer interface {
}

type groupsAuthorizer struct {
allowed map[string]bool
allowAll bool
allowlist []string
}

// wildCardToRegexp converts a wildcard pattern to a regular expression pattern.
func wildCardToRegexp(pattern string) string {
components := strings.Split(pattern, "*")
if len(components) == 1 {
// if len is 1, there are no *'s, return exact match pattern
return "^" + pattern + "$"
}
var result strings.Builder
for i, literal := range components {

// Replace * with .*
if i > 0 {
result.WriteString(".*")
}

// Quote any regular expression meta characters in the
// literal text.
result.WriteString(regexp.QuoteMeta(literal))
}
return "^" + result.String() + "$"
}

func match(pattern string, value string) bool {
result, _ := regexp.MatchString(wildCardToRegexp(pattern), value)
return result
}

func newGroupsAuthorizer(allowlist []string) Authorizer {
allowed := map[string]bool{}
allowAll := false
for _, g := range allowlist {
if g == wildcardMatcher {
allowed = map[string]bool{g: true}
allowAll = true
break
}
allowed[g] = true
}
return &groupsAuthorizer{
allowed: allowed,
allowAll: allowAll,
allowlist: allowlist,
}
}

func (ga *groupsAuthorizer) Authorize(r *http.Request, userinfo user.Info) (bool, string, error) {
if ga.allowed[wildcardMatcher] {
if ga.allowAll {
return true, "", nil
}
for _, g := range userinfo.GetGroups() {
if ga.allowed[g] {
return true, "", nil
for _, group := range userinfo.GetGroups() {
for _, allowedGroupPattern := range ga.allowlist {
if match(allowedGroupPattern, group) {
return true, "", nil
}
}
}
reason := fmt.Sprintf("User's groups ([%s]) are not in allowlist.",
Expand Down
90 changes: 90 additions & 0 deletions authorizer_groups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,96 @@ func TestGroupsAuthorizer(t *testing.T) {
userGroups: []string{"d", "e"},
allowed: false,
},
{
name: "user groups in allowlist wildcard prefix 1",
allowlist: []string{"agroup*"},
userGroups: []string{"agroup"},
allowed: true,
},
{
name: "user groups in allowlist wildcard prefix 2",
allowlist: []string{"agroup*"},
userGroups: []string{"agroup-any-character"},
allowed: true,
},
{
name: "user groups not in allowlist wildcard prefix 1",
allowlist: []string{"agroup*"},
userGroups: []string{"any-character-agroup-any-character"},
allowed: false,
},
{
name: "user groups not in allowlist wildcard prefix 2",
allowlist: []string{"agroup*"},
userGroups: []string{"any-character-agroup"},
allowed: false,
},
{
name: "user groups in allowlist wildcard suffix 1",
allowlist: []string{"*agroup"},
userGroups: []string{"agroup"},
allowed: true,
},
{
name: "user groups in allowlist wildcard suffix 2",
allowlist: []string{"*agroup"},
userGroups: []string{"any-character-agroup"},
allowed: true,
},
{
name: "user groups not in allowlist wildcard suffix 1",
allowlist: []string{"*agroup"},
userGroups: []string{"agroup-any-character"},
allowed: false,
},
{
name: "user groups not in allowlist wildcard suffix 2",
allowlist: []string{"agroup*"},
userGroups: []string{"any-character-agroup-any-character"},
allowed: false,
},
{
name: "user groups in allowlist wildcard match 1",
allowlist: []string{"*agroup*"},
userGroups: []string{"agroup"},
allowed: true,
},
{
name: "user groups in allowlist wildcard match 2",
allowlist: []string{"*agroup*"},
userGroups: []string{"agroup-any-character"},
allowed: true,
},
{
name: "user groups in allowlist wildcard match 3",
allowlist: []string{"*agroup*"},
userGroups: []string{"any-character-agroup"},
allowed: true,
},
{
name: "user groups in allowlist wildcard match 4",
allowlist: []string{"*agroup*"},
userGroups: []string{"any-character-agroup-any-character"},
allowed: true,
},
{
name: "user groups in allowlist wildcard match 5",
allowlist: []string{"group/*/test"},
userGroups: []string{"group/2/test"},
allowed: true,
},
{
name: "user groups not in allowlist wildcard match 1",
allowlist: []string{"group/*/test"},
userGroups: []string{"group/test"},
allowed: false,
},
{
name: "user groups not in allowlist wildcard match 2",
allowlist: []string{"*agroup*"},
userGroups: []string{"any-character"},
allowed: false,
},
}

for _, test := range tests {
Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func main() {
log.Fatalf("Error getting K8s config: %v", err)
} else if err != nil {
// If Kubernetes authenticator is disabled, ignore the error.
log.Debugf("Error getting K8s config: %v. " +
log.Debugf("Error getting K8s config: %v. "+
"Kubernetes authenticator is disabled, skipping ...", err)
} else {
k8sAuthenticator, err = authenticators.NewKubernetesAuthenticator(
Expand All @@ -122,7 +122,7 @@ func main() {
log.Fatalf("Error creating K8s authenticator: %v", err)
} else if err != nil {
// If Kubernetes authenticator is disabled, ignore the error.
log.Debugf("Error creating K8s authenticator:: %v. " +
log.Debugf("Error creating K8s authenticator:: %v. "+
"Kubernetes authenticator is disabled, skipping ...", err)
}
}
Expand Down