Skip to content

[app] Update Permission Handling #348

New issue

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

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

Already on GitHub? # to your account

Merged
merged 2 commits into from
Jun 13, 2022
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan
### Changed

- [#346](https://github.com/kobsio/kobs/pull/346): [app] :warning: _Breaking change:_ :warning: Rework kobs architecture, by introducing a `hub` and a `satellite` component. This new architecture allows us to run the kobs `hub` component in a central cluster and access clusters / services (plugins) through the kobs `satellite` component. More information regarding the new kobs architecture can be found in the documentation at [kobs.io](https://kobs.io).
- [#348](https://github.com/kobsio/kobs/pull/348): [app] Update permission handling.

## [v0.8.0](https://github.com/kobsio/kobs/releases/tag/v0.8.0) (2022-03-24)

Expand Down
2 changes: 1 addition & 1 deletion deploy/helm/hub/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform
type: application
home: https://kobs.io
icon: https://kobs.io/assets/images/logo.svg
version: 0.14.3
version: 0.14.4
appVersion: v0.8.0
2 changes: 1 addition & 1 deletion deploy/helm/satellite/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform
type: application
home: https://kobs.io
icon: https://kobs.io/assets/images/logo.svg
version: 0.14.3
version: 0.14.4
appVersion: v0.8.0
11 changes: 3 additions & 8 deletions deploy/helm/satellite/crds/kobs.io_teams.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,6 @@ spec:
type:
type: string
required:
- clusters
- namespaces
- satellites
- type
type: object
type: array
Expand All @@ -203,9 +200,12 @@ spec:
x-kubernetes-preserve-unknown-fields: true
satellite:
type: string
type:
type: string
required:
- name
- satellite
- type
type: object
type: array
resources:
Expand Down Expand Up @@ -243,11 +243,6 @@ spec:
items:
type: string
type: array
required:
- applications
- plugins
- resources
- teams
type: object
satellite:
type: string
Expand Down
11 changes: 3 additions & 8 deletions deploy/helm/satellite/crds/kobs.io_users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ spec:
type:
type: string
required:
- clusters
- namespaces
- satellites
- type
type: object
type: array
Expand All @@ -187,9 +184,12 @@ spec:
x-kubernetes-preserve-unknown-fields: true
satellite:
type: string
type:
type: string
required:
- name
- satellite
- type
type: object
type: array
resources:
Expand Down Expand Up @@ -227,11 +227,6 @@ spec:
items:
type: string
type: array
required:
- applications
- plugins
- resources
- teams
type: object
satellite:
type: string
Expand Down
11 changes: 3 additions & 8 deletions deploy/kustomize/crds/kobs.io_teams.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,6 @@ spec:
type:
type: string
required:
- clusters
- namespaces
- satellites
- type
type: object
type: array
Expand All @@ -203,9 +200,12 @@ spec:
x-kubernetes-preserve-unknown-fields: true
satellite:
type: string
type:
type: string
required:
- name
- satellite
- type
type: object
type: array
resources:
Expand Down Expand Up @@ -243,11 +243,6 @@ spec:
items:
type: string
type: array
required:
- applications
- plugins
- resources
- teams
type: object
satellite:
type: string
Expand Down
11 changes: 3 additions & 8 deletions deploy/kustomize/crds/kobs.io_users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ spec:
type:
type: string
required:
- clusters
- namespaces
- satellites
- type
type: object
type: array
Expand All @@ -187,9 +184,12 @@ spec:
x-kubernetes-preserve-unknown-fields: true
satellite:
type: string
type:
type: string
required:
- name
- satellite
- type
type: object
type: array
resources:
Expand Down Expand Up @@ -227,11 +227,6 @@ spec:
items:
type: string
type: array
required:
- applications
- plugins
- resources
- teams
type: object
satellite:
type: string
Expand Down
16 changes: 12 additions & 4 deletions pkg/hub/api/plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package plugins

import (
"net/http"
"strings"

authContext "github.com/kobsio/kobs/pkg/hub/middleware/userauth/context"
"github.com/kobsio/kobs/pkg/hub/satellites"
Expand Down Expand Up @@ -41,6 +42,13 @@ func (router *Router) getPlugins(w http.ResponseWriter, r *http.Request) {
func (router *Router) proxyPlugins(w http.ResponseWriter, r *http.Request) {
satelliteName := r.Header.Get("x-kobs-satellite")
pluginName := r.Header.Get("x-kobs-plugin")
pluginType := strings.Split(r.URL.RawPath, "/")

if len(pluginType) < 4 {
log.Warn(r.Context(), "The user is not authorized to access the plugin", zap.String("satellite", satelliteName), zap.String("plugin", pluginName), zap.Strings("pluginTypes", pluginType))
errresponse.Render(w, r, nil, http.StatusUnauthorized, "You are not authorized to access the plugin")
return
}

if satelliteName == "" && pluginName == "" {
satelliteName = r.URL.Query().Get("x-kobs-satellite")
Expand All @@ -49,20 +57,20 @@ func (router *Router) proxyPlugins(w http.ResponseWriter, r *http.Request) {

user, err := authContext.GetUser(r.Context())
if err != nil {
log.Warn(r.Context(), "The user is not authorized to access the plugin", zap.String("satellite", satelliteName), zap.String("plugin", pluginName), zap.Error(err))
log.Warn(r.Context(), "The user is not authorized to access the plugin", zap.String("satellite", satelliteName), zap.String("plugin", pluginName), zap.String("pluginType", pluginType[3]), zap.Error(err))
errresponse.Render(w, r, err, http.StatusUnauthorized, "You are not authorized to access the plugin")
return
}

if !user.HasPluginAccess(satelliteName, pluginName) {
log.Warn(r.Context(), "The user is not allowed to access the plugin", zap.String("satellite", satelliteName), zap.String("plugin", pluginName), zap.Error(err))
if !user.HasPluginAccess(satelliteName, pluginType[3], pluginName) {
log.Warn(r.Context(), "The user is not allowed to access the plugin", zap.String("satellite", satelliteName), zap.String("plugin", pluginName), zap.String("pluginType", pluginType[3]), zap.Error(err))
errresponse.Render(w, r, err, http.StatusForbidden, "You are not allowed to access the plugin")
return
}

satellite := router.satellitesClient.GetSatellite(satelliteName)
if satellite == nil {
log.Error(r.Context(), "Satellite was not found", zap.String("satellite", satelliteName), zap.String("plugin", pluginName))
log.Error(r.Context(), "Satellite was not found", zap.String("satellite", satelliteName), zap.String("plugin", pluginName), zap.String("pluginType", pluginType[3]))
errresponse.Render(w, r, nil, http.StatusInternalServerError, "Satellite was not found")
return
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/hub/api/teams/teams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestGetTeams(t *testing.T) {
name: "get all teams",
url: "/teams?all=true",
expectedStatusCode: http.StatusOK,
expectedBody: "[{\"group\":\"team1\",\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}},{\"group\":\"team2\",\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}},{\"group\":\"team3\",\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}]\n",
expectedBody: "[{\"group\":\"team1\",\"permissions\":{}},{\"group\":\"team2\",\"permissions\":{}},{\"group\":\"team3\",\"permissions\":{}}]\n",
prepare: func(t *testing.T, mockStoreClient *store.MockClient) {
mockStoreClient.On("GetTeams", mock.Anything).Return([]teamv1.TeamSpec{{Group: "team1"}, {Group: "team2"}, {Group: "team3"}, {Group: "team1"}, {Group: "team2"}}, nil)
mockStoreClient.AssertNotCalled(t, "GetTeamsByGroups", mock.Anything, mock.Anything)
Expand Down Expand Up @@ -99,7 +99,7 @@ func TestGetTeams(t *testing.T) {
name: "get own teams",
url: "/teams",
expectedStatusCode: http.StatusOK,
expectedBody: "[{\"group\":\"team1\",\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}]\n",
expectedBody: "[{\"group\":\"team1\",\"permissions\":{}}]\n",
prepare: func(t *testing.T, mockStoreClient *store.MockClient) {
mockStoreClient.AssertNotCalled(t, "GetTeams", mock.Anything)
mockStoreClient.On("GetTeamsByGroups", mock.Anything, []string{"team1"}).Return([]teamv1.TeamSpec{{Group: "team1"}, {Group: "team1"}}, nil)
Expand Down Expand Up @@ -181,7 +181,7 @@ func TestGetTeam(t *testing.T) {
name: "get team",
url: "/teams/team?group=team1",
expectedStatusCode: http.StatusOK,
expectedBody: "{\"group\":\"team1\",\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}\n",
expectedBody: "{\"group\":\"team1\",\"permissions\":{}}\n",
prepare: func(t *testing.T, mockStoreClient *store.MockClient) {
mockStoreClient.On("GetTeamByGroup", mock.Anything, "team1").Return(&teamv1.TeamSpec{Group: "team1"}, nil)
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/hub/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func New(debugUsername, debugPassword, hubAddress string, authEnabled bool, auth
r.Use(middleware.URLFormat)
r.Use(metrics.Metrics)
r.Use(httplog.Logger)
r.Use(userauth.Handler(authEnabled, authHeaderUser, authHeaderTeams, authSessionToken, authSessionInterval, nil))
r.Use(userauth.Handler(authEnabled, authHeaderUser, authHeaderTeams, authSessionToken, authSessionInterval, storeClient))
r.Use(render.SetContentType(render.ContentTypeJSON))

r.Mount("/auth", userauth.Mount(authLogoutRedirect))
Expand Down
6 changes: 5 additions & 1 deletion pkg/hub/middleware/userauth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ func (a *Auth) Handler(next http.Handler) http.Handler {
http.SetCookie(w, &http.Cookie{
Name: "kobs-auth",
Value: token,
Path: "/",
Expires: time.Now().Add(a.sessionInterval),
Secure: true,
HttpOnly: true,
})
Expand Down Expand Up @@ -144,6 +146,8 @@ func (a *Auth) Handler(next http.Handler) http.Handler {
http.SetCookie(w, &http.Cookie{
Name: "kobs-auth",
Value: token,
Path: "/",
Expires: time.Now().Add(a.sessionInterval),
Secure: true,
HttpOnly: true,
})
Expand All @@ -162,7 +166,7 @@ func (a *Auth) Handler(next http.Handler) http.Handler {
Permissions: userv1.Permissions{
Applications: []userv1.ApplicationPermissions{{Type: "all"}},
Teams: []string{"*"},
Plugins: []userv1.Plugin{{Satellite: "*", Name: "*"}},
Plugins: []userv1.Plugin{{Satellite: "*", Type: "*", Name: "*"}},
Resources: []userv1.Resources{{Satellites: []string{"*"}, Clusters: []string{"*"}, Namespaces: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}}},
},
})
Expand Down
8 changes: 4 additions & 4 deletions pkg/hub/middleware/userauth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestAuthHandler(t *testing.T) {
name: "auth disabled",
auth: Auth{enabled: false},
expectedStatusCode: http.StatusOK,
expectedBody: "{\"email\":\"\",\"teams\":[\"*\"],\"permissions\":{\"applications\":[{\"type\":\"all\",\"satellites\":null,\"clusters\":null,\"namespaces\":null}],\"teams\":[\"*\"],\"plugins\":[{\"satellite\":\"*\",\"name\":\"*\",\"permissions\":null}],\"resources\":[{\"satellites\":[\"*\"],\"clusters\":[\"*\"],\"namespaces\":[\"*\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]}]}}\n",
expectedBody: "{\"email\":\"\",\"teams\":[\"*\"],\"permissions\":{\"applications\":[{\"type\":\"all\"}],\"teams\":[\"*\"],\"plugins\":[{\"satellite\":\"*\",\"name\":\"*\",\"type\":\"*\",\"permissions\":null}],\"resources\":[{\"satellites\":[\"*\"],\"clusters\":[\"*\"],\"namespaces\":[\"*\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]}]}}\n",
prepareRequest: func(r *http.Request) {},
prepareStoreClient: func(mockStoreClient *store.MockClient) {},
},
Expand Down Expand Up @@ -145,7 +145,7 @@ func TestAuthHandler(t *testing.T) {
name: "auth enabled, request without cookie, success",
auth: Auth{enabled: true, headerUser: "X-Auth-Request-Email"},
expectedStatusCode: http.StatusOK,
expectedBody: "{\"email\":\"user1@kobs.io\",\"teams\":null,\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}\n",
expectedBody: "{\"email\":\"user1@kobs.io\",\"teams\":null,\"permissions\":{}}\n",
prepareRequest: func(r *http.Request) {
r.Header.Add("X-Auth-Request-Email", "user1@kobs.io")
},
Expand Down Expand Up @@ -189,7 +189,7 @@ func TestAuthHandler(t *testing.T) {
name: "auth enabled, request with cookie, validate token error, success",
auth: Auth{enabled: true, headerUser: "X-Auth-Request-Email"},
expectedStatusCode: http.StatusOK,
expectedBody: "{\"email\":\"user1@kobs.io\",\"teams\":null,\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}\n",
expectedBody: "{\"email\":\"user1@kobs.io\",\"teams\":null,\"permissions\":{}}\n",
prepareRequest: func(r *http.Request) {
token, _ := jwt.CreateToken(authContext.User{}, "sessionToken", 10*time.Second)
r.AddCookie(&http.Cookie{Name: "kobs-auth", Value: token})
Expand All @@ -204,7 +204,7 @@ func TestAuthHandler(t *testing.T) {
name: "auth enabled, request with cookie, valid token, success",
auth: Auth{enabled: true, headerUser: "X-Auth-Request-Email"},
expectedStatusCode: http.StatusOK,
expectedBody: "{\"email\":\"user1@kobs.io\",\"teams\":null,\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}\n",
expectedBody: "{\"email\":\"user1@kobs.io\",\"teams\":null,\"permissions\":{}}\n",
prepareRequest: func(r *http.Request) {
token, _ := jwt.CreateToken(authContext.User{Email: "user1@kobs.io"}, "", 10*time.Minute)
r.AddCookie(&http.Cookie{Name: "kobs-auth", Value: token})
Expand Down
8 changes: 5 additions & 3 deletions pkg/hub/middleware/userauth/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ func (u *User) HasTeamAccess(teamGroup string) bool {
}

// HasPluginAccess checks if the user has access to the given plugin.
func (u *User) HasPluginAccess(satellite, plugin string) bool {
func (u *User) HasPluginAccess(satellite, pluginType, pluginName string) bool {
for _, p := range u.Permissions.Plugins {
if p.Satellite == satellite || p.Satellite == "*" {
if p.Name == plugin || p.Name == "*" {
return true
if p.Type == pluginType || p.Type == "*" {
if p.Name == pluginName || p.Name == "*" {
return true
}
}
}
}
Expand Down
20 changes: 11 additions & 9 deletions pkg/hub/middleware/userauth/context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func TestToString(t *testing.T) {
u := User{Email: "test1"}
require.Equal(t, "{\"email\":\"test1\",\"teams\":null,\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}", u.ToString())
require.Equal(t, "{\"email\":\"test1\",\"teams\":null,\"permissions\":{}}", u.ToString())
}

func TestHasApplicationAccess(t *testing.T) {
Expand Down Expand Up @@ -60,16 +60,18 @@ func TestHasPluginAccess(t *testing.T) {
user User
expectedHasAccess bool
}{
{user: User{Email: "user1@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Name: "*"}}}}, expectedHasAccess: true},
{user: User{Email: "user2@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Name: "plugin1"}}}}, expectedHasAccess: true},
{user: User{Email: "user3@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Name: "plugin1"}, {Satellite: "*", Name: "plugin2"}}}}, expectedHasAccess: true},
{user: User{Email: "user4@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Name: "plugin2"}}}}, expectedHasAccess: false},
{user: User{Email: "user5@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Name: "plugin2"}, {Satellite: "*", Name: "*"}}}}, expectedHasAccess: true},
{user: User{Email: "user6@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "test-satellite1", Name: "*"}}}}, expectedHasAccess: true},
{user: User{Email: "user7@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "test-satellite2", Name: "*"}}}}, expectedHasAccess: false},
{user: User{Email: "user1@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "prometheus", Name: "*"}}}}, expectedHasAccess: true},
{user: User{Email: "user2@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "prometheus", Name: "plugin1"}}}}, expectedHasAccess: true},
{user: User{Email: "user3@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "prometheus", Name: "plugin1"}, {Satellite: "*", Type: "prometheus", Name: "plugin2"}}}}, expectedHasAccess: true},
{user: User{Email: "user4@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "prometheus", Name: "plugin2"}}}}, expectedHasAccess: false},
{user: User{Email: "user5@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "prometheus", Name: "plugin2"}, {Satellite: "*", Type: "prometheus", Name: "*"}}}}, expectedHasAccess: true},
{user: User{Email: "user6@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "test-satellite1", Type: "prometheus", Name: "*"}}}}, expectedHasAccess: true},
{user: User{Email: "user7@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "test-satellite2", Type: "prometheus", Name: "*"}}}}, expectedHasAccess: false},
{user: User{Email: "user1@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "klogs", Name: "*"}}}}, expectedHasAccess: false},
{user: User{Email: "user1@kobs.io", Permissions: userv1.Permissions{Plugins: []userv1.Plugin{{Satellite: "*", Type: "*", Name: "*"}}}}, expectedHasAccess: true},
} {
t.Run(tt.user.Email, func(t *testing.T) {
actualHasAccess := tt.user.HasPluginAccess("test-satellite1", "plugin1")
actualHasAccess := tt.user.HasPluginAccess("test-satellite1", "prometheus", "plugin1")
require.Equal(t, tt.expectedHasAccess, actualHasAccess)
})
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/hub/middleware/userauth/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestUserAuthHandler(t *testing.T) {
name: "get user",
user: authContext.User{},
expectedStatusCode: http.StatusOK,
expectedBody: "{\"email\":\"\",\"teams\":null,\"permissions\":{\"applications\":null,\"teams\":null,\"plugins\":null,\"resources\":null}}\n",
expectedBody: "{\"email\":\"\",\"teams\":null,\"permissions\":{}}\n",
},
{
name: "could not get user",
Expand Down
Loading