Skip to content

Commit

Permalink
Tighten RBAC
Browse files Browse the repository at this point in the history
In the old world, before service accounts, the user needed excess
premissions to achieve anything, as the token was propagated around
services.  You couldn't have a user that could just provision
Kubernetes, they needed to be able to provision identities and physical
networks too, the latter is a major concern when it comes to DoS attacks
against the platform via the API.  This also scopes endpoints so they
are easier to work with, and also adds in better RBAC for flavors and
images.
  • Loading branch information
spjmurray committed Nov 4, 2024
1 parent 8c155cb commit ea4c55a
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 92 deletions.
4 changes: 2 additions & 2 deletions charts/identity/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn's IdP

type: application

version: v0.2.43
appVersion: v0.2.43
version: v0.2.45
appVersion: v0.2.45

icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png

Expand Down
109 changes: 56 additions & 53 deletions charts/identity/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,90 +77,93 @@ roles:
description: Platform administrator
scopes:
global:
organizations: [create,read,update,delete]
oauth2providers: [create,read,update,delete]
roles: [create,read,update,delete]
groups: [create,read,update,delete]
projects: [create,read,update,delete]
regions: [create,read,update,delete]
identities: [create,read,update,delete]
quotas: [create,read,update,delete]
physicalnetworks: [create,read,update,delete]
kubernetesclustermanagers: [create,read,update,delete]
kubernetesclusters: [create,read,update,delete]
applications: [create,read,update,delete]
applicationsets: [create,read,update,delete]
computeclusters: [create,read,update,delete]
servers: [create,read,update,delete]
securitygroups: [create,read,update,delete]
identity:organizations: [create,read,update,delete]
identity:oauth2providers: [create,read,update,delete]
identity:roles: [create,read,update,delete]
identity:groups: [create,read,update,delete]
identity:projects: [create,read,update,delete]
region:regions: [create,read,update,delete]
region:flavors: [create,read,update,delete]
region:images: [create,read,update,delete]
region:externalnetworks: [create,read,update,delete]
region:identities: [create,read,update,delete]
region:physicalnetworks: [create,read,update,delete]
region:securitygroups: [create,read,update,delete]
region:servers: [create,read,update,delete]
kubernetes:clustermanagers: [create,read,update,delete]
kubernetes:clusters: [create,read,update,delete]
compute:clusters: [create,read,update,delete]
application:applications: [create,read,update,delete]
application:applicationsets: [create,read,update,delete]
# An infrastructure manager service is a role primarily for Kubernetes like
# services that can manage identities and physical networks on behalf of a cluster.
infra-manager-service:
decription: Infrastructure manager service
scopes:
global:
regions: [read]
identities: [read,delete]
physicalnetworks: [read,delete]
quotas: [read,update]
identity:identities: [create,read,delete]
region:regions: [read]
region:flavors: [read]
region:images: [read]
region:externalnetworks: [read]
region:physicalnetworks: [create,read,delete]
region:servers: [create,read,update,delete]
region:securitygroups: [create,read,update,delete]
# An application manager is a role primarily for the application service that
# needs to be able to see the Kubernetes clusters it's deploying applications
# on to.
application-manager-service:
description: Application manager service
scopes:
global:
kubernetesclusters: [read]
kubernetes:clusters: [read]
# An administrator can do anything within an organization.
administrator:
description: Organization administrator
scopes:
organization:
organizations: [read,update]
oauth2providers: [create,read,update,delete]
roles: [create,read,update,delete]
groups: [create,read,update,delete]
projects: [create,read,update,delete]
regions: [read]
identities: [create]
physicalnetworks: [create]
kubernetesclustermanagers: [create,read,update,delete]
kubernetesclusters: [create,read,update,delete]
applications: [read]
applicationsets: [create,read,update,delete]
computeclusters: [create,read,update,delete]
servers: [create,read,update,delete]
securitygroups: [create,read,update,delete]
identity:organizations: [read,update]
identity:oauth2providers: [create,read,update,delete]
identity:roles: [create,read,update,delete]
identity:groups: [create,read,update,delete]
identity:projects: [create,read,update,delete]
region:regions: [read]
region:flavors: [read]
region:images: [read]
kubernetes:clusters: [create,read,update,delete]
compute:clusters: [create,read,update,delete]
application:applications: [read]
application:applicationsets: [create,read,update,delete]
# A user can view projects they are a member of and
# provision kubernetes clusters.
# provision clusters.
user:
description: Project user
scopes:
organization:
regions: [read]
region:regions: [read]
region:flavors: [read]
region:images: [read]
project:
projects: [read]
identities: [create]
physicalnetworks: [create]
kubernetesclustermanagers: [read]
kubernetesclusters: [create,read,update,delete]
applications: [read]
applicationsets: [create,read,update,delete]
computeclusters: [create,read,update,delete]
identity:projects: [read]
kubernetes:clusters: [create,read,update,delete]
compute:clusters: [create,read,update,delete]
application:applications: [read]
applicationapplicationsets: [create,read,update,delete]
# A reader can view projects they are a member of and view
# kubernetes clusters.
reader:
description: Project reader
scopes:
organization:
regions: [read]
region:regions: [read]
region:flavors: [read]
region:images: [read]
project:
projects: [read]
kubernetesclustermanagers: [read]
kubernetesclusters: [read]
applications: [read]
applicationsets: [read]
computeclusters: [read]
identity:projects: [read]
kubernetes:clusters: [read]
compute:clusters: [read]
application:applications: [read]
application:applicationsets: [read]

ingress:
# Sets the ingress class to use.
Expand Down
27 changes: 14 additions & 13 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (

coreclient "github.com/unikorn-cloud/core/pkg/client"
"github.com/unikorn-cloud/identity/pkg/middleware/authorization"
"github.com/unikorn-cloud/identity/pkg/middleware/openapi/accesstoken"
"github.com/unikorn-cloud/identity/pkg/openapi"

"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -78,28 +77,30 @@ func (c *Client) HTTPClient(ctx context.Context) (*http.Client, error) {
return client, nil
}

// requestMutator implements OAuth2 bearer token authorization.
func RequestMutator(ctx context.Context, req *http.Request) error {
// NOTE: this can legitimately not be set e.g. if we are actually getting
// an access token, which makes the error checking somewhat useless!
if accessToken, err := accesstoken.FromContext(ctx); err == nil {
req.Header.Set("Authorization", "bearer "+accessToken)
}
// RequestMutator implements OAuth2 bearer token authorization.
func RequestMutator(accessToken AccessTokener) func(context.Context, *http.Request) error {
return func(ctx context.Context, req *http.Request) error {
// NOTE: this can legitimately not be set e.g. if we are actually getting
// an access token, which makes the error checking somewhat useless!
if accessToken != nil {
req.Header.Set("Authorization", "bearer "+accessToken.Get())
}

otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
authorization.InjectClientCert(ctx, req.Header)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
authorization.InjectClientCert(ctx, req.Header)

return nil
return nil
}
}

// Client returns a new OpenAPI client that can be used to access the API.
func (c *Client) Client(ctx context.Context) (*openapi.ClientWithResponses, error) {
func (c *Client) Client(ctx context.Context, accessToken AccessTokener) (*openapi.ClientWithResponses, error) {
httpClient, err := c.HTTPClient(ctx)
if err != nil {
return nil, err
}

client, err := openapi.NewClientWithResponses(c.options.Host(), openapi.WithHTTPClient(httpClient), openapi.WithRequestEditorFn(RequestMutator))
client, err := openapi.NewClientWithResponses(c.options.Host(), openapi.WithHTTPClient(httpClient), openapi.WithRequestEditorFn(RequestMutator(accessToken)))
if err != nil {
return nil, err
}
Expand Down
22 changes: 22 additions & 0 deletions pkg/client/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Copyright 2024 the Unikorn Authors.
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 client

// AccessTokener provides an interface to retrieve an access token.
type AccessTokener interface {
Get() string
}
19 changes: 15 additions & 4 deletions pkg/client/tokenissuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"go.opentelemetry.io/otel/trace"

coreclient "github.com/unikorn-cloud/core/pkg/client"
"github.com/unikorn-cloud/identity/pkg/middleware/openapi/accesstoken"
identityapi "github.com/unikorn-cloud/identity/pkg/openapi"

"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -70,8 +69,16 @@ func NewTokenIssuer(client client.Client, identityOptions *Options, clientOption
}
}

// Context issues an access token for the non-user client/service and injects it into the context.
func (i *TokenIssuer) Context(ctx context.Context, traceName string) (context.Context, error) {
type AccessTokenGetter struct {
accessToken string
}

func (a *AccessTokenGetter) Get() string {
return a.accessToken
}

// Issue issues an access token for the non-user client/service.
func (i *TokenIssuer) Issue(ctx context.Context, traceName string) (*AccessTokenGetter, error) {
identityClient := New(i.client, i.identityOptions, i.clientOptions)

identityHTTPClient, err := identityClient.HTTPClient(ctx)
Expand Down Expand Up @@ -140,5 +147,9 @@ func (i *TokenIssuer) Context(ctx context.Context, traceName string) (context.Co
return nil, err
}

return accesstoken.NewContext(ctx, token.AccessToken), nil
getter := &AccessTokenGetter{
accessToken: token.AccessToken,
}

return getter, nil
}
Loading

0 comments on commit ea4c55a

Please sign in to comment.