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

feat: Add resource provider allowlist #481

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
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 @@ -268,6 +282,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 @@ -615,6 +643,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 @@ -10,21 +10,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 @@ -79,6 +81,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 @@ -190,6 +200,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 @@ -414,6 +436,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 @@ -61,12 +61,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 @@ -85,6 +87,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
Loading