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

Add block clients capability #779

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
26 changes: 15 additions & 11 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,17 @@ func (s *Settings) Validate() error {
}

type Options struct {
RedisClient *redis.Client
Repository storage.TransactionalRepository
APISettings *Settings
OperationSettings *operations.Settings
WSSettings *ws.Settings
Notificator storage.Notificator
WaitGroup *sync.WaitGroup
TenantLabelKey string
Agents *agents.Settings
RedisClient *redis.Client
Repository storage.TransactionalRepository
APISettings *Settings
StorageSettings *storage.Settings
OperationSettings *operations.Settings
WSSettings *ws.Settings
Notificator storage.Notificator
WaitGroup *sync.WaitGroup
TenantLabelKey string
Agents *agents.Settings
BlockedClientsCache *storage.Cache
}

// New returns the minimum set of REST APIs needed for the Service Manager
Expand All @@ -132,7 +134,7 @@ func New(ctx context.Context, e env.Environment, options *Options) (*web.API, er
NewServiceInstanceController(ctx, options),
NewServiceBindingController(ctx, options),
apiNotifications.NewController(ctx, options.Repository, options.WSSettings, options.Notificator),

NewBlockedClientsController(ctx, options),
NewServiceOfferingController(ctx, options),
NewServicePlanController(ctx, options),
NewOperationsController(ctx, options),
Expand Down Expand Up @@ -189,10 +191,12 @@ func New(ctx context.Context, e env.Environment, options *Options) (*web.API, er
}

api.RegisterFiltersBefore(filters.ProtectedLabelsFilterName, &filters.DisabledQueryParametersFilter{DisabledQueryParameters: options.APISettings.DisabledQueryParameters})
api.RegisterFiltersAfter(filters.LoggingFilterName,
filters.NewBlockedClientsFilter(options.BlockedClientsCache, options.TenantLabelKey))

if rateLimiters != nil {
api.RegisterFiltersAfter(
filters.LoggingFilterName,
filters.BlockedClientsFilterName,
filters.NewRateLimiterFilter(
rateLimiters,
options.APISettings.RateLimitExcludeClients,
Expand Down
108 changes: 108 additions & 0 deletions api/blocked_clients_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package api

import (
"context"
"encoding/json"
"fmt"
"github.com/Peripli/service-manager/pkg/types"
"github.com/Peripli/service-manager/pkg/util"
"github.com/Peripli/service-manager/pkg/web"
"github.com/Peripli/service-manager/storage"
"net/http"
)

// BlockedClientsController configuration controller
type BlockedClientsController struct {
*BaseController
cache *storage.Cache
}

func NewBlockedClientsController(ctx context.Context, options *Options) *BlockedClientsController {
return &BlockedClientsController{
BaseController: NewController(ctx, options, web.BlockedClientsConfigURL, types.BlockedClientsType, func() types.Object {
return &types.BlockedClient{}
}, false),
cache: options.BlockedClientsCache,
}

}

var AVAILABLE_METHODS map[string]byte = map[string]byte{
http.MethodDelete: 1,
http.MethodGet: 1,
http.MethodPut: 1,
http.MethodPost: 1,
http.MethodOptions: 1,
http.MethodPatch: 1,
}

func (c *BlockedClientsController) AddBlockedClient(r *web.Request) (*web.Response, error) {

var blockedClient types.BlockedClient
err := json.Unmarshal(r.Body, &blockedClient)
if err != nil {
return nil, &util.HTTPError{
ErrorType: "BlockedClientError",
StatusCode: http.StatusBadRequest,
}
}

if len(blockedClient.BlockedMethods) > 0 {
for _, method := range blockedClient.BlockedMethods {
if _, ok := AVAILABLE_METHODS[method]; !ok {
return nil, &util.HTTPError{
ErrorType: "BlockedClientError",
Description: fmt.Sprintf("Invalid value for a blocked method. Allowed methods to block are: GET, PUT, POST, PATCH, DELETE, OPTIONS."),
StatusCode: http.StatusBadRequest,
}
}
}
}
return c.CreateObject(r)
}

func (c *BlockedClientsController) ResyncBlockedClientsCache(r *web.Request) (*web.Response, error) {
err := c.cache.FlushL()
if err != nil {
return nil, &util.HTTPError{
ErrorType: "BlockedClientError",
Description: fmt.Sprintf("failed to resync blocked_cleints cache"),
StatusCode: http.StatusInternalServerError,
}
}
return util.NewJSONResponse(http.StatusOK, struct{}{})
}

// Routes provides endpoints for modifying and obtaining the logging configuration
func (c *BlockedClientsController) Routes() []web.Route {
return []web.Route{
{
Endpoint: web.Endpoint{
Method: http.MethodGet,
Path: web.BlockedClientsConfigURL,
},
Handler: c.ListObjects,
},
{
Endpoint: web.Endpoint{
Method: http.MethodPost,
Path: web.BlockedClientsConfigURL,
},
Handler: c.AddBlockedClient,
},
{
Endpoint: web.Endpoint{
Method: http.MethodGet,
Path: web.ResyncBlockedClients,
},
Handler: c.ResyncBlockedClientsCache,
},
{
Endpoint: web.Endpoint{
Method: http.MethodDelete,
Path: fmt.Sprintf("%s/{%s}", web.BlockedClientsConfigURL, web.PathParamResourceID),
},
Handler: c.DeleteSingleObject,
},
}
}
1 change: 1 addition & 0 deletions api/extensions/security/authn.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func Register(ctx context.Context, cfg *config.Settings, smb *sm.ServiceManagerB
web.ConfigURL+"/**",
web.ProfileURL+"/**",
web.OperationsURL+"/**",
web.BlockedClientsConfigURL+"/**",
).
Method(http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete).
WithAuthentication(bearerAuthenticator).Required()
Expand Down
96 changes: 96 additions & 0 deletions api/filters/check_blocked_clients_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package filters
sigalmaya marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"
"github.com/Peripli/service-manager/pkg/types"
"github.com/Peripli/service-manager/pkg/util"
"github.com/Peripli/service-manager/pkg/web"
"github.com/Peripli/service-manager/storage"
"net/http"
)

const BlockedClientsFilterName = "BlockedClientsFilter"

type BlockedClientsFilter struct {
blockedClientsCache *storage.Cache
tenantLabelKey string
}

func NewBlockedClientsFilter(cache *storage.Cache, tenantLabelKey string) *BlockedClientsFilter {
b := &BlockedClientsFilter{blockedClientsCache: cache, tenantLabelKey: tenantLabelKey}
return b
}
func (b *BlockedClientsFilter) Name() string {
return BlockedClientsFilterName
}

func (b *BlockedClientsFilter) Run(request *web.Request, next web.Handler) (*web.Response, error) {
reqCtx := request.Context()
method := request.Method
userContext, ok := web.UserFromContext(reqCtx)
if !ok {
//there is no context on the endpoint
return next.Handle(request)
}
blockedClient, isBlockedClient := b.isClientBlocked(userContext, method)
if isBlockedClient {
errorResponse := &util.HTTPError{
ErrorType: "RequestNotAllowed",
StatusCode: http.StatusMethodNotAllowed,
sigalmaya marked this conversation as resolved.
Show resolved Hide resolved
}

errorResponse.Description = fmt.Sprintf("You're blocked to execute this request. Client: %s", blockedClient.ClientID)

return nil, errorResponse

}

return next.Handle(request)
}

func (bc *BlockedClientsFilter) isClientBlocked(userContext *web.UserContext, method string) (*types.BlockedClient, bool) {
//don't restrict global users
if userContext.AccessLevel == web.GlobalAccess || userContext.AccessLevel == web.AllTenantAccess {
return nil, false
}

if userContext.AuthenticationType == web.Basic {
platform := types.Platform{}
err := userContext.Data(&platform)
if err != nil {
return nil, false
}

if _, isTenantScopedPlatform := platform.Labels[bc.tenantLabelKey]; !isTenantScopedPlatform {
return nil, false
}

}

blockedClientCache, ok := bc.blockedClientsCache.GetL(userContext.Name)
if !ok {
return nil, false
}
blockedClient := blockedClientCache.(types.BlockedClient)
return &blockedClient, contains(blockedClient.BlockedMethods, method)

}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

func (bc *BlockedClientsFilter) FilterMatchers() []web.FilterMatcher {
return []web.FilterMatcher{
{
Matchers: []web.Matcher{
web.Path("/**"),
web.Methods(http.MethodPost, http.MethodPatch, http.MethodGet, http.MethodDelete, http.MethodOptions),
},
},
}
}
Loading