diff --git a/bidengine/order.go b/bidengine/order.go index 70411bbb3..b55e1beaf 100644 --- a/bidengine/order.go +++ b/bidengine/order.go @@ -25,6 +25,10 @@ import ( ctypes "github.com/akash-network/provider/cluster/types/v1beta3" "github.com/akash-network/provider/event" "github.com/akash-network/provider/session" + + clusterClient "github.com/akash-network/provider/cluster/kube" + providerflags "github.com/akash-network/provider/cmd/provider-services/cmd/flags" + "github.com/spf13/viper" ) // order manages bidding and general lifecycle handling of an order. @@ -402,11 +406,64 @@ loop: offer := mtypes.ResourceOfferFromRU(reservation.GetAllocatedResources()) - // Begin submitting fulfillment - msg = mtypes.NewMsgCreateBid(o.orderID, o.session.Provider().Address(), price, o.cfg.Deposit, offer) - bidch = runner.Do(func() runner.Result { - return runner.NewResult(o.session.Client().Tx().Broadcast(ctx, []sdk.Msg{msg}, aclient.WithResultCodeAsError())) - }) + // Check if the provider's address matches the allowed or denied wallet addresses. + var allowWalletAddress, denyWalletAddress []string + ownerAddress := o.orderID.Owner + + // Fetching configuration path and namespace from viper. + configPath, ns := viper.GetString(providerflags.FlagKubeConfig), viper.GetString(providerflags.FlagK8sManifestNS) + + // Initialize a new client for cluster operations. + client, err := clusterClient.NewClient(ctx, o.log, ns, configPath) + if err != nil { + o.log.Error("Failed to initialize cluster client", "err", err) + break loop + } + + // Retrieving all moderation filters. + allModerationFilters, err := client.AllModerationFilters(ctx) + if err != nil { + o.log.Error("Failed to retrieve moderation filters", "error", err) + break loop + } + + // Filtering allowed and denied wallet addresses based on moderation filters. + for _, allFilters := range allModerationFilters { + if allFilters.Type == "TenantAddress" && allFilters.Allow { + allowWalletAddress = append(allowWalletAddress, allFilters.Pattern) + } + if allFilters.Type == "TenantAddress" && !allFilters.Allow { + denyWalletAddress = append(denyWalletAddress, allFilters.Pattern) + } + } + + // Check if both allowWalletAddress and denyWalletAddress are empty + if len(allowWalletAddress) == 0 && len(denyWalletAddress) == 0 { + // Both lists are empty, proceeding to create and submit the bid + // Begin submitting fulfillment + msg = mtypes.NewMsgCreateBid(o.orderID, o.session.Provider().Address(), price, o.cfg.Deposit, offer) + bidch = runner.Do(func() runner.Result { + return runner.NewResult(o.session.Client().Tx().Broadcast(ctx, []sdk.Msg{msg}, aclient.WithResultCodeAsError())) + }) + } else { + // Checking if the owner's address is in the allow or deny list and acting accordingly. + if ContainsString(allowWalletAddress, ownerAddress) { + // Address is allowed, proceeding to create and submit the bid. + // Begin submitting fulfillment + msg = mtypes.NewMsgCreateBid(o.orderID, o.session.Provider().Address(), price, o.cfg.Deposit, offer) + bidch = runner.Do(func() runner.Result { + return runner.NewResult(o.session.Client().Tx().Broadcast(ctx, []sdk.Msg{msg}, aclient.WithResultCodeAsError())) + }) + } else if ContainsString(denyWalletAddress, ownerAddress) { + o.log.Info("Wallet Address Check", "denyWalletAddress", denyWalletAddress, "foundAddress", ownerAddress) + o.log.Error("Bid not placed: deployer's address is denied", "address", ownerAddress) + break loop + } else { + o.log.Info("Wallet Address Check", "allowedAddresses", allowWalletAddress, "foundAddress", ownerAddress) + o.log.Error("Bid not placed: deployer's address is not in the allow list", "address", ownerAddress) + break loop + } + } case result := <-bidch: bidch = nil @@ -488,6 +545,15 @@ loop: } } +func ContainsString(slice []string, target string) bool { + for _, element := range slice { + if element == target { + return true + } + } + return false +} + func (o *order) shouldBid(group *dtypes.Group) (bool, error) { // does provider have required attributes? if !group.GroupSpec.MatchAttributes(o.session.Provider().Attributes) { diff --git a/cluster/client.go b/cluster/client.go index fa2fe956f..05212a81c 100644 --- a/cluster/client.go +++ b/cluster/client.go @@ -54,6 +54,8 @@ type ReadClient interface { AllHostnames(context.Context) ([]ctypes.ActiveHostname, error) GetManifestGroup(context.Context, mtypes.LeaseID) (bool, crd.ManifestGroup, error) + AllModerationFilters(context.Context) ([]ctypes.ActiveFilters, error) + ObserveHostnameState(ctx context.Context) (<-chan ctypes.HostnameResourceEvent, error) GetHostnameDeploymentConnections(ctx context.Context) ([]ctypes.LeaseIDHostnameConnection, error) @@ -607,6 +609,10 @@ func (c *nullClient) AllHostnames(context.Context) ([]ctypes.ActiveHostname, err return nil, nil } +func (c *nullClient) AllModerationFilters(context.Context) ([]ctypes.ActiveFilters, error) { + return nil, nil +} + func (c *nullClient) KubeVersion() (*version.Info, error) { return nil, nil } diff --git a/cluster/kube/client_moderationfilters_connection.go b/cluster/kube/client_moderationfilters_connection.go new file mode 100644 index 000000000..d84dbbae7 --- /dev/null +++ b/cluster/kube/client_moderationfilters_connection.go @@ -0,0 +1,45 @@ +package kube + +import ( + "context" + "fmt" + + "github.com/akash-network/provider/cluster/kube/builder" + ctypes "github.com/akash-network/provider/cluster/types/v1beta3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/pager" + + crd "github.com/akash-network/provider/pkg/apis/akash.network/v2beta2" +) + +func (c *client) AllModerationFilters(ctx context.Context) ([]ctypes.ActiveFilters, error) { + result := make([]ctypes.ActiveFilters, 0) + + filterPager := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + return c.ac.AkashV2beta2().ModerationFilters(c.ns).List(ctx, opts) + }) + + listOptions := metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=true", builder.AkashManagedLabelName), + } + + err := filterPager.EachListItem(ctx, listOptions, func(obj runtime.Object) error { + fp := obj.(*crd.ModerationFilter) + + for _, filter := range fp.Spec { + result = append(result, ctypes.ActiveFilters{ + Allow: filter.Allow, + Type: filter.Type, + Pattern: filter.Pattern, + }) + } + return nil + + }) + + if err != nil { + return nil, err + } + return result, nil +} diff --git a/cluster/mocks/client.go b/cluster/mocks/client.go index 0700c8624..c4a5a7725 100644 --- a/cluster/mocks/client.go +++ b/cluster/mocks/client.go @@ -89,6 +89,60 @@ func (_c *Client_AllHostnames_Call) RunAndReturn(run func(context.Context) ([]v1 return _c } +// AllHostnames provides a mock function with given fields: _a0 +func (_m *Client) AllModerationFilters(_a0 context.Context) ([]v1beta3.ActiveFilters, error) { + ret := _m.Called(_a0) + + var r0 []v1beta3.ActiveFilters + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]v1beta3.ActiveFilters, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) []v1beta3.ActiveFilters); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v1beta3.ActiveFilters) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_ActiveFilters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ActiveFilters' +type Client_ActiveFilters_Call struct { + *mock.Call +} + +// ActiveFilters is a helper method to define mock.On call +// - _a0 context.Context +func (_e *Client_Expecter) ActiveFilters(_a0 interface{}) *Client_ActiveFilters_Call { + return &Client_ActiveFilters_Call{Call: _e.mock.On("ActiveFilters", _a0)} +} + +func (_c *Client_ActiveFilters_Call) Run(run func(_a0 context.Context)) *Client_ActiveFilters_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Client_ActiveFilters_Call) Return(_a0 []v1beta3.ActiveFilters, _a1 error) *Client_ActiveFilters_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_ActiveFilters_Call) RunAndReturn(run func(context.Context) ([]v1beta3.ActiveFilters, error)) *Client_ActiveFilters_Call { + _c.Call.Return(run) + return _c +} + // ConnectHostnameToDeployment provides a mock function with given fields: ctx, directive func (_m *Client) ConnectHostnameToDeployment(ctx context.Context, directive v1beta3.ConnectHostnameToDeploymentDirective) error { ret := _m.Called(ctx, directive) diff --git a/cluster/types/v1beta3/moderationfilter.go b/cluster/types/v1beta3/moderationfilter.go new file mode 100644 index 000000000..a9360ffa2 --- /dev/null +++ b/cluster/types/v1beta3/moderationfilter.go @@ -0,0 +1,7 @@ +package v1beta3 + +type ActiveFilters struct { + Allow bool + Pattern string + Type string +} diff --git a/pkg/apis/akash.network/crd.yaml b/pkg/apis/akash.network/crd.yaml index 2bde5868d..bb2642133 100644 --- a/pkg/apis/akash.network/crd.yaml +++ b/pkg/apis/akash.network/crd.yaml @@ -526,3 +526,49 @@ spec: type: string sharing_key: type: string +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + # name must match the spec fields below, and be in the form: . + name: moderationfilters.akash.network + # DO NOT REMOVE resource-policy annotation! + annotations: + "helm.sh/resource-policy": keep +spec: + names: + # plural name to be used in the URL: /apis/// + plural: moderationfilters + # singular name to be used as an alias on the CLI and for display + singular: moderationfilter + # kind is normally the CamelCased singular type. Your resource manifests use this. + kind: ModerationFilter + # shortNames allow shorter string to match your resource on the CLI + shortNames: + - mfilter + # group name to use for REST API: /apis// + # list of versions supported by this CustomResourceDefinition + group: akash.network + scope: Namespaced + versions: + - name: v2beta2 + # Each version can be enabled/disabled by Served flag. + served: true + # One and only one version must be marked as the storage version. + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: array + items: + type: object + properties: + type: + type: string + enum: [TenantAddress, Hostname, Image] + pattern: + type: string + allow: + type: boolean diff --git a/pkg/apis/akash.network/v2beta2/moderationfilter.go b/pkg/apis/akash.network/v2beta2/moderationfilter.go new file mode 100644 index 000000000..bdfa006b7 --- /dev/null +++ b/pkg/apis/akash.network/v2beta2/moderationfilter.go @@ -0,0 +1,30 @@ +package v2beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ModerationFilter store metadata, specifications and status of the ModerationFilter +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ModerationFilter struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec []ModerationFilterSpec `json:"spec,omitempty"` +} + +// ModerationFilterList stores metadata and items list of moderationfilter +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ModerationFilterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []ModerationFilter `json:"items"` +} + +// ModerationFilterSpec stores LeaseID, Group and metadata details +type ModerationFilterSpec struct { + Type string `json:"type"` + Allow bool `json:"allow"` + Pattern string `json:"pattern"` +} diff --git a/pkg/apis/akash.network/v2beta2/types.go b/pkg/apis/akash.network/v2beta2/types.go index 41ca22ef2..72bc632ce 100644 --- a/pkg/apis/akash.network/v2beta2/types.go +++ b/pkg/apis/akash.network/v2beta2/types.go @@ -208,3 +208,13 @@ func resourcesFromAkash(aru types.Resources) (Resources, error) { return res, nil } + +type FilterName struct { + Rules []Rules `json:"rules"` +} + +type Rules struct { + Type string `json:"type"` + Allow bool `json:"allow"` + Pattern string `json:"size"` +} diff --git a/pkg/apis/akash.network/v2beta2/zz_generated.deepcopy.go b/pkg/apis/akash.network/v2beta2/zz_generated.deepcopy.go index ace644d5d..c9fcb3c65 100644 --- a/pkg/apis/akash.network/v2beta2/zz_generated.deepcopy.go +++ b/pkg/apis/akash.network/v2beta2/zz_generated.deepcopy.go @@ -1019,3 +1019,84 @@ func (in *StorageCapabilities) DeepCopy() *StorageCapabilities { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ModerationFilter) DeepCopyInto(out *ModerationFilter) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + for index, inSpec := range in.Spec { + inSpec.DeepCopyInto(&out.Spec[index]) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Manifest. +func (in *ModerationFilter) DeepCopy() *ModerationFilter { + if in == nil { + return nil + } + out := new(ModerationFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ModerationFilter) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ModerationFilterList) DeepCopyInto(out *ModerationFilterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ModerationFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManifestList. +func (in *ModerationFilterList) DeepCopy() *ModerationFilterList { + if in == nil { + return nil + } + out := new(ModerationFilterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ModerationFilterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ModerationFilterSpec) DeepCopyInto(out *ModerationFilterSpec) { + *out = *in + out.Allow = in.Allow + out.Pattern = in.Pattern + out.Type = in.Type + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModerationFilterSpec. +func (in *ModerationFilterSpec) DeepCopy() *ModerationFilterSpec { + if in == nil { + return nil + } + out := new(ModerationFilterSpec) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/akash.network_client.go b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/akash.network_client.go index e7470cd56..0448a10b3 100644 --- a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/akash.network_client.go +++ b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/akash.network_client.go @@ -31,6 +31,7 @@ type AkashV2beta2Interface interface { InventoriesGetter InventoryRequestsGetter ManifestsGetter + ModerationFiltersGetter ProviderHostsGetter ProviderLeasedIPsGetter } @@ -52,6 +53,10 @@ func (c *AkashV2beta2Client) Manifests(namespace string) ManifestInterface { return newManifests(c, namespace) } +func (c *AkashV2beta2Client) ModerationFilters(namespace string) ModerationFilterInterface { + return newModerationFilters(c, namespace) +} + func (c *AkashV2beta2Client) ProviderHosts(namespace string) ProviderHostInterface { return newProviderHosts(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_akash.network_client.go b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_akash.network_client.go index 34a8a12ce..57d261994 100644 --- a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_akash.network_client.go +++ b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_akash.network_client.go @@ -40,6 +40,10 @@ func (c *FakeAkashV2beta2) Manifests(namespace string) v2beta2.ManifestInterface return &FakeManifests{c, namespace} } +func (c *FakeAkashV2beta2) ModerationFilters(namespace string) v2beta2.ModerationFilterInterface { + return &FakeModerationFilters{c, namespace} +} + func (c *FakeAkashV2beta2) ProviderHosts(namespace string) v2beta2.ProviderHostInterface { return &FakeProviderHosts{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_moderation_filter.go b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_moderation_filter.go new file mode 100644 index 000000000..9c2c95620 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/fake/fake_moderation_filter.go @@ -0,0 +1,54 @@ +package fake + +import ( + "context" + + v2beta2 "github.com/akash-network/provider/pkg/apis/akash.network/v2beta2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + testing "k8s.io/client-go/testing" +) + +// FakeModerationFilters implements ModerationFilterInterface +type FakeModerationFilters struct { + Fake *FakeAkashV2beta2 + ns string +} + +var moderationfiltersResource = schema.GroupVersionResource{Group: "akash.network", Version: "v2beta2", Resource: "moderationfilters"} + +var moderationfiltersKind = schema.GroupVersionKind{Group: "akash.network", Version: "v2beta2", Kind: "ModerationFilter"} + +// Get takes name of the moderationfilter, and returns the corresponding moderationfilter object, and an error if there is any. +func (c *FakeModerationFilters) Get(ctx context.Context, name string, options v1.GetOptions) (result *v2beta2.ModerationFilter, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(moderationfiltersResource, c.ns, name), &v2beta2.ModerationFilter{}) + + if obj == nil { + return nil, err + } + return obj.(*v2beta2.ModerationFilter), err +} + +// List takes label and field selectors, and returns the list of ModerationFilters that match those selectors. +func (c *FakeModerationFilters) List(ctx context.Context, opts v1.ListOptions) (result *v2beta2.ModerationFilterList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(moderationfiltersResource, moderationfiltersKind, c.ns, opts), &v2beta2.ModerationFilterList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v2beta2.ModerationFilterList{ListMeta: obj.(*v2beta2.ModerationFilterList).ListMeta} + for _, item := range obj.(*v2beta2.ModerationFilterList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} diff --git a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/generated_expansion.go b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/generated_expansion.go index 75cfc83c1..09a4a72fa 100644 --- a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/generated_expansion.go @@ -24,6 +24,8 @@ type InventoryRequestExpansion interface{} type ManifestExpansion interface{} +type ModerationFilterExpansion interface{} + type ProviderHostExpansion interface{} type ProviderLeasedIPExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/akash.network/v2beta2/moderationfilter.go b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/moderationfilter.go new file mode 100644 index 000000000..062b74470 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/akash.network/v2beta2/moderationfilter.go @@ -0,0 +1,72 @@ +/* +Copyright The Akash Network 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v2beta2 + +import ( + "context" + "time" + + v2beta2 "github.com/akash-network/provider/pkg/apis/akash.network/v2beta2" + scheme "github.com/akash-network/provider/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rest "k8s.io/client-go/rest" +) + +// ModerationFiltersGetter has a method to return a ModerationFilterInterface. +// A group's client should implement this interface. +type ModerationFiltersGetter interface { + ModerationFilters(namespace string) ModerationFilterInterface +} + +// ModerationFilterInterface has methods to work with ModerationFilter resources. +type ModerationFilterInterface interface { + List(ctx context.Context, opts v1.ListOptions) (*v2beta2.ModerationFilterList, error) + ModerationFilterExpansion +} + +// moderationfilters implements ModerationFilterInterface +type moderationfilters struct { + client rest.Interface + ns string +} + +// newModerationFilters returns a ModerationFilters +func newModerationFilters(c *AkashV2beta2Client, namespace string) *moderationfilters { + return &moderationfilters{ + client: c.RESTClient(), + ns: namespace, + } +} + +// List takes label and field selectors, and returns the list of ModerationFilters that match those selectors. +func (c *moderationfilters) List(ctx context.Context, opts v1.ListOptions) (result *v2beta2.ModerationFilterList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v2beta2.ModerationFilterList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("moderationfilters"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +}