Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Implement AuthNZ provider mechanism for Plank #16

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
17 changes: 10 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
module github.com/vmware/transport-go

go 1.13
go 1.16

require (
github.com/fatih/color v1.7.0
github.com/go-stomp/stomp v2.0.3+incompatible
github.com/google/uuid v1.1.1
github.com/fatih/color v1.12.0
github.com/go-stomp/stomp v2.1.4+incompatible
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.1
github.com/gorilla/websocket v1.4.2
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/stretchr/testify v1.4.0
github.com/urfave/cli v1.22.1
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.5
github.com/vmware/transport-go/plank v0.0.0-20210816220202-47a1f3e15fb8
)

replace github.com/vmware/transport-go/plank => ./plank
701 changes: 701 additions & 0 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion plank/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/fatih/color v1.12.0
github.com/go-git/go-git/v5 v5.4.2
github.com/go-stomp/stomp v2.1.4+incompatible // indirect
github.com/go-stomp/stomp v2.1.4+incompatible
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.8.0
Expand Down
1 change: 1 addition & 0 deletions plank/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vmware/transport-go v1.2.0 h1:bLfB3xiyYlGanOQY+wnLc6Ul5GHClyYa0jdEBx6+maU=
github.com/vmware/transport-go v1.2.0/go.mod h1:yVx33Ih199+jJbOTQRXuyNKm6Hu9nUH9nlM1gbj0buQ=
github.com/vmware/transport-go/plank v0.0.0-20210816220202-47a1f3e15fb8/go.mod h1:B2cUNq/oeOk+icxcz8JUmkhacHWNM7MWU60JpJ13Hnk=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
Expand Down
116 changes: 116 additions & 0 deletions plank/pkg/server/auth_provider_manager/auth_provider_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package auth_provider_manager

import (
"fmt"
"regexp"
"sync"
)

var authProviderMgrInstance AuthProviderManager

type AuthProviderManager interface {
GetRESTAuthProvider(uri string) (*RESTAuthProvider, error)
GetSTOMPAuthProvider() (*STOMPAuthProvider, error)
SetRESTAuthProvider(regex *regexp.Regexp, provider *RESTAuthProvider) int
SetSTOMPAuthProvider(provider *STOMPAuthProvider) error
DeleteRESTAuthProvider(idx int) error
DeleteSTOMPAuthProvider() error
}

type regexpRESTAuthProviderPair struct {
regexp *regexp.Regexp
restAuthProvider *RESTAuthProvider
}

type authProviderManager struct {
stompAuthProvider *STOMPAuthProvider
uriPatternRestAuthProviderPairs []*regexpRESTAuthProviderPair
mu sync.Mutex
}

type AuthProviderNotFoundError struct {}

func (e *AuthProviderNotFoundError) Error() string {
return fmt.Sprintf("no auth provider was found at the given location/name")
}

type AuthError struct {
Code int
Message string
}
func (e *AuthError) Error() string {
return fmt.Sprintf("authentication/authorization error (%d): %s", e.Code, e.Message)
}

func (a *authProviderManager) GetRESTAuthProvider(uri string) (*RESTAuthProvider, error) {
a.mu.Lock()
defer a.mu.Unlock()

// perform regex tests to find the first matching REST auth provider
for _, pair := range a.uriPatternRestAuthProviderPairs {
if pair.regexp.Match([]byte(uri)) {
return pair.restAuthProvider, nil
}
}
return nil, &AuthProviderNotFoundError{}
}

func (a *authProviderManager) GetSTOMPAuthProvider() (*STOMPAuthProvider, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.stompAuthProvider == nil {
return nil, &AuthProviderNotFoundError{}
}
return a.stompAuthProvider, nil
}

func (a *authProviderManager) SetRESTAuthProvider(regex *regexp.Regexp, provider *RESTAuthProvider) int {
a.mu.Lock()
defer a.mu.Unlock()
a.uriPatternRestAuthProviderPairs = append(a.uriPatternRestAuthProviderPairs, &regexpRESTAuthProviderPair{
regexp: regex,
restAuthProvider: provider,
})

return len(a.uriPatternRestAuthProviderPairs)-1
}

func (a *authProviderManager) SetSTOMPAuthProvider(provider *STOMPAuthProvider) error {
a.mu.Lock()
defer a.mu.Unlock()
a.stompAuthProvider = provider
return nil
}

func (a *authProviderManager) DeleteRESTAuthProvider(idx int) error {
a.mu.Lock()
defer a.mu.Unlock()
if idx < 0 || idx > len(a.uriPatternRestAuthProviderPairs)-1 {
return fmt.Errorf("no REST auth provider exists at index %d", idx)
}
borderLeft := idx
borderRight := idx+1
if idx > 0 {
borderLeft--
}
a.uriPatternRestAuthProviderPairs = append(a.uriPatternRestAuthProviderPairs[:borderLeft], a.uriPatternRestAuthProviderPairs[borderRight:]...)
return nil
}

func (a *authProviderManager) DeleteSTOMPAuthProvider() error {
a.mu.Lock()
defer a.mu.Unlock()
a.stompAuthProvider = nil
return nil
}

func GetAuthProviderManager() AuthProviderManager {
if authProviderMgrInstance == nil {
authProviderMgrInstance = &authProviderManager{}
}
return authProviderMgrInstance
}

func DestroyAuthProviderManager() {
authProviderMgrInstance = nil
}
69 changes: 69 additions & 0 deletions plank/pkg/server/auth_provider_manager/rest_auth_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package auth_provider_manager

import (
"github.com/vmware/transport-go/plank/utils"
"net/http"
"sort"
"sync"
)

type HttpRequestValidatorFn func(request *http.Request) *AuthError

type httpRequestValidatorRule struct {
name string
priority int
validatorFn HttpRequestValidatorFn
}

type RESTAuthProvider struct {
rules map[string]*httpRequestValidatorRule
rulesByPriority []*httpRequestValidatorRule
mu sync.Mutex
}

func (ap *RESTAuthProvider) Validate(request *http.Request) *AuthError {
ap.mu.Lock()
defer ap.mu.Unlock()
defer func() {
if r := recover(); r != nil {
utils.Log.Errorln(r)
}
}()

for _, rule := range ap.rulesByPriority {
err := rule.validatorFn(request)
if err != nil {
return err
}
}
return nil
}

func (ap *RESTAuthProvider) AddRule(name string, priority int, validatorFn HttpRequestValidatorFn) {
ap.mu.Lock()
defer ap.mu.Unlock()

rule := &httpRequestValidatorRule{
name: name,
priority: priority,
validatorFn: validatorFn,
}
ap.rules[name] = rule
ap.rulesByPriority = append(ap.rulesByPriority, rule)
sort.SliceStable(ap.rulesByPriority, func(i, j int) bool {
return ap.rulesByPriority[i].priority < ap.rulesByPriority[j].priority
})
}

func (ap *RESTAuthProvider) Reset() {
ap.mu.Lock()
defer ap.mu.Unlock()
ap.rules = make(map[string]*httpRequestValidatorRule)
ap.rulesByPriority = make([]*httpRequestValidatorRule, 0)
}

func NewRESTAuthProvider() *RESTAuthProvider {
ap := &RESTAuthProvider{}
ap.Reset()
return ap
}
77 changes: 77 additions & 0 deletions plank/pkg/server/auth_provider_manager/stomp_auth_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package auth_provider_manager

import (
"github.com/go-stomp/stomp/frame"
"github.com/vmware/transport-go/plank/utils"
"sort"
"sync"
)

type StompFrameValidatorFn func(fr *frame.Frame) *AuthError

type stompFrameValidatorRule struct {
msgFrameType string
priority int
validatorFn StompFrameValidatorFn
}

type STOMPAuthProvider struct {
rules map[string][]*stompFrameValidatorRule
mu sync.Mutex
}

func (ap *STOMPAuthProvider) Validate(fr *frame.Frame) error {
ap.mu.Lock()
defer ap.mu.Unlock()
defer func() {
if r := recover(); r != nil {
utils.Log.Errorln(r)
}
}()

rules, found := ap.rules[fr.Command]
// if no rule was found let the request pass through
if !found {
return nil
}

for _, rule := range rules {
err := rule.validatorFn(fr)
if err != nil {
return err
}
}
return nil
}

func (ap *STOMPAuthProvider) AddRule(types []string, priority int, validatorFn StompFrameValidatorFn) {
ap.mu.Lock()
defer ap.mu.Unlock()

for _, typ := range types {
rule := &stompFrameValidatorRule{
msgFrameType: typ,
priority: priority,
validatorFn: validatorFn,
}
if _, ok := ap.rules[typ]; !ok {
ap.rules[typ] = make([]*stompFrameValidatorRule, 0)
}
ap.rules[typ] = append(ap.rules[typ], rule)
sort.SliceStable(ap.rules[typ], func(i, j int) bool {
return ap.rules[typ][i].priority < ap.rules[typ][j].priority
})
}
}

func (ap *STOMPAuthProvider) Reset() {
ap.mu.Lock()
defer ap.mu.Unlock()
ap.rules = make(map[string][]*stompFrameValidatorRule)
}

func NewSTOMPAuthProvider() *STOMPAuthProvider {
return &STOMPAuthProvider{
rules: make(map[string][]*stompFrameValidatorRule),
}
}
20 changes: 20 additions & 0 deletions plank/pkg/server/endpointer_handler_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/sirupsen/logrus"
"github.com/vmware/transport-go/bus"
"github.com/vmware/transport-go/model"
"github.com/vmware/transport-go/plank/pkg/server/auth_provider_manager"
"github.com/vmware/transport-go/plank/utils"
"github.com/vmware/transport-go/service"
"net/http"
Expand All @@ -25,6 +27,24 @@ func buildEndpointHandler(svcChannel string, reqBuilder service.RequestBuilder,
}
}()

apm := auth_provider_manager.GetAuthProviderManager()
provider, err := apm.GetRESTAuthProvider(r.URL.Path)

if err != nil && !errors.Is(err, &auth_provider_manager.AuthProviderNotFoundError{}){
utils.Log.Errorln(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}

// run validation against rules registered in the provider only if such provider exists
if provider != nil {
err := provider.Validate(r)
if err != nil {
http.Error(w, err.Message, err.Code)
return
}
}

// set context that expires after the provided amount of time in restBridgeTimeout to prevent requests from hanging forever
ctx, cancelFn := context.WithTimeout(context.Background(), restBridgeTimeout)
defer cancelFn()
Expand Down
2 changes: 1 addition & 1 deletion stompserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (s *stompServer) waitForConnections() {
if err != nil {
log.Println("Failed to establish client connection:", err)
} else {
c := NewStompConn(rawConn, s.config, s.connectionEvents)
c := NewStompConn(rawConn, s.config, s.connectionEvents, true)

s.connectionEvents <- &ConnEvent{
ConnId: c.GetId(),
Expand Down
Loading