forked from TrueCloudLab/frostfs-s3-gw
[#XX] Support frostfsid validation
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
80237822d1
commit
c20194cc9d
13 changed files with 1001 additions and 65 deletions
|
@ -1,13 +1,18 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -65,3 +70,45 @@ func Auth(center Center, log *zap.Logger) Func {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
type FrostFSID interface {
|
||||
ValidatePublicKey(key *keys.PublicKey) error
|
||||
}
|
||||
|
||||
func IAM(frostfsID FrostFSID, log *zap.Logger) Func {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
bd, err := GetBoxData(ctx)
|
||||
if err != nil || bd.Gate.BearerToken == nil {
|
||||
reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipIAMValidation)
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateBearerToken(frostfsID, bd.Gate.BearerToken); err != nil {
|
||||
reqLogOrDefault(ctx, log).Error(logs.IAMValidationFailed, zap.Error(err))
|
||||
WriteErrorResponse(w, GetReqInfo(r.Context()), err)
|
||||
return
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func validateBearerToken(frostfsID FrostFSID, bt *bearer.Token) error {
|
||||
m := new(acl.BearerToken)
|
||||
bt.WriteToV2(m)
|
||||
|
||||
pk, err := keys.NewPublicKeyFromBytes(m.GetSignature().GetKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid bearer token public key: %w", err)
|
||||
}
|
||||
|
||||
if err = frostfsID.ValidatePublicKey(pk); err != nil {
|
||||
return fmt.Errorf("validation data user key failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -89,29 +89,50 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func AttachChi(api *chi.Mux, domains []string, throttle middleware.ThrottleOpts, h Handler, center s3middleware.Center, log *zap.Logger, appMetrics *metrics.AppMetrics) {
|
||||
type Config struct {
|
||||
Throttle middleware.ThrottleOpts
|
||||
Handler Handler
|
||||
Center s3middleware.Center
|
||||
Log *zap.Logger
|
||||
Metrics *metrics.AppMetrics
|
||||
|
||||
// Domains optional. If empty no virtual hosted domains will be attached.
|
||||
Domains []string
|
||||
|
||||
// FrostfsID optional. If nil middleware.IAM won't be attached.
|
||||
FrostfsID s3middleware.FrostFSID
|
||||
}
|
||||
|
||||
func NewRouter(cfg Config) *chi.Mux {
|
||||
api := chi.NewRouter()
|
||||
api.Use(
|
||||
s3middleware.Request(log),
|
||||
middleware.ThrottleWithOpts(throttle),
|
||||
s3middleware.Request(cfg.Log),
|
||||
middleware.ThrottleWithOpts(cfg.Throttle),
|
||||
middleware.Recoverer,
|
||||
s3middleware.Tracing(),
|
||||
s3middleware.Metrics(log, h.ResolveBucket, appMetrics),
|
||||
s3middleware.LogSuccessResponse(log),
|
||||
s3middleware.Auth(center, log),
|
||||
s3middleware.Metrics(cfg.Log, cfg.Handler.ResolveBucket, cfg.Metrics),
|
||||
s3middleware.LogSuccessResponse(cfg.Log),
|
||||
s3middleware.Auth(cfg.Center, cfg.Log),
|
||||
)
|
||||
|
||||
if cfg.FrostfsID != nil {
|
||||
api.Use(s3middleware.IAM(cfg.FrostfsID, cfg.Log))
|
||||
}
|
||||
|
||||
defaultRouter := chi.NewRouter()
|
||||
defaultRouter.Mount(fmt.Sprintf("/{%s}", s3middleware.BucketURLPrm), bucketRouter(h, log))
|
||||
defaultRouter.Get("/", named("ListBuckets", h.ListBucketsHandler))
|
||||
defaultRouter.Mount(fmt.Sprintf("/{%s}", s3middleware.BucketURLPrm), bucketRouter(cfg.Handler, cfg.Log))
|
||||
defaultRouter.Get("/", named("ListBuckets", cfg.Handler.ListBucketsHandler))
|
||||
|
||||
hr := NewHostBucketRouter("bucket")
|
||||
hr.Default(defaultRouter)
|
||||
for _, domain := range domains {
|
||||
hr.Map(domain, bucketRouter(h, log))
|
||||
for _, domain := range cfg.Domains {
|
||||
hr.Map(domain, bucketRouter(cfg.Handler, cfg.Log))
|
||||
}
|
||||
api.Mount("/", hr)
|
||||
|
||||
attachErrorHandler(api)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
func named(name string, handlerFunc http.HandlerFunc) http.HandlerFunc {
|
||||
|
|
|
@ -107,19 +107,17 @@ func TestRouterObjectEscaping(t *testing.T) {
|
|||
}
|
||||
|
||||
func prepareRouter(t *testing.T) *chi.Mux {
|
||||
throttleOps := middleware.ThrottleOpts{
|
||||
cfg := Config{
|
||||
Throttle: middleware.ThrottleOpts{
|
||||
Limit: 10,
|
||||
BacklogTimeout: 30 * time.Second,
|
||||
},
|
||||
Handler: &handlerMock{t: t},
|
||||
Center: ¢erMock{},
|
||||
Log: zaptest.NewLogger(t),
|
||||
Metrics: &metrics.AppMetrics{},
|
||||
}
|
||||
|
||||
handleMock := &handlerMock{t: t}
|
||||
cntrMock := ¢erMock{}
|
||||
log := zaptest.NewLogger(t)
|
||||
metric := &metrics.AppMetrics{}
|
||||
|
||||
chiRouter := chi.NewRouter()
|
||||
AttachChi(chiRouter, nil, throttleOps, handleMock, cntrMock, log, metric)
|
||||
return chiRouter
|
||||
return NewRouter(cfg)
|
||||
}
|
||||
|
||||
func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult {
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/notifications"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/services"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
||||
|
@ -35,7 +36,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -58,6 +58,8 @@ type (
|
|||
obj layer.Client
|
||||
api api.Handler
|
||||
|
||||
frostfsid *frostfsid.FrostFSID
|
||||
|
||||
servers []Server
|
||||
|
||||
metrics *metrics.AppMetrics
|
||||
|
@ -128,6 +130,7 @@ func (a *App) init(ctx context.Context) {
|
|||
a.setRuntimeParameters()
|
||||
a.initAPI(ctx)
|
||||
a.initMetrics()
|
||||
a.initIAM(ctx)
|
||||
a.initServers(ctx)
|
||||
a.initTracing(ctx)
|
||||
}
|
||||
|
@ -309,6 +312,22 @@ func (a *App) initMetrics() {
|
|||
a.metrics.State().SetHealth(metrics.HealthStatusStarting)
|
||||
}
|
||||
|
||||
func (a *App) initIAM(ctx context.Context) {
|
||||
if !a.cfg.GetBool(cfgFrostfsIDEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
a.frostfsid, err = frostfsid.New(ctx, frostfsid.Config{
|
||||
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
|
||||
Contract: a.cfg.GetString(cfgFrostfsIDContract),
|
||||
Key: a.key,
|
||||
})
|
||||
if err != nil {
|
||||
a.log.Fatal("init frostfsid contract", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) initResolver() {
|
||||
var err error
|
||||
a.bucketResolver, err = resolver.NewBucketResolver(a.getResolverConfig())
|
||||
|
@ -489,13 +508,25 @@ func (a *App) Serve(ctx context.Context) {
|
|||
domains := a.cfg.GetStringSlice(cfgListenDomains)
|
||||
a.log.Info(logs.FetchDomainsPrepareToUseAPI, zap.Strings("domains", domains))
|
||||
|
||||
throttleOps := middleware.ThrottleOpts{
|
||||
cfg := api.Config{
|
||||
Throttle: middleware.ThrottleOpts{
|
||||
Limit: a.settings.maxClient.count,
|
||||
BacklogTimeout: a.settings.maxClient.deadline,
|
||||
},
|
||||
Handler: a.api,
|
||||
Center: a.ctr,
|
||||
Log: a.log,
|
||||
Metrics: a.metrics,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
chiRouter := chi.NewRouter()
|
||||
api.AttachChi(chiRouter, domains, throttleOps, a.api, a.ctr, a.log, a.metrics)
|
||||
// We cannot make direct assignment if frostfsid.FrostFSID is nil
|
||||
// because in that case the interface won't be nil, it will just contain nil value.
|
||||
if a.frostfsid != nil {
|
||||
cfg.FrostfsID = a.frostfsid
|
||||
}
|
||||
|
||||
chiRouter := api.NewRouter(cfg)
|
||||
|
||||
// Use mux.Router as http.Handler
|
||||
srv := new(http.Server)
|
||||
|
|
|
@ -160,6 +160,10 @@ const ( // Settings.
|
|||
// Runtime.
|
||||
cfgSoftMemoryLimit = "runtime.soft_memory_limit"
|
||||
|
||||
// FrostfsID.
|
||||
cfgFrostfsIDEnabled = "frostfsid.enabled"
|
||||
cfgFrostfsIDContract = "frostfsid.contract"
|
||||
|
||||
// envPrefix is an environment variables prefix used for configuration.
|
||||
envPrefix = "S3_GW"
|
||||
)
|
||||
|
@ -533,6 +537,9 @@ func newSettings() *viper.Viper {
|
|||
v.SetDefault(cfgKludgeCompleteMultipartUploadKeepalive, 10*time.Second)
|
||||
v.SetDefault(cfgKludgeBypassContentEncodingCheckInChunks, false)
|
||||
|
||||
// frostfsid
|
||||
v.SetDefault(cfgFrostfsIDContract, "frostfsid.frostfs")
|
||||
|
||||
// Bind flags
|
||||
if err := bindFlags(v, flags); err != nil {
|
||||
panic(fmt.Errorf("bind flags: %w", err))
|
||||
|
|
|
@ -148,3 +148,9 @@ S3_GW_TRACING_ENDPOINT="localhost:4318"
|
|||
S3_GW_TRACING_EXPORTER="otlp_grpc"
|
||||
|
||||
S3_GW_RUNTIME_SOFT_MEMORY_LIMIT=1073741824
|
||||
|
||||
# FrostfsID contract configuration. To enable this functionality the `rpc_endpoint` param must be also set.
|
||||
# Enables check that allow requests only users that is registered in FrostfsID contract.
|
||||
S3_GW_FROSTFSID_ENABLED=false
|
||||
# FrostfsID contract hash (LE) or name in NNS.
|
||||
S3_GW_FROSTFSID_CONTRACT=frostfsid.frostfs
|
||||
|
|
|
@ -174,3 +174,10 @@ kludge:
|
|||
|
||||
runtime:
|
||||
soft_memory_limit: 1gb
|
||||
|
||||
# FrostfsID contract configuration. To enable this functionality the `rpc_endpoint` param must be also set.
|
||||
frostfsid:
|
||||
# Enables check that allow requests only users that is registered in FrostfsID contract.
|
||||
enabled: false
|
||||
# FrostfsID contract hash (LE) or name in NNS.
|
||||
contract: frostfsid.frostfs
|
||||
|
|
|
@ -186,6 +186,7 @@ There are some custom types used for brevity:
|
|||
| `resolve_bucket` | [Bucket name resolving configuration](#resolve_bucket-section) |
|
||||
| `kludge` | [Different kludge configuration](#kludge-section) |
|
||||
| `runtime` | [Runtime configuration](#runtime-section) |
|
||||
| `frostfsid` | [FrostfsID configuration](#frostfsid-section) |
|
||||
|
||||
### General section
|
||||
|
||||
|
@ -214,10 +215,10 @@ allowed_access_key_id_prefixes:
|
|||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|----------------------------------|------------|---------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|----------------------------------|------------|-------------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `listen_domains` | `[]string` | | | Domains to be able to use virtual-hosted-style access to bucket. |
|
||||
| `rpc_endpoint` | `string` | yes | | The address of the RPC host to which the gateway connects to resolve bucket names (required to use the `nns` resolver). |
|
||||
| `resolve_order` | `[]string` | yes | `[dns]` | Order of bucket name resolvers to use. Available resolvers: `dns`, `nns`. | |
|
||||
| `rpc_endpoint` | `string` | depends on context (see related sections) | | The address of the RPC host to which the gateway connects to resolve bucket names and interact with frostfs contracts (required to use the `nns` resolver and `frostfsid` contract). |
|
||||
| `resolve_order` | `[]string` | yes | `[dns]` | Order of bucket name resolvers to use. Available resolvers: `dns`, `nns`. For this resolvers `rpc_endpoint` supports SIGHUP reload. |
|
||||
| `connect_timeout` | `duration` | | `10s` | Timeout to connect to a node. |
|
||||
| `stream_timeout` | `duration` | | `10s` | Timeout for individual operations in streaming RPC. |
|
||||
| `healthcheck_timeout` | `duration` | | `15s` | Timeout to check node health during rebalance. |
|
||||
|
@ -560,3 +561,18 @@ runtime:
|
|||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|---------------------|--------|---------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `soft_memory_limit` | `size` | yes | maxint64 | Soft memory limit for the runtime. Zero or no value stands for no limit. If `GOMEMLIMIT` environment variable is set, the value from the configuration file will be ignored. |
|
||||
|
||||
# `frostfsid` section
|
||||
|
||||
FrostfsID contract configuration. To enable this functionality the `rpc_endpoint` param must be also set (In this context `rpc_endpoint` does not support SIGHUP reload).
|
||||
|
||||
```yaml
|
||||
frostfsid:
|
||||
enabled: false
|
||||
contract: frostfsid.frostfs
|
||||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|------------|----------|---------------|-------------------|----------------------------------------------------------------------------------------|
|
||||
| `enabled` | `bool` | no | false | Enables check that allow requests only users that is registered in FrostfsID contract. |
|
||||
| `contract` | `string` | no | frostfsid.frostfs | FrostfsID contract hash (LE) or name in NNS. |
|
||||
|
|
12
go.mod
12
go.mod
|
@ -12,7 +12,7 @@ require (
|
|||
github.com/google/uuid v1.3.0
|
||||
github.com/minio/sio v0.3.0
|
||||
github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d
|
||||
github.com/nspcc-dev/neo-go v0.101.2-0.20230601131642-a0117042e8fc
|
||||
github.com/nspcc-dev/neo-go v0.101.5-0.20230808195420-5fc61be5f6c5
|
||||
github.com/panjf2000/ants/v2 v2.5.0
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
|
@ -23,20 +23,19 @@ require (
|
|||
github.com/urfave/cli/v2 v2.3.0
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/trace v1.16.0
|
||||
go.uber.org/zap v1.24.0
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/crypto v0.9.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231004065251-4194633db7bb // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 // indirect
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
@ -47,9 +46,8 @@ require (
|
|||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
|
||||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
|
@ -63,7 +61,7 @@ require (
|
|||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230615193820-9185820289ce // indirect
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230808195420-5fc61be5f6c5 // indirect
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
|
|
80
internal/frostfs/frostfsid/frostfsid.go
Normal file
80
internal/frostfs/frostfsid/frostfsid.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package frostfsid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
type FrostFSID struct {
|
||||
cli *client.Client
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// RPCAddress is an endpoint to connect to neo rpc.
|
||||
RPCAddress string
|
||||
|
||||
// Contract is hash of contract or its name in NNS.
|
||||
Contract string
|
||||
|
||||
// Key is used to interact with frostfsid contract.
|
||||
// If this is nil than random key will be generated.
|
||||
Key *keys.PrivateKey
|
||||
}
|
||||
|
||||
var _ middleware.FrostFSID = (*FrostFSID)(nil)
|
||||
|
||||
// New creates new FrostfsID contract wrapper that implements auth.FrostFSID interface.
|
||||
func New(ctx context.Context, cfg Config) (*FrostFSID, error) {
|
||||
rpcCli, err := rpcclient.New(ctx, cfg.RPCAddress, rpcclient.Options{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init rpc client: %w", err)
|
||||
}
|
||||
|
||||
contractHash, err := fetchContractHash(rpcCli, cfg.Contract)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
|
||||
}
|
||||
|
||||
key := cfg.Key
|
||||
if key == nil {
|
||||
if key, err = keys.NewPrivateKey(); err != nil {
|
||||
return nil, fmt.Errorf("generate anon private key for frostfsid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cli, err := client.New(rpcCli, wallet.NewAccountFromPrivateKey(key), contractHash, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init frostfsid client: %w", err)
|
||||
}
|
||||
|
||||
return &FrostFSID{
|
||||
cli: cli,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *FrostFSID) ValidatePublicKey(key *keys.PublicKey) error {
|
||||
_, err := f.cli.GetSubjectByKey(key)
|
||||
return err
|
||||
}
|
||||
|
||||
func fetchContractHash(rpcCli *rpcclient.Client, contractName string) (util.Uint160, error) {
|
||||
if hash, err := util.Uint160DecodeStringLE(contractName); err == nil {
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
splitName := strings.Split(contractName, ".")
|
||||
if len(splitName) != 2 {
|
||||
return util.Uint160{}, fmt.Errorf("invalid contract name: '%s'", contractName)
|
||||
}
|
||||
|
||||
return nns.ResolveHash(rpcCli, contractName)
|
||||
}
|
62
internal/frostfs/nns/nns.go
Normal file
62
internal/frostfs/nns/nns.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// ResolveHash resolves contract hash.
|
||||
func ResolveHash(cli *rpcclient.Client, domain string) (util.Uint160, error) {
|
||||
nnsCs, err := cli.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("get NNS contract by id: %w", err)
|
||||
}
|
||||
|
||||
item, err := nnsResolve(invoker.New(cli, nil), nnsCs.Hash, domain)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return parseNNSResolveResult(item)
|
||||
}
|
||||
|
||||
func nnsResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
|
||||
return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT)))
|
||||
}
|
||||
|
||||
// parseNNSResolveResult parses the result of resolving NNS record.
|
||||
// It works with multiple formats (corresponding to multiple NNS versions).
|
||||
// If array of hashes is provided, it returns only the first one.
|
||||
func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
arr = []stackitem.Item{res}
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
|
||||
return util.Uint160{}, errors.New("NNS record is missing")
|
||||
}
|
||||
for i := range arr {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
h, err := address.StringToUint160(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
h, err = util.Uint160DecodeStringLE(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
return util.Uint160{}, errors.New("no valid hashes are found")
|
||||
}
|
|
@ -114,4 +114,6 @@ const (
|
|||
CouldNotInitializeAPIHandler = "could not initialize API handler" // Fatal in ../../cmd/s3-gw/app.go
|
||||
RuntimeSoftMemoryDefinedWithGOMEMLIMIT = "soft runtime memory defined with GOMEMLIMIT environment variable, config value skipped" // Warn in ../../cmd/s3-gw/app.go
|
||||
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated" // Info in ../../cmd/s3-gw/app.go
|
||||
AnonRequestSkipIAMValidation = "anon request, skip IAM validation" // Debug in ../../api/middleware/auth.go
|
||||
IAMValidationFailed = "IAM validation failed" // Error in ../../api/middleware/auth.go
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue