Skip to content

Commit

Permalink
feat: Add resource provider allowlist (#481)
Browse files Browse the repository at this point in the history
* feat: Add enable resource provider allowlist option

* feat: Add AllowedResourceProvider model

* feat: Add allowed resource provider store methods

* test: Add allowed resource provider tests

* feat: Add allowed resource provider memory implementations

* feat: Add allowed resource provider database implementations

* feat: Check resource provider when allowlist enabled
  • Loading branch information
bgins authored Feb 11, 2025
1 parent 7864ba1 commit 05f285d
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 21 deletions.
7 changes: 4 additions & 3 deletions pkg/http/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ type ServerOptions struct {
}

type AccessControlOptions struct {
ValidationTokenSecret string
ValidationTokenExpiration int
ValidationTokenKid string
EnableResourceProviderAllowlist bool
ValidationTokenSecret string
ValidationTokenExpiration int
ValidationTokenKid string
}

type ValidationToken struct {
Expand Down
18 changes: 14 additions & 4 deletions pkg/options/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ func GetDefaultServerOptions() http.ServerOptions {

func GetDefaultAccessControlOptions() http.AccessControlOptions {
return http.AccessControlOptions{
ValidationTokenSecret: GetDefaultServeOptionString("SERVER_VALIDATION_TOKEN_SECRET", ""),
ValidationTokenExpiration: GetDefaultServeOptionInt("SERVER_VALIDATION_TOKEN_EXPIRATION", 604800), // one week
ValidationTokenKid: GetDefaultServeOptionString("SERVER_VALIDATION_TOKEN_KID", ""),
// When false, any resource provider may post a resource offer.
EnableResourceProviderAllowlist: GetDefaultServeOptionBool("SERVER_ENABLE_RESOURCE_PROVIDER_ALLOWLIST", false),
ValidationTokenSecret: GetDefaultServeOptionString("SERVER_VALIDATION_TOKEN_SECRET", ""),
ValidationTokenExpiration: GetDefaultServeOptionInt("SERVER_VALIDATION_TOKEN_EXPIRATION", 604800), // one week
ValidationTokenKid: GetDefaultServeOptionString("SERVER_VALIDATION_TOKEN_KID", ""),
}
}

Expand All @@ -45,6 +47,11 @@ func AddServerCliFlags(cmd *cobra.Command, serverOptions *http.ServerOptions) {
&serverOptions.Port, "server-port", serverOptions.Port,
`The port to bind the api server to (SERVER_PORT).`,
)
cmd.PersistentFlags().BoolVar(
&serverOptions.AccessControl.EnableResourceProviderAllowlist, "server-enable-resource-provider-allowlist",
serverOptions.AccessControl.EnableResourceProviderAllowlist,
`Enable resource provider allowlist (SERVER_ENABLE_RESOURCE_PROVIDER_ALLOWLIST).`,
)
cmd.PersistentFlags().StringVar(
&serverOptions.AccessControl.ValidationTokenSecret, "server-validation-token-secret",
serverOptions.AccessControl.ValidationTokenSecret,
Expand All @@ -70,10 +77,13 @@ func AddServerCliFlags(cmd *cobra.Command, serverOptions *http.ServerOptions) {
)
}

func CheckServerOptions(options http.ServerOptions) error {
func CheckServerOptions(options http.ServerOptions, storeType string) error {
if options.URL == "" {
return fmt.Errorf("SERVER_URL is required")
}
if options.AccessControl.EnableResourceProviderAllowlist && storeType == "memory" {
return fmt.Errorf("Enabling the resource provider allowlist requires the database store. Set STORE_TYPE to \"database\".")
}
if options.AccessControl.ValidationTokenSecret == "" {
return fmt.Errorf("SERVER_VALIDATION_TOKEN_SECRET is required")
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/options/solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ func AddSolverCliFlags(cmd *cobra.Command, options *solver.SolverOptions) {
}

func CheckSolverOptions(options solver.SolverOptions) error {
err := CheckServerOptions(options.Server)
err := CheckStoreOptions(options.Store)
if err != nil {
return err
}
err = CheckStoreOptions(options.Store)
err = CheckServerOptions(options.Server, options.Store.Type)
if err != nil {
return err
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/solver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
corehttp "net/http"
"os"
"path/filepath"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -312,6 +313,21 @@ func (solverServer *solverServer) addResourceOffer(resourceOffer data.ResourceOf
if signerAddress != resourceOffer.ResourceProvider {
return nil, fmt.Errorf("resource provider address does not match signer address")
}

// Resource provider must be in allowlist when enabled
if solverServer.options.AccessControl.EnableResourceProviderAllowlist {
allowedProviders, err := solverServer.store.GetAllowedResourceProviders()
if err != nil {
log.Error().Err(err).Msgf("Unable to load resource provider allowlist: %s", err)
return nil, err
}

if !slices.Contains(allowedProviders, resourceOffer.ResourceProvider) {
log.Debug().Msgf("resource provider not in allowlist %s", resourceOffer.ResourceProvider)
return nil, errors.New("resource provider not in beta program, request beta program access here: https://forms.gle/XaE3rRuXVLxTnZto7")
}
}

err = data.CheckResourceOffer(resourceOffer)
if err != nil {
log.Error().Err(err).Msgf("Error checking resource offer")
Expand Down
36 changes: 36 additions & 0 deletions pkg/solver/store/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewSolverStoreDatabase(connStr string, gormLogLevel string) (*SolverStoreDa
db.AutoMigrate(&Deal{})
db.AutoMigrate(&Result{})
db.AutoMigrate(&MatchDecision{})
db.AutoMigrate(&AllowedResourceProvider{})

return &SolverStoreDatabase{db}, nil
}
Expand Down Expand Up @@ -135,6 +136,19 @@ func (store *SolverStoreDatabase) AddMatchDecision(resourceOffer string, jobOffe
return decision, nil
}

func (store *SolverStoreDatabase) AddAllowedResourceProvider(resourceProvider string) (string, error) {
record := AllowedResourceProvider{
ResourceProvider: resourceProvider,
}

result := store.db.Create(&record)
if result.Error != nil {
return "", result.Error
}

return resourceProvider, nil
}

func (store *SolverStoreDatabase) GetJobOffers(query store.GetJobOffersQuery) ([]data.JobOfferContainer, error) {
q := store.db.Where([]JobOffer{})

Expand Down Expand Up @@ -274,6 +288,20 @@ func (store *SolverStoreDatabase) GetMatchDecisions() ([]data.MatchDecision, err
return decisions, nil
}

func (store *SolverStoreDatabase) GetAllowedResourceProviders() ([]string, error) {
var records []AllowedResourceProvider
if err := store.db.Find(&records).Error; err != nil {
return nil, err
}

providers := make([]string, len(records))
for i, record := range records {
providers[i] = record.ResourceProvider
}

return providers, nil
}

func (store *SolverStoreDatabase) GetJobOffer(id string) (*data.JobOfferContainer, error) {
// Offers are unique by CID, so we can query first
var record JobOffer
Expand Down Expand Up @@ -621,6 +649,14 @@ func (store *SolverStoreDatabase) RemoveMatchDecision(resourceOffer string, jobO
return nil
}

func (store *SolverStoreDatabase) RemoveAllowedResourceProvider(resourceProvider string) error {
result := store.db.Where("resource_provider = ?", resourceProvider).Delete(&AllowedResourceProvider{})
if result.Error != nil {
return result.Error
}
return nil
}

// Strictly speaking, the compiler will check the interface
// implementation without this check. But some code editors
// report errors more effectively when we have it.
Expand Down
5 changes: 5 additions & 0 deletions pkg/solver/store/db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ type MatchDecision struct {
JobOffer string `gorm:"primaryKey"`
Attributes datatypes.JSONType[data.MatchDecision]
}

type AllowedResourceProvider struct {
gorm.Model
ResourceProvider string `gorm:"index"`
}
51 changes: 40 additions & 11 deletions pkg/solver/store/memory/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ import (
)

type SolverStoreMemory struct {
jobOfferMap map[string]*data.JobOfferContainer
resourceOfferMap map[string]*data.ResourceOfferContainer
dealMap map[string]*data.DealContainer
resultMap map[string]*data.Result
matchDecisionMap map[string]*data.MatchDecision
mutex sync.RWMutex
jobOfferMap map[string]*data.JobOfferContainer
resourceOfferMap map[string]*data.ResourceOfferContainer
dealMap map[string]*data.DealContainer
resultMap map[string]*data.Result
matchDecisionMap map[string]*data.MatchDecision
allowedResourceProviderMap map[string]string
mutex sync.RWMutex
}

func NewSolverStoreMemory() (*SolverStoreMemory, error) {
return &SolverStoreMemory{
jobOfferMap: map[string]*data.JobOfferContainer{},
resourceOfferMap: map[string]*data.ResourceOfferContainer{},
dealMap: map[string]*data.DealContainer{},
resultMap: map[string]*data.Result{},
matchDecisionMap: map[string]*data.MatchDecision{},
jobOfferMap: map[string]*data.JobOfferContainer{},
resourceOfferMap: map[string]*data.ResourceOfferContainer{},
dealMap: map[string]*data.DealContainer{},
resultMap: map[string]*data.Result{},
matchDecisionMap: map[string]*data.MatchDecision{},
allowedResourceProviderMap: map[string]string{},
}, nil
}

Expand Down Expand Up @@ -80,6 +82,14 @@ func (s *SolverStoreMemory) AddMatchDecision(resourceOffer string, jobOffer stri
return decision, nil
}

func (store *SolverStoreMemory) AddAllowedResourceProvider(resourceProvider string) (string, error) {
store.mutex.Lock()
defer store.mutex.Unlock()
store.allowedResourceProviderMap[resourceProvider] = resourceProvider

return resourceProvider, nil
}

func (s *SolverStoreMemory) GetJobOffers(query store.GetJobOffersQuery) ([]data.JobOfferContainer, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
Expand Down Expand Up @@ -209,6 +219,18 @@ func (s *SolverStoreMemory) GetMatchDecisions() ([]data.MatchDecision, error) {
return results, nil
}

func (store *SolverStoreMemory) GetAllowedResourceProviders() ([]string, error) {
store.mutex.RLock()
defer store.mutex.RUnlock()

providers := []string{}
for provider := range store.allowedResourceProviderMap {
providers = append(providers, provider)
}

return providers, nil
}

func (s *SolverStoreMemory) GetJobOffer(id string) (*data.JobOfferContainer, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
Expand Down Expand Up @@ -433,6 +455,13 @@ func (s *SolverStoreMemory) RemoveMatchDecision(resourceOffer string, jobOffer s
return nil
}

func (store *SolverStoreMemory) RemoveAllowedResourceProvider(resourceProvider string) error {
store.mutex.Lock()
defer store.mutex.Unlock()
delete(store.allowedResourceProviderMap, resourceProvider)
return nil
}

// Strictly speaking, the compiler will check the interface
// implementation without this check. But some code editors
// report errors more effectively when we have it.
Expand Down
3 changes: 3 additions & 0 deletions pkg/solver/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ type SolverStore interface {
AddDeal(deal data.DealContainer) (*data.DealContainer, error)
AddResult(result data.Result) (*data.Result, error)
AddMatchDecision(resourceOffer string, jobOffer string, deal string, result bool) (*data.MatchDecision, error)
AddAllowedResourceProvider(resourceProvider string) (string, error)
GetJobOffers(query GetJobOffersQuery) ([]data.JobOfferContainer, error)
GetResourceOffers(query GetResourceOffersQuery) ([]data.ResourceOfferContainer, error)
GetDeals(query GetDealsQuery) ([]data.DealContainer, error)
GetDealsAll() ([]data.DealContainer, error)
GetResults() ([]data.Result, error)
GetMatchDecisions() ([]data.MatchDecision, error)
GetAllowedResourceProviders() ([]string, error)
GetJobOffer(id string) (*data.JobOfferContainer, error)
GetResourceOffer(id string) (*data.ResourceOfferContainer, error)
GetResourceOfferByAddress(address string) (*data.ResourceOfferContainer, error)
Expand All @@ -91,6 +93,7 @@ type SolverStore interface {
RemoveDeal(id string) error
RemoveResult(id string) error
RemoveMatchDecision(resourceOffer string, jobOffer string) error
RemoveAllowedResourceProvider(resourceProvider string) error
}

func GetMatchID(resourceOffer string, jobOffer string) string {
Expand Down
Loading

0 comments on commit 05f285d

Please sign in to comment.