Skip to content

Commit

Permalink
modify default protal roles acl
Browse files Browse the repository at this point in the history
  • Loading branch information
greenpau committed Mar 24, 2024
1 parent 5aa0d64 commit bfaeabc
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 58 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/greenpau/go-authcrunch

go 1.20
go 1.21

require (
github.com/crewjam/saml v0.4.14
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down Expand Up @@ -79,6 +80,7 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
Expand Down Expand Up @@ -116,6 +118,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
Expand All @@ -138,3 +141,4 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
52 changes: 52 additions & 0 deletions pkg/authn/enums/role/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2024 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package role

import (
"fmt"
)

// Kind is the type of a role.
type Kind int

const (
// Unknown operator signals invalid role type.
Unknown Kind = iota
// Anonymous indicates anonymous.
Anonymous
// Admin indicates role with administrative privileges.
Admin
// User indicates role with user privileges.
User
// Guest indicates role with guest privileges.
Guest
)

// String returns string representation of a role type.
func (e Kind) String() string {
switch e {
case Unknown:
return "Unknown"
case Anonymous:
return "Anonymous"
case Admin:
return "Admin"
case User:
return "User"
case Guest:
return "Guest"
}
return fmt.Sprintf("RoleKind(%d)", int(e))
}
60 changes: 60 additions & 0 deletions pkg/authn/gatekeeper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authn

import (
"fmt"
"slices"

Check failure on line 19 in pkg/authn/gatekeeper.go

View workflow job for this annotation

GitHub Actions / Build (1.19.x, ubuntu-latest)

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.19.13/x64/src/slices)

Check failure on line 19 in pkg/authn/gatekeeper.go

View workflow job for this annotation

GitHub Actions / Build (1.20.x, ubuntu-latest)

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.20.14/x64/src/slices)

"github.com/greenpau/go-authcrunch/pkg/authn/enums/role"
"github.com/greenpau/go-authcrunch/pkg/user"
)

func (p *Portal) authorizedRole(usr *user.User, authorizedRoles []role.Kind, authenticated bool) error {
if !authenticated {
if slices.Contains(authorizedRoles, role.Anonymous) {
return nil
}
return fmt.Errorf("user is not authenticated")
}

if slices.Contains(authorizedRoles, role.User) {
for roleName := range p.config.PortalUserRoles {
if usr.HasRole(roleName) {
return nil
}
}
for _, roleNamePattern := range p.config.userRolePatterns {
if usr.HasRolePattern(roleNamePattern) {
return nil
}
}
}

if slices.Contains(authorizedRoles, role.Admin) {
for roleName := range p.config.PortalAdminRoles {
if usr.HasRole(roleName) {
return nil
}
}
for _, roleNamePattern := range p.config.adminRolePatterns {
if usr.HasRolePattern(roleNamePattern) {
return nil
}
}
}

return fmt.Errorf("user is not authorized")
}
73 changes: 66 additions & 7 deletions pkg/authn/portal.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ import (
)

const (
defaultPortalACLCondition = "match roles authp/admin authp/user authp/guest superuser superadmin"
defaultPortalACLAction = "allow stop"
defaultPortalACLAction = "allow stop"
)

// Portal is an authentication portal.
Expand Down Expand Up @@ -225,18 +224,78 @@ func (p *Portal) configureEssentials() error {
return err
}
p.cookie = c

p.logger.Debug(
"Configuring defaul portal user roles",
zap.String("portal_name", p.config.Name),
zap.Any("portal_admin_roles", p.config.PortalAdminRoles),
zap.Any("portal_user_roles", p.config.PortalUserRoles),
zap.Any("portal_guest_roles", p.config.PortalGuestRoles),
zap.Any("portal_admin_role_patterns", p.config.PortalAdminRolePatterns),
zap.Any("portal_user_role_patterns", p.config.PortalUserRolePatterns),
zap.Any("portal_guest_role_patterns", p.config.PortalGuestRolePatterns),
)

return nil
}

func (p *Portal) configureCryptoKeyStore() error {
if len(p.config.AccessListConfigs) == 0 {
p.config.AccessListConfigs = []*acl.RuleConfiguration{
{
// Admin users can access everything.
Conditions: []string{defaultPortalACLCondition},
defaultACLConfig := []*acl.RuleConfiguration{}

// Configure ACL by role names
for roleName := range p.config.PortalAdminRoles {
aclConfig := &acl.RuleConfiguration{
Comment: "admin role name match",
Conditions: []string{"match role " + roleName},
Action: defaultPortalACLAction,
}
defaultACLConfig = append(defaultACLConfig, aclConfig)
}
for roleName := range p.config.PortalUserRoles {
aclConfig := &acl.RuleConfiguration{
Comment: "user role name match",
Conditions: []string{"match role " + roleName},
Action: defaultPortalACLAction,
}
defaultACLConfig = append(defaultACLConfig, aclConfig)
}
for roleName := range p.config.PortalGuestRoles {
aclConfig := &acl.RuleConfiguration{
Comment: "guest role name match",
Conditions: []string{"match role " + roleName},
Action: defaultPortalACLAction,
},
}
defaultACLConfig = append(defaultACLConfig, aclConfig)
}

// Configure ACL by role patterns
for _, roleNameRegex := range p.config.adminRolePatterns {
aclConfig := &acl.RuleConfiguration{
Comment: "admin role name pattern match",
Conditions: []string{"regex match role " + roleNameRegex.String()},
Action: defaultPortalACLAction,
}
defaultACLConfig = append(defaultACLConfig, aclConfig)
}
for _, roleNameRegex := range p.config.userRolePatterns {
aclConfig := &acl.RuleConfiguration{
Comment: "user role name pattern match",
Conditions: []string{"regex match role " + roleNameRegex.String()},
Action: defaultPortalACLAction,
}
defaultACLConfig = append(defaultACLConfig, aclConfig)
}
for _, roleNameRegex := range p.config.guestRolePatterns {
aclConfig := &acl.RuleConfiguration{
Comment: "guest role name pattern match",
Conditions: []string{"regex match role " + roleNameRegex.String()},
Action: defaultPortalACLAction,
}
defaultACLConfig = append(defaultACLConfig, aclConfig)
}

p.config.AccessListConfigs = defaultACLConfig
}

p.logger.Debug(
Expand Down
19 changes: 15 additions & 4 deletions pkg/authn/portal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,21 @@ func TestNewPortal(t *testing.T) {
"validate_bearer_header": true
},
"access_list_configs": [
{
"action": "` + defaultPortalACLAction + `",
"conditions": ["` + defaultPortalACLCondition + `"]
}
{
"action": "allow stop",
"comment": "admin role name match",
"conditions": ["match role authp/admin"]
},
{
"action": "allow stop",
"comment": "user role name match",
"conditions": ["match role authp/user"]
},
{
"action": "allow stop",
"comment": "guest role name match",
"conditions": ["match role authp/guest"]
}
],
"identity_stores": ["local_backend"]
}
Expand Down
66 changes: 28 additions & 38 deletions pkg/authn/respond_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ package authn

import (
"context"
"net/http"
"strings"

"github.com/greenpau/go-authcrunch/pkg/authn/enums/role"
"github.com/greenpau/go-authcrunch/pkg/requests"
addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr"
"go.uber.org/zap"
"net/http"
"strings"
)

func (p *Portal) handleAPI(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error {
Expand All @@ -47,48 +49,36 @@ func (p *Portal) handleAPI(ctx context.Context, w http.ResponseWriter, r *http.R
return p.handleJSONErrorWithLog(ctx, w, r, rr, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
}

if !rr.Response.Authenticated {
p.logger.Debug(
"User is not authorized to use API",
zap.String("session_id", rr.Upstream.SessionID),
zap.String("request_id", rr.ID),
)
return p.handleJSONErrorWithLog(ctx, w, r, rr, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
}

if !usr.HasRole("authp/admin") {
p.logger.Debug(
"API is not available because user has no admin privileges",
zap.String("session_id", rr.Upstream.SessionID),
zap.String("request_id", rr.ID),
)
return p.handleJSONErrorWithLog(ctx, w, r, rr, http.StatusForbidden, http.StatusText(http.StatusForbidden))
}

if p.config.API == nil || (p.config.API != nil && !p.config.API.ProfileEnabled && !p.config.API.AdminEnabled) {
p.logger.Debug(
"API is not available",
zap.String("session_id", rr.Upstream.SessionID),
zap.String("request_id", rr.ID),
zap.Any("api_config", p.config.API),
)
return p.handleJSONError(ctx, w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}

switch {
case p.config.API.AdminEnabled && strings.HasSuffix(r.URL.Path, "/api/metadata"):
return p.handleAPIMetadata(ctx, w, r, rr, usr)
case p.config.API.AdminEnabled && strings.Contains(r.URL.Path, "/api/orgs"):
return p.handleJSONError(ctx, w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
case p.config.API.AdminEnabled && strings.Contains(r.URL.Path, "/api/teams"):
case p.config.API.AdminEnabled && r.Method == "POST" && strings.Contains(r.URL.Path, "/api/manager"):
if err := p.authorizedRole(usr, []role.Kind{role.Admin}, rr.Response.Authenticated); err != nil {
p.logger.Debug(
"User is not authorized accessing API",
zap.String("session_id", rr.Upstream.SessionID),
zap.String("request_id", rr.ID),
zap.String("reason", err.Error()),
)
return p.handleJSONError(ctx, w, http.StatusForbidden, http.StatusText(http.StatusForbidden))
}
// case p.config.API.AdminEnabled && strings.HasSuffix(r.URL.Path, "/api/metadata"):
// return p.handleAPIMetadata(ctx, w, r, rr, usr)
// case p.config.API.AdminEnabled && strings.Contains(r.URL.Path, "/api/users"):
// return p.handleAPIListUsers(ctx, w, r, rr, usr)
return p.handleJSONError(ctx, w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
case p.config.API.AdminEnabled && strings.Contains(r.URL.Path, "/api/users"):
return p.handleAPIListUsers(ctx, w, r, rr, usr)
case p.config.API.ProfileEnabled && r.Method == "POST" && strings.Contains(r.URL.Path, "/api/profile"):
if err := p.authorizedRole(usr, []role.Kind{role.Admin, role.User}, rr.Response.Authenticated); err != nil {
p.logger.Debug(
"User is not authorized accessing API",
zap.String("session_id", rr.Upstream.SessionID),
zap.String("request_id", rr.ID),
zap.String("reason", err.Error()),
)
return p.handleJSONError(ctx, w, http.StatusForbidden, http.StatusText(http.StatusForbidden))
}
return p.handleAPIProfile(ctx, w, r, rr, usr)
default:
p.logger.Debug(
"API endpoint is not supported",
"API endpoint is not available",
zap.String("session_id", rr.Upstream.SessionID),
zap.String("request_id", rr.ID),
zap.Any("api_config", p.config.API),
Expand Down
16 changes: 14 additions & 2 deletions pkg/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ package user
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"time"

"github.com/greenpau/go-authcrunch/pkg/errors"
cfgutil "github.com/greenpau/go-authcrunch/pkg/util/cfg"
datautil "github.com/greenpau/go-authcrunch/pkg/util/data"
"strings"
"time"
)

/*
Expand Down Expand Up @@ -296,6 +298,16 @@ func (u *User) HasRole(roles ...string) bool {
return false
}

// HasRolePattern checks whether a user has a role matching the provided pattern.
func (u *User) HasRolePattern(ptrn *regexp.Regexp) bool {
for roleName := range u.rkv {
if ptrn.MatchString(roleName) {
return true
}
}
return false
}

// HasRoles checks whether a user has all of the provided roles.
func (u *User) HasRoles(roles ...string) bool {
for _, role := range roles {
Expand Down
Loading

0 comments on commit bfaeabc

Please sign in to comment.