diff --git a/.github/workflows/beekeeper.yml b/.github/workflows/beekeeper.yml index 17117b83cc4..71c069a2366 100644 --- a/.github/workflows/beekeeper.yml +++ b/.github/workflows/beekeeper.yml @@ -154,9 +154,6 @@ jobs: - name: Test manifest id: manifest run: timeout ${TIMEOUT} beekeeper check --cluster-name local-dns --checks=ci-manifest - - name: Test authenticate - id: authenticate - run: timeout ${TIMEOUT} bash -c 'until beekeeper check --cluster-name local-dns --checks ci-authenticate; do echo "waiting for auth..."; sleep .3; done' - name: Test postage stamps id: postage-stamps run: timeout ${TIMEOUT} beekeeper check --cluster-name local-dns --checks ci-postage diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 4008e3a7b4e..83097774128 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -40,7 +40,7 @@ jobs: if: steps.checkdocs.outputs.build_docs == 'true' uses: acud/openapi-dockerized@v1 with: - build-roots: 'openapi/Swarm.yaml openapi/SwarmDebug.yaml' + build-roots: 'openapi/Swarm.yaml' env: AWS_ACCESS_KEY_ID: ${{ secrets.DO_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DO_AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7d7edc9e0b4..75eec69aab7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -33,10 +33,9 @@ jobs: env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Set the API and debug API versions + - name: Set the API version run: | echo "BEE_API_VERSION=$(grep '^ version:' openapi/Swarm.yaml | awk '{print $2}')" >> $GITHUB_ENV - echo "BEE_DEBUG_API_VERSION=$(grep '^ version:' openapi/SwarmDebug.yaml | awk '{print $2}')" >> $GITHUB_ENV - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: @@ -50,4 +49,3 @@ jobs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }} BEE_API_VERSION: ${{ env.BEE_API_VERSION }} - BEE_DEBUG_API_VERSION: ${{ env.BEE_DEBUG_API_VERSION }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 0f8844cbfdc..d84f3b1e8cd 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -19,7 +19,6 @@ builds: - -X github.com/ethersphere/bee/v2.commitHash={{ .ShortCommit }} - -X github.com/ethersphere/bee/v2.commitTime={{ .CommitTimestamp }} - -X github.com/ethersphere/bee/v2/pkg/api.Version={{ .Env.BEE_API_VERSION }} - - -X github.com/ethersphere/bee/v2/pkg/debugapi.Version={{ .Env.BEE_DEBUG_API_VERSION }} env: - CGO_ENABLED=0 goos: @@ -43,7 +42,6 @@ builds: - -X github.com/ethersphere/bee/v2.commitHash={{ .ShortCommit }} - -X github.com/ethersphere/bee/v2.commitTime={{ .CommitTimestamp }} - -X github.com/ethersphere/bee/v2/pkg/api.Version={{ .Env.BEE_API_VERSION }} - - -X github.com/ethersphere/bee/v2/pkg/debugapi.Version={{ .Env.BEE_DEBUG_API_VERSION }} env: - CGO_ENABLED=0 goos: @@ -69,7 +67,6 @@ builds: - -X github.com/ethersphere/bee/v2.commitHash={{ .ShortCommit }} - -X github.com/ethersphere/bee/v2.commitTime={{ .CommitTimestamp }} - -X github.com/ethersphere/bee/v2/pkg/api.Version={{ .Env.BEE_API_VERSION }} - - -X github.com/ethersphere/bee/v2/pkg/debugapi.Version={{ .Env.BEE_DEBUG_API_VERSION }} env: - CGO_ENABLED=0 goos: @@ -89,7 +86,6 @@ builds: - -X github.com/ethersphere/bee/v2.commitHash={{ .ShortCommit }} - -X github.com/ethersphere/bee/v2.commitTime={{ .CommitTimestamp }} - -X github.com/ethersphere/bee/v2/pkg/api.Version={{ .Env.BEE_API_VERSION }} - - -X github.com/ethersphere/bee/v2/pkg/debugapi.Version={{ .Env.BEE_DEBUG_API_VERSION }} env: - CGO_ENABLED=0 goos: @@ -108,7 +104,6 @@ builds: - -X github.com/ethersphere/bee/v2.commitHash={{ .ShortCommit }} - -X github.com/ethersphere/bee/v2.commitTime={{ .CommitTimestamp }} - -X github.com/ethersphere/bee/v2/pkg/api.Version={{ .Env.BEE_API_VERSION }} - - -X github.com/ethersphere/bee/v2/pkg/debugapi.Version={{ .Env.BEE_DEBUG_API_VERSION }} env: - CGO_ENABLED=0 goos: diff --git a/CODING.md b/CODING.md index 98efe16e63f..f72712ee8fe 100644 --- a/CODING.md +++ b/CODING.md @@ -206,9 +206,9 @@ A value of `all` will enable the highest verbosity of V-level. Examples: -`curl -XPUT http://localhost:1635/loggers/bm9kZS8q/none` - will disable all loggers; `bm9kZS8q` is base64 encoded `node/*` regular expression. +`curl -XPUT http://localhost:1633/loggers/bm9kZS8q/none` - will disable all loggers; `bm9kZS8q` is base64 encoded `node/*` regular expression. -`curl -XPUT http://localhost:1635/loggers/bm9kZS9hcGlbMV1bXT4-ODI0NjM0OTMzMjU2/error` - will set the verbosity of the logger with the subsystem `node/api[1][]>>824634933256` to `error`. +`curl -XPUT http://localhost:1633/loggers/bm9kZS9hcGlbMV1bXT4-ODI0NjM0OTMzMjU2/error` - will set the verbosity of the logger with the subsystem `node/api[1][]>>824634933256` to `error`. ## Commit Messages diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba690740ec1..88973b5ffe2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ These are work items that are good if you're contributing to the codebase for th - Performance optimizations - The best way to propose any optimizations would be to provide the relevant data to describe the problem and then also the same data after the optimizations are done. Keep in mind, Bee nodes work in a distributed system, so changes that would seem good locally may not hold in some cases. The Bee client in debug mode can show you metrics as well as pprof information. This can be used to demonstrate the optimizations. + The best way to propose any optimizations would be to provide the relevant data to describe the problem and then also the same data after the optimizations are done. Keep in mind, Bee nodes work in a distributed system, so changes that would seem good locally may not hold in some cases. The Bee client can show you metrics as well as pprof information. This can be used to demonstrate the optimizations. - Concurrency related optimizations diff --git a/Dockerfile b/Dockerfile index 5aa35de1e8c..e0f0738de6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 AS build +FROM golang:1.22 AS build WORKDIR /src # enable modules caching in separate layer @@ -24,7 +24,7 @@ RUN mkdir -p /home/bee/.bee && chown 999:999 /home/bee/.bee COPY --from=build /src/dist/bee /usr/local/bin/bee -EXPOSE 1633 1634 1635 +EXPOSE 1633 1634 USER bee WORKDIR /home/bee VOLUME /home/bee/.bee diff --git a/Dockerfile.ci b/Dockerfile.ci index 2c12b44ad4a..b2f962ddc13 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -8,7 +8,7 @@ RUN addgroup --system bee --gid 998; \ COPY bee /bee -EXPOSE 1633 1634 1635 +EXPOSE 1633 1634 USER bee WORKDIR /home/bee diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser index 907c29ed0ab..cd1fe90dc0d 100644 --- a/Dockerfile.goreleaser +++ b/Dockerfile.goreleaser @@ -14,7 +14,7 @@ RUN mkdir -p /home/bee/.bee && chown 999:999 /home/bee/.bee COPY bee /usr/local/bin/bee -EXPOSE 1633 1634 1635 +EXPOSE 1633 1634 USER bee WORKDIR /home/bee VOLUME /home/bee/.bee diff --git a/Dockerfile.scratch b/Dockerfile.scratch index 85905fd691b..40b0bec6c94 100644 --- a/Dockerfile.scratch +++ b/Dockerfile.scratch @@ -17,7 +17,7 @@ COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs COPY --from=0 /etc/passwd /etc/passwd COPY --from=0 /home /home -EXPOSE 1633 1634 1635 +EXPOSE 1633 1634 USER bee WORKDIR /home/bee VOLUME /home/bee/.bee diff --git a/Makefile b/Makefile index 46da4995b14..9aa79e4f9cd 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ REACHABILITY_OVERRIDE_PUBLIC ?= false BATCHFACTOR_OVERRIDE_PUBLIC ?= 5 BEE_API_VERSION ?= "$(shell grep '^ version:' openapi/Swarm.yaml | awk '{print $$2}')" -BEE_DEBUG_API_VERSION ?= "$(shell grep '^ version:' openapi/SwarmDebug.yaml | awk '{print $$2}')" VERSION ?= "$(shell git describe --tags --abbrev=0 | cut -c2-)" COMMIT_HASH ?= "$(shell git describe --long --dirty --always --match "" || true)" @@ -24,7 +23,6 @@ LDFLAGS ?= -s -w \ -X github.com/ethersphere/bee/v2.commitHash="$(COMMIT_HASH)" \ -X github.com/ethersphere/bee/v2.commitTime="$(COMMIT_TIME)" \ -X github.com/ethersphere/bee/v2/pkg/api.Version="$(BEE_API_VERSION)" \ --X github.com/ethersphere/bee/v2/pkg/api.DebugVersion="$(BEE_DEBUG_API_VERSION)" \ -X github.com/ethersphere/bee/v2/pkg/p2p/libp2p.reachabilityOverridePublic="$(REACHABILITY_OVERRIDE_PUBLIC)" \ -X github.com/ethersphere/bee/v2/pkg/postage/listener.batchFactorOverridePublic="$(BATCHFACTOR_OVERRIDE_PUBLIC)" diff --git a/README.md b/README.md index c5fb6fdf04c..c310e4fea89 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![codecov](https://codecov.io/gh/ethersphere/bee/branch/master/graph/badge.svg?token=63RNRLO3RU)](https://codecov.io/gh/ethersphere/bee) [![Go Report Card](https://goreportcard.com/badge/github.com/ethersphere/bee)](https://goreportcard.com/report/github.com/ethersphere/bee) [![API OpenAPI Specs](https://img.shields.io/badge/openapi-api-blue)](https://docs.ethswarm.org/api/) -[![Debug API OpenAPI Specs](https://img.shields.io/badge/openapi-debugapi-lightblue)](https://docs.ethswarm.org/debug-api/) ![Docker Pulls](https://img.shields.io/docker/pulls/ethersphere/bee) ![GitHub all releases](https://img.shields.io/github/downloads/ethersphere/bee/total) ![GitHub](https://img.shields.io/github/license/ethersphere/bee) @@ -27,7 +26,7 @@ There are two versioning schemes used in Bee that you should be aware of. The ma strict Semantic Versioning. Bee hosts different peer-to-peer wire protocol implementations and individual protocol breaking changes would necessitate a bump in the major part of the version. Breaking changes are expected with bumps of the minor version component. New (backward-compatible) features and bug fixes are expected with a bump of the patch component. Major version bumps are reserved for significant changes in Swarm's incentive structure. -The second set of versions that are important are the Bee's API versions (denoted in our [Bee](https://github.com/ethersphere/bee/blob/master/openapi/Swarm.yaml) and [Bee Debug](https://github.com/ethersphere/bee/blob/master/openapi/SwarmDebug.yaml) OpenAPI specifications). These versions **do follow** +The second is the Bee's API version (denoted in our [Bee](https://github.com/ethersphere/bee/blob/master/openapi/Swarm.yaml) OpenAPI specifications). This version **follows** Semantic Versioning and hence you should follow these for breaking changes. ## Contributing diff --git a/cmd/bee/cmd/bcrypt.go b/cmd/bee/cmd/bcrypt.go deleted file mode 100644 index 94c48a86d37..00000000000 --- a/cmd/bee/cmd/bcrypt.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2022 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/cobra" - "golang.org/x/crypto/bcrypt" -) - -func (c *command) initHasherCmd() (err error) { - cmd := &cobra.Command{ - Use: "bcrypt", - Short: "Generate or validate a bcrypt hash", - Long: `Generate or validate a bcrypt hash - -Takes a single plain text argument in order to generate a bcrypt hash. -If '--check' flag is provided it will validate the first (plain text) argument against -the second one, which is expected to be a quoted bcrypt hash.`, - Example: ` -$> bee bcrypt super$ecret -$2a$10$eZP5YuhJq2k8DFmj9UJGWOIjDtXu6NcAQMrz7Zj1bgIVBcHA3bU5u - -$> bee bcrypt --check super$ecret '$2a$10$eZP5YuhJq2k8DFmj9UJGWOIjDtXu6NcAQMrz7Zj1bgIVBcHA3bU5u' -OK: password hash matches provided plain text`, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 || len(args) > 2 { - return cmd.Help() - } - - isCheck := c.config.GetBool("check") - - if isCheck { - if len(args) != 2 { - fmt.Println("Usage:", "bee bcrypt", "--check", "your-plain-text-password", "'password-hash'") - return nil - } - - err := bcrypt.CompareHashAndPassword([]byte(args[1]), []byte(args[0])) - if err != nil { - return errors.New("password hash does not match provided plain text") - } - - fmt.Println("OK: password hash matches provided plain text") - return nil - } - - if len(args) != 1 { - return cmd.Help() - } - - hashed, err := bcrypt.GenerateFromPassword([]byte(args[0]), bcrypt.DefaultCost) - if err != nil { - return errors.New("failed to generate password hash") - } - - fmt.Print(string(hashed)) - return nil - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - return c.config.BindPFlags(cmd.Flags()) - }, - } - - cmd.Flags().Bool("check", false, "validate existing hash") - - c.root.AddCommand(cmd) - return nil -} diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index e02e3ff6f3f..f6028e878ec 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -34,8 +34,6 @@ const ( optionNameP2PAddr = "p2p-addr" optionNameNATAddr = "nat-addr" optionNameP2PWSEnable = "p2p-ws-enable" - optionNameDebugAPIEnable = "debug-api-enable" - optionNameDebugAPIAddr = "debug-api-addr" optionNameBootnodes = "bootnode" optionNameNetworkID = "network-id" optionWelcomeMessage = "welcome-message" @@ -78,9 +76,6 @@ const ( optionNameStaticNodes = "static-nodes" optionNameAllowPrivateCIDRs = "allow-private-cidrs" optionNameSleepAfter = "sleep-after" - optionNameRestrictedAPI = "restricted" - optionNameTokenEncryptionKey = "token-encryption-key" - optionNameAdminPasswordHash = "admin-password" optionNameUsePostageSnapshot = "use-postage-snapshot" optionNameStorageIncentivesEnable = "storage-incentives-enable" optionNameStateStoreCacheCapacity = "statestore-cache-capacity" @@ -144,10 +139,6 @@ func newCommand(opts ...option) (c *command, err error) { return nil, err } - if err := c.initHasherCmd(); err != nil { - return nil, err - } - if err := c.initInitCmd(); err != nil { return nil, err } @@ -256,8 +247,6 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().String(optionNameNATAddr, "", "NAT exposed address") cmd.Flags().Bool(optionNameP2PWSEnable, false, "enable P2P WebSocket transport") cmd.Flags().StringSlice(optionNameBootnodes, []string{""}, "initial nodes to connect to") - cmd.Flags().Bool(optionNameDebugAPIEnable, false, "enable debug HTTP API") - cmd.Flags().String(optionNameDebugAPIAddr, ":1635", "debug HTTP API listen address") cmd.Flags().Uint64(optionNameNetworkID, chaincfg.Mainnet.NetworkID, "ID of the Swarm network") cmd.Flags().StringSlice(optionCORSAllowedOrigins, []string{}, "origins with CORS headers enabled") cmd.Flags().Bool(optionNameTracingEnabled, false, "enable tracing") @@ -297,9 +286,6 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Bool(optionNamePProfMutex, false, "enable pprof mutex profile") cmd.Flags().StringSlice(optionNameStaticNodes, []string{}, "protect nodes from getting kicked out on bootnode") cmd.Flags().Bool(optionNameAllowPrivateCIDRs, false, "allow to advertise private CIDRs to the public network") - cmd.Flags().Bool(optionNameRestrictedAPI, false, "enable permission check on the http APIs") - cmd.Flags().String(optionNameTokenEncryptionKey, "", "admin username to get the security token") - cmd.Flags().String(optionNameAdminPasswordHash, "", "bcrypt hash of the admin password to get the security token") cmd.Flags().Bool(optionNameUsePostageSnapshot, false, "bootstrap node using postage snapshot from the network") cmd.Flags().Bool(optionNameStorageIncentivesEnable, true, "enable storage incentives feature") cmd.Flags().Uint64(optionNameStateStoreCacheCapacity, 100_000, "lru memory caching capacity in number of statestore entries") diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index e659d125116..d4a39c99bd0 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/ethersphere/bee/v2" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" chaincfg "github.com/ethersphere/bee/v2/pkg/config" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/crypto/clef" @@ -73,7 +74,6 @@ func (c *command) initStartCmd() (err error) { fmt.Print(beeWelcomeMessage) fmt.Printf("\n\nversion: %v - planned to be supported until %v, please follow https://ethswarm.org/\n\n", bee.Version, endSupportDate()) - fmt.Printf("DEPRECATION NOTICE:\nThe Debug API is deprecated and will be removed in the next release, version [2.2.0].\nPlease update your integrations to use the main Bee API to avoid service disruptions.\n\n") logger.Info("bee version", "version", bee.Version) go startTimeBomb(logger) @@ -214,11 +214,6 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo } } - debugAPIAddr := c.config.GetString(optionNameDebugAPIAddr) - if !c.config.GetBool(optionNameDebugAPIEnable) { - debugAPIAddr = "" - } - signerConfig, err := c.configureSigner(cmd, logger) if err != nil { return nil, err @@ -293,7 +288,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo neighborhoodSuggester = c.config.GetString(optionNameNeighborhoodSuggester) } - b, err := node.NewBee(ctx, c.config.GetString(optionNameP2PAddr), signerConfig.publicKey, signerConfig.signer, networkID, logger, signerConfig.libp2pPrivateKey, signerConfig.pssPrivateKey, &node.Options{ + b, err := node.NewBee(ctx, c.config.GetString(optionNameP2PAddr), signerConfig.publicKey, signerConfig.signer, networkID, logger, signerConfig.libp2pPrivateKey, signerConfig.pssPrivateKey, signerConfig.session, &node.Options{ DataDir: c.config.GetString(optionNameDataDir), CacheCapacity: c.config.GetUint64(optionNameCacheCapacity), DBOpenFilesLimit: c.config.GetUint64(optionNameDBOpenFilesLimit), @@ -301,7 +296,6 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo DBWriteBufferSize: c.config.GetUint64(optionNameDBWriteBufferSize), DBDisableSeeksCompaction: c.config.GetBool(optionNameDBDisableSeeksCompaction), APIAddr: c.config.GetString(optionNameAPIAddr), - DebugAPIAddr: debugAPIAddr, Addr: c.config.GetString(optionNameP2PAddr), NATAddr: c.config.GetString(optionNameNATAddr), EnableWS: c.config.GetBool(optionNameP2PWSEnable), @@ -338,9 +332,6 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo MutexProfile: c.config.GetBool(optionNamePProfMutex), StaticNodes: staticNodes, AllowPrivateCIDRs: c.config.GetBool(optionNameAllowPrivateCIDRs), - Restricted: c.config.GetBool(optionNameRestrictedAPI), - TokenEncryptionKey: c.config.GetString(optionNameTokenEncryptionKey), - AdminPasswordHash: c.config.GetString(optionNameAdminPasswordHash), UsePostageSnapshot: c.config.GetBool(optionNameUsePostageSnapshot), EnableStorageIncentives: c.config.GetBool(optionNameStorageIncentivesEnable), StatestoreCacheCapacity: c.config.GetUint64(optionNameStateStoreCacheCapacity), @@ -373,6 +364,7 @@ type signerConfig struct { publicKey *ecdsa.PublicKey libp2pPrivateKey *ecdsa.PrivateKey pssPrivateKey *ecdsa.PrivateKey + session accesscontrol.Session } func waitForClef(logger log.Logger, maxRetries uint64, endpoint string) (externalSigner *external.ExternalSigner, err error) { @@ -403,6 +395,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config var signer crypto.Signer var password string var publicKey *ecdsa.PublicKey + var session accesscontrol.Session if p := c.config.GetString(optionNamePassword); p != "" { password = p } else if pf := c.config.GetString(optionNamePasswordFile); pf != "" { @@ -475,6 +468,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config } signer = crypto.NewDefaultSigner(swarmPrivateKey) publicKey = &swarmPrivateKey.PublicKey + session = accesscontrol.NewDefaultSession(swarmPrivateKey) } logger.Info("swarm public key", "public_key", hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(publicKey))) @@ -513,6 +507,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config publicKey: publicKey, libp2pPrivateKey: libp2pPrivateKey, pssPrivateKey: pssPrivateKey, + session: session, }, nil } diff --git a/cmd/bee/cmd/start_dev.go b/cmd/bee/cmd/start_dev.go index a372094bf20..c53158daeaa 100644 --- a/cmd/bee/cmd/start_dev.go +++ b/cmd/bee/cmd/start_dev.go @@ -57,15 +57,9 @@ func (c *command) initStartDevCmd() (err error) { fmt.Println("Starting in development mode") fmt.Println() - debugAPIAddr := c.config.GetString(optionNameDebugAPIAddr) - if !c.config.GetBool(optionNameDebugAPIEnable) { - debugAPIAddr = "" - } - // generate signer in here b, err := node.NewDevBee(logger, &node.DevOptions{ APIAddr: c.config.GetString(optionNameAPIAddr), - DebugAPIAddr: debugAPIAddr, Logger: logger, DBOpenFilesLimit: c.config.GetUint64(optionNameDBOpenFilesLimit), DBBlockCacheCapacity: c.config.GetUint64(optionNameDBBlockCacheCapacity), @@ -73,9 +67,6 @@ func (c *command) initStartDevCmd() (err error) { DBDisableSeeksCompaction: c.config.GetBool(optionNameDBDisableSeeksCompaction), CORSAllowedOrigins: c.config.GetStringSlice(optionCORSAllowedOrigins), ReserveCapacity: c.config.GetUint64(optionNameDevReserveCapacity), - Restricted: c.config.GetBool(optionNameRestrictedAPI), - TokenEncryptionKey: c.config.GetString(optionNameTokenEncryptionKey), - AdminPasswordHash: c.config.GetString(optionNameAdminPasswordHash), }) if err != nil { return err @@ -140,9 +131,7 @@ func (c *command) initStartDevCmd() (err error) { }, } - cmd.Flags().Bool(optionNameDebugAPIEnable, true, "enable debug HTTP API") cmd.Flags().String(optionNameAPIAddr, ":1633", "HTTP API listen address") - cmd.Flags().String(optionNameDebugAPIAddr, ":1635", "debug HTTP API listen address") cmd.Flags().String(optionNameVerbosity, "info", "log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace") cmd.Flags().Uint64(optionNameDevReserveCapacity, 4194304, "cache reserve capacity") cmd.Flags().StringSlice(optionCORSAllowedOrigins, []string{}, "origins with CORS headers enabled") @@ -150,9 +139,6 @@ func (c *command) initStartDevCmd() (err error) { cmd.Flags().Uint64(optionNameDBBlockCacheCapacity, 32*1024*1024, "size of block cache of the database in bytes") cmd.Flags().Uint64(optionNameDBWriteBufferSize, 32*1024*1024, "size of the database write buffer in bytes") cmd.Flags().Bool(optionNameDBDisableSeeksCompaction, false, "disables db compactions triggered by seeks") - cmd.Flags().Bool(optionNameRestrictedAPI, false, "enable permission check on the http APIs") - cmd.Flags().String(optionNameTokenEncryptionKey, "", "security token encryption hash") - cmd.Flags().String(optionNameAdminPasswordHash, "$2a$10$Maw2HUQjcUINtqdnasOs1ee5MtQl7jxnkv2GqCGfbytAiCElzcbYC", "bcrypt hash of the admin password to get the security token") c.root.AddCommand(cmd) return nil diff --git a/go.mod b/go.mod index b3d4a7b6623..da46b1d2016 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,17 @@ module github.com/ethersphere/bee/v2 -go 1.21 +go 1.22 + +toolchain go1.22.0 require ( contrib.go.opencensus.io/exporter/prometheus v0.4.2 github.com/armon/go-radix v1.0.0 github.com/btcsuite/btcd/btcec/v2 v2.3.2 - github.com/casbin/casbin/v2 v2.35.0 github.com/coreos/go-semver v0.3.0 - github.com/ethereum/go-ethereum v1.13.4 + github.com/ethereum/go-ethereum v1.14.3 github.com/ethersphere/go-price-oracle-abi v0.2.0 - github.com/ethersphere/go-storage-incentives-abi v0.8.6-rc5 + github.com/ethersphere/go-storage-incentives-abi v0.8.6 github.com/ethersphere/go-sw3-abi v0.6.5 github.com/ethersphere/langos v1.0.0 github.com/go-playground/validator/v10 v10.11.1 @@ -43,12 +44,12 @@ require ( gitlab.com/nolash/go-mockbytes v0.0.7 go.uber.org/atomic v1.11.0 go.uber.org/goleak v1.3.0 - golang.org/x/crypto v0.19.0 + golang.org/x/crypto v0.23.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a - golang.org/x/net v0.21.0 - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.17.0 - golang.org/x/term v0.17.0 + golang.org/x/net v0.25.0 + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.20.0 + golang.org/x/term v0.20.0 golang.org/x/time v0.5.0 gopkg.in/yaml.v2 v2.4.0 resenje.org/multex v0.1.0 @@ -58,12 +59,11 @@ require ( require ( github.com/BurntSushi/toml v1.1.0 // indirect - github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.7.0 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/codahale/hdrhistogram v0.0.0-00010101000000-000000000000 // indirect @@ -71,34 +71,32 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect + github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect - github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-ole/go-ole v1.2.5 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/holiman/uint256 v1.2.3 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect @@ -166,10 +164,10 @@ require ( go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/mod v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.18.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.20.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.57.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/go.sum b/go.sum index f5e9509bbea..956c674a405 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,6 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1 h1:nEjGZtKHMK92888VT6XkzKwyiW14v5FFRGeWq2uV7N0= github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -116,8 +114,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= -github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= @@ -140,8 +138,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/casbin/casbin/v2 v2.35.0 h1:f0prVg9LgTJTihjAxWEZhfJptXvah1GpZh12sb5KXNA= -github.com/casbin/casbin/v2 v2.35.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= @@ -160,16 +156,14 @@ github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= -github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= -github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= -github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= -github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= -github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= +github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= @@ -195,8 +189,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= -github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -233,22 +229,23 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= -github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.10.4/go.mod h1:nEE0TP5MtxGzOMd7egIrbPJMQBnhVU3ELNxhBglIzhg= -github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= -github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= +github.com/ethereum/go-ethereum v1.14.3 h1:5zvnAqLtnCZrU9uod1JCvHWJbPMURzYFHfc2eHz4PHA= +github.com/ethereum/go-ethereum v1.14.3/go.mod h1:1STrq471D0BQbCX9He0hUj4bHxX2k6mt5nOQJhDNOJ8= github.com/ethersphere/go-price-oracle-abi v0.2.0 h1:wtIcYLgNZHY4BjYwJCnu93SvJdVAZVvBaKinspyyHvQ= github.com/ethersphere/go-price-oracle-abi v0.2.0/go.mod h1:sI/Qj4/zJ23/b1enzwMMv0/hLTpPNVNacEwCWjo6yBk= -github.com/ethersphere/go-storage-incentives-abi v0.8.6-rc5 h1:lW7p+KwAkCZbhoZIxYHfAJSqXHL02FXs1Su5WtevVrI= -github.com/ethersphere/go-storage-incentives-abi v0.8.6-rc5/go.mod h1:SXvJVtM4sEsaSKD0jc1ClpDLw8ErPoROZDme4Wrc/Nc= +github.com/ethersphere/go-storage-incentives-abi v0.8.6 h1:M9WwEtWoxVHKehBAoPMzQhXlzlBetuXsrGgoFvM5I8Y= +github.com/ethersphere/go-storage-incentives-abi v0.8.6/go.mod h1:SXvJVtM4sEsaSKD0jc1ClpDLw8ErPoROZDme4Wrc/Nc= github.com/ethersphere/go-sw3-abi v0.6.5 h1:M5dcIe1zQYvGpY2K07UNkNU9Obc4U+A1fz68Ho/Q+XE= github.com/ethersphere/go-sw3-abi v0.6.5/go.mod h1:BmpsvJ8idQZdYEtWnvxA8POYQ8Rl/NhyCdF0zLMOOJU= github.com/ethersphere/langos v1.0.0 h1:NBtNKzXTTRSue95uOlzPN4py7Aofs0xWPzyj4AI1Vcc= github.com/ethersphere/langos v1.0.0/go.mod h1:dlcN2j4O8sQ+BlCaxeBu43bgr4RQ+inJ+pHwLeZg5Tw= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= @@ -262,6 +259,10 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -284,8 +285,9 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -297,8 +299,6 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4 github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -332,8 +332,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -350,8 +348,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -379,6 +377,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -448,13 +448,13 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= @@ -1003,8 +1003,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1043,8 +1043,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1097,8 +1097,8 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1121,8 +1121,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1198,17 +1198,18 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1217,8 +1218,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1278,10 +1279,9 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1384,8 +1384,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/openapi/Swarm.yaml b/openapi/Swarm.yaml index 7bcd224a7d0..d8dcb65c5e0 100644 --- a/openapi/Swarm.yaml +++ b/openapi/Swarm.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - version: 6.0.0 + version: 7.0.0 title: Bee API description: "A list of the currently provided Interfaces to interact with the swarm, implementing file operations and sending messages" @@ -31,63 +31,134 @@ servers: description: Service port provided in bee node config paths: - "/auth": + "/grantee": post: - summary: "Authenticate - This endpoint is experimental" + summary: "Create grantee list" tags: - - Auth - security: - - basicAuth: [ ] + - ACT + parameters: + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + name: swarm-postage-batch-id + required: true + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmTagParameter" + name: swarm-tag + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" + name: swarm-pin + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmDeferredUpload" + name: swarm-deferred-upload + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" + name: swarm-act-history-address + required: false requestBody: required: true content: application/json: schema: - $ref: "SwarmCommon.yaml#/components/schemas/SecurityTokenRequest" + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesCreateRequest" responses: "201": description: Ok content: application/json: schema: - $ref: "SwarmCommon.yaml#/components/schemas/SecurityTokenResponse" + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesOperationResponse" "400": $ref: "SwarmCommon.yaml#/components/responses/400" - "401": - $ref: "SwarmCommon.yaml#/components/responses/401" "500": $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - "/refresh": - post: - summary: "Refresh the auth token - This endpoint is experimental" + "/grantee/{reference}": + get: + summary: "Get grantee list" tags: - - Auth - security: - - bearerAuth: [ ] + - ACT + parameters: + - in: path + name: reference + schema: + $ref: "SwarmCommon.yaml#/components/schemas/SwarmEncryptedReference" + required: true + description: Grantee list reference + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "SwarmCommon.yaml#/components/schemas/PublicKey" + "404": + $ref: "SwarmCommon.yaml#/components/responses/404" + "500": + $ref: "SwarmCommon.yaml#/components/responses/500" + patch: + summary: "Update grantee list" + description: "Add or remove grantees from an existing grantee list" + tags: + - ACT + parameters: + - in: path + name: reference + schema: + $ref: "SwarmCommon.yaml#/components/schemas/SwarmEncryptedReference" + required: true + description: Grantee list reference + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" + name: swarm-act-history-address + required: true + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + name: swarm-postage-batch-id + required: true + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmTagParameter" + name: swarm-tag + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" + name: swarm-pin + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmDeferredUpload" + name: swarm-deferred-upload + required: false requestBody: required: true content: application/json: schema: - $ref: "SwarmCommon.yaml#/components/schemas/SecurityTokenRequest" + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesPatchRequest" responses: - "201": + "200": description: Ok content: application/json: schema: - $ref: "SwarmCommon.yaml#/components/schemas/SecurityTokenResponse" + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesOperationResponse" "400": $ref: "SwarmCommon.yaml#/components/responses/400" - "401": - $ref: "SwarmCommon.yaml#/components/responses/401" "500": $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response "/bytes": post: @@ -167,6 +238,9 @@ paths: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyStrategyParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyFallbackModeParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmChunkRetrievalTimeoutParameter" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Retrieved content specified by reference @@ -190,6 +264,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists @@ -208,6 +285,8 @@ paths: parameters: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmTagParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" requestBody: description: Chunk binary data that has to have at least 8 bytes. content: @@ -223,6 +302,8 @@ paths: description: Tag UID if it was passed to the request `swarm-tag` header. schema: $ref: "SwarmCommon.yaml#/components/schemas/Uid" + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" content: application/json: schema: @@ -279,6 +360,8 @@ paths: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmDeferredUpload" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyLevelParameter" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" requestBody: content: multipart/form-data: @@ -305,6 +388,8 @@ paths: $ref: "SwarmCommon.yaml#/components/headers/SwarmTag" "etag": $ref: "SwarmCommon.yaml#/components/headers/ETag" + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" content: application/json: schema: @@ -334,6 +419,9 @@ paths: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyStrategyParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyFallbackModeParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmChunkRetrievalTimeoutParameter" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Ok @@ -363,6 +451,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists @@ -749,6 +840,8 @@ paths: $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" name: swarm-postage-batch-id required: true + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" requestBody: required: true description: The SOC binary data is composed of the span (8 bytes) and the at most 4KB payload. @@ -764,6 +857,9 @@ paths: application/json: schema: $ref: "SwarmCommon.yaml#/components/schemas/ReferenceResponse" + headers: + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" "400": $ref: "SwarmCommon.yaml#/components/responses/400" "401": @@ -801,6 +897,8 @@ paths: description: "Feed indexing scheme (default: sequence)" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "201": description: Created @@ -808,6 +906,9 @@ paths: application/json: schema: $ref: "SwarmCommon.yaml#/components/schemas/ReferenceResponse" + headers: + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" "400": $ref: "SwarmCommon.yaml#/components/responses/400" "401": @@ -930,7 +1031,6 @@ paths: "/addresses": get: summary: Get overlay and underlay addresses of the node - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. tags: - Connectivity responses: @@ -982,7 +1082,6 @@ paths: "/balances": get: summary: Get the balances with all known peers including prepaid services - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1002,7 +1101,6 @@ paths: "/balances/{address}": get: summary: Get the balances with a specific peer including prepaid services - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1031,7 +1129,6 @@ paths: "/blocklist": get: summary: Get a list of blocklisted peers - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1051,7 +1148,6 @@ paths: "/consumed": get: summary: Get the past due consumption balances with all known peers - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1071,7 +1167,6 @@ paths: "/consumed/{address}": get: summary: Get the past due consumption balance with a specific peer - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1100,7 +1195,6 @@ paths: "/chequebook/address": get: summary: Get the address of the chequebook contract used - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1116,7 +1210,6 @@ paths: "/chequebook/balance": get: summary: Get the balance of the chequebook - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1150,6 +1243,9 @@ paths: $ref: "SwarmCommon.yaml#/components/parameters/SwarmCache" name: swarm-cache required: false + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Retrieved chunk content @@ -1177,6 +1273,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists @@ -1190,7 +1289,6 @@ paths: "/connect/{multiAddress}": post: summary: Connect to address - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1220,7 +1318,6 @@ paths: "/reservestate": get: summary: Get reserve state - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1238,7 +1335,6 @@ paths: "/chainstate": get: summary: Get chain state - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1256,7 +1352,6 @@ paths: "/node": get: summary: Get information about the node - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. tags: - Status responses: @@ -1272,7 +1367,6 @@ paths: "/peers": get: summary: Get a list of peers - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1290,7 +1384,6 @@ paths: "/peers/{address}": delete: summary: Remove peer - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1319,7 +1412,6 @@ paths: "/pingpong/{address}": post: summary: Try connection to node - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1350,7 +1442,6 @@ paths: "/settlements/{address}": get: summary: Get amount of sent and received from settlements with a peer - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1379,7 +1470,6 @@ paths: "/settlements": get: summary: Get settlements with all known peers and total amount sent or received - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1399,7 +1489,6 @@ paths: "/timesettlements": get: summary: Get time based settlements with all known peers and total amount sent or received - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1419,7 +1508,6 @@ paths: "/topology": get: summary: Get topology of known network - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1435,7 +1523,6 @@ paths: "/welcome-message": get: summary: Get configured P2P welcome message - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1453,7 +1540,6 @@ paths: description: Default response post: summary: Set P2P welcome message - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1480,7 +1566,6 @@ paths: "/chequebook/cashout/{peer-id}": get: summary: Get last cashout action for the peer - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] parameters: @@ -1507,7 +1592,6 @@ paths: description: Default response post: summary: Cashout the last cheque for the peer - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] parameters: @@ -1540,7 +1624,6 @@ paths: "/chequebook/cheque/{peer-id}": get: summary: Get last cheques for the peer - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] parameters: @@ -1569,7 +1652,6 @@ paths: "/chequebook/cheque": get: summary: Get last cheques for all peers - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1591,7 +1673,6 @@ paths: "/chequebook/deposit": post: summary: Deposit tokens from overlay address into chequebook - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] parameters: @@ -1621,7 +1702,6 @@ paths: "/chequebook/withdraw": post: summary: Withdraw tokens from the chequebook to the overlay address - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] parameters: @@ -1651,7 +1731,6 @@ paths: "/transactions": get: summary: Get list of pending transactions - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. tags: - Transaction responses: @@ -1669,7 +1748,6 @@ paths: "/transactions/{txHash}": get: summary: Get information about a sent transaction - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. parameters: - in: path name: txHash @@ -1694,7 +1772,6 @@ paths: description: Default response post: summary: Rebroadcast existing transaction - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. parameters: - in: path name: txHash @@ -1719,7 +1796,6 @@ paths: description: Default response delete: summary: Cancel existing transaction - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. parameters: - in: path name: txHash @@ -1747,7 +1823,6 @@ paths: "/stamps": get: summary: Get stamps for this node - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1775,7 +1850,6 @@ paths: description: Swarm address of the stamp get: summary: Get an individual postage batch status - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1804,7 +1878,6 @@ paths: description: Swarm address of the stamp get: summary: Get extended bucket data of a batch - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: @@ -1830,8 +1903,6 @@ paths: - bearerAuth: [ ] description: | Be aware, this endpoint creates an on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance! - - This endpoint can be restricted if the node is spawned with the `--restricted` flag. tags: - Postage Stamps parameters: @@ -1881,8 +1952,6 @@ paths: summary: Top up an existing postage batch. description: | Be aware, this endpoint creates on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance! - - This endpoint can be restricted if the node is spawned with the `--restricted` flag. tags: - Postage Stamps parameters: @@ -1923,8 +1992,6 @@ paths: summary: Dilute an existing postage batch. description: | Be aware, this endpoint creates on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance! - - This endpoint can be restricted if the node is spawned with the `--restricted` flag. tags: - Postage Stamps parameters: @@ -1961,7 +2028,6 @@ paths: "/batches": get: summary: Get all globally available batches that were purchased by all nodes. - description: This endpoint can be restricted if the node is spawned with the `--restricted` flag. security: - bearerAuth: [ ] tags: diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index 84d690fc64f..8559a7c4e53 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - version: 3.2.7 + version: 3.2.8 title: Common Data Types description: | \*****bzzz***** @@ -87,6 +87,36 @@ components: ghostBalance: $ref: "#/components/schemas/BigInt" + ActGranteesCreateRequest: + type: object + properties: + grantees: + type: array + items: + $ref: "#/components/schemas/PublicKey" + + ActGranteesPatchRequest: + type: object + properties: + add: + type: array + items: + $ref: "#/components/schemas/PublicKey" + description: List of grantees to add + revoke: + type: array + items: + $ref: "#/components/schemas/PublicKey" + description: List of grantees to revoke future access from + + ActGranteesOperationResponse: + type: object + properties: + ref: + $ref: "#/components/schemas/SwarmEncryptedReference" + historyref: + $ref: "#/components/schemas/SwarmEncryptedReference" + Balance: type: object properties: @@ -439,10 +469,6 @@ components: type: string default: "0.0.0" description: The default value is set in case the bee binary was not build correctly. - debugApiVersion: - type: string - default: "0.0.0" - description: The default value is set in case the bee binary was not build correctly. PostageBatch: type: object @@ -853,7 +879,7 @@ components: reserveSize: type: integer reserveSizeWithinRadius: - type: interger + type: integer pullsyncRate: type: number storageRadius: @@ -977,6 +1003,12 @@ components: schema: $ref: "#/components/schemas/HexString" + SwarmActHistoryAddress: + description: "Swarm address reference to the new ACT history entry" + schema: + $ref: "#/components/schemas/SwarmAddress" + required: false + ETag: description: | The RFC7232 ETag header field in a response provides the current entity- @@ -1137,6 +1169,40 @@ components: required: false description: "Determines if the download data should be cached on the node. By default the download will be cached" + SwarmAct: + in: header + name: swarm-act + schema: + type: boolean + default: "false" + required: false + description: "Determines if the uploaded data should be treated as ACT content" + + SwarmActPublisher: + in: header + name: swarm-act-publisher + schema: + $ref: "#/components/schemas/PublicKey" + required: false + description: "ACT content publisher's public key" + + SwarmActHistoryAddress: + in: header + name: swarm-act-history-address + schema: + $ref: "#/components/schemas/SwarmAddress" + required: false + description: "ACT history reference address" + + SwarmActTimestamp: + in: header + name: swarm-act-timestamp + schema: + type: integer + format: int64 + required: false + description: "ACT history Unix timestamp" + responses: "200": description: OK. diff --git a/openapi/SwarmDebug.yaml b/openapi/SwarmDebug.yaml deleted file mode 100644 index e2091171729..00000000000 --- a/openapi/SwarmDebug.yaml +++ /dev/null @@ -1,1170 +0,0 @@ -openapi: 3.0.3 -info: - version: 4.1.1 - title: Bee Debug API - description: "A list of the currently provided debug interfaces to interact with the bee node" - -security: - - {} - -externalDocs: - description: Browse the documentation @ the Swarm Docs - url: "https://docs.ethswarm.org" - -servers: - - url: "http://{apiRoot}:{port}" - variables: - apiRoot: - default: "localhost" - description: Base address of the local bee node debug API - port: - default: "1635" - description: Service port provided in bee node config - -paths: - "/addresses": - get: - summary: Get overlay and underlay addresses of the node - tags: - - Connectivity - responses: - "200": - description: Own node underlay and overlay addresses - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Addresses" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/accounting": - get: - summary: Get all accounting associated values with all known peers - tags: - - Balance - responses: - "200": - description: Own accounting associated values with all known peers - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/PeerAccountingData" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/balances": - get: - summary: Get the balances with all known peers including prepaid services - tags: - - Balance - responses: - "200": - description: Own balances with all known peers - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Balances" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/balances/{address}": - get: - summary: Get the balances with a specific peer including prepaid services - tags: - - Balance - parameters: - - in: path - name: address - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - responses: - "200": - description: Balance with the specific peer - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Balance" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/blocklist": - get: - summary: Get a list of blocklisted peers - tags: - - Connectivity - responses: - "200": - description: Returns overlay addresses of blocklisted peers - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Peers" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/consumed": - get: - summary: Get the past due consumption balances with all known peers - tags: - - Balance - responses: - "200": - description: Own past due consumption balances with all known peers - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Balances" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/consumed/{address}": - get: - summary: Get the past due consumption balance with a specific peer - tags: - - Balance - parameters: - - in: path - name: address - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - responses: - "200": - description: Past-due consumption balance with the specific peer - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Balance" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chequebook/address": - get: - summary: Get the address of the chequebook contract used - tags: - - Chequebook - responses: - "200": - description: Ethereum address of chequebook contract - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/ChequebookAddress" - - "/chequebook/balance": - get: - summary: Get the balance of the chequebook - tags: - - Chequebook - responses: - "200": - description: Balance of the chequebook - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/ChequebookBalance" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chunks/{address}": - get: - summary: Check if chunk at address exists locally - tags: - - Chunk - parameters: - - in: path - name: address - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of chunk - responses: - "200": - description: Chunk exists - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Response" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - default: - description: Default response - - "/connect/{multiAddress}": - post: - summary: Connect to address - tags: - - Connectivity - parameters: - - in: path - allowReserved: true - name: multiAddress - schema: - $ref: "SwarmCommon.yaml#/components/schemas/MultiAddress" - required: true - description: Underlay address of peer - responses: - "200": - description: Returns overlay address of connected peer - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Address" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/reservestate": - get: - summary: Get reserve state - tags: - - Status - responses: - "200": - description: Reserve State - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/ReserveState" - default: - description: Default response - - "/chainstate": - get: - summary: Get chain state - tags: - - Status - responses: - "200": - description: Chain State - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/ChainState" - default: - description: Default response - - "/node": - get: - summary: Get information about the node - tags: - - Status - responses: - "200": - description: Information about the node - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Node" - default: - description: Default response - - "/peers": - get: - summary: Get a list of peers - tags: - - Connectivity - responses: - "200": - description: Returns overlay addresses of connected peers - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Peers" - default: - description: Default response - - "/peers/{address}": - delete: - summary: Remove peer - tags: - - Connectivity - parameters: - - in: path - name: address - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - responses: - "200": - description: Disconnected peer - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Response" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/pingpong/{address}": - post: - summary: Try connection to node - tags: - - Connectivity - parameters: - - in: path - name: address - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - responses: - "200": - description: Returns round trip time for given peer - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/RttMs" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/health": - get: - summary: Get node overall health Status - description: | - Health Status will indicate node healthiness. - - If node is unhealthy please check node logs for errors. - tags: - - Status - responses: - "200": - description: Health Status of node - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/HealthStatus" - default: - description: Default response - - "/readiness": - get: - summary: Readiness endpoint indicates if node is ready to start accepting traffic - tags: - - Status - responses: - "200": - description: Indicates that node is ready - $ref: "SwarmCommon.yaml#/components/responses/200" - "400": - description: Indicates that node is not ready - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response - - "/settlements/{address}": - get: - summary: Get amount of sent and received from settlements with a peer - tags: - - Settlements - parameters: - - in: path - name: address - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - responses: - "200": - description: Amount of sent or received from settlements with a peer - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Settlement" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/settlements": - get: - summary: Get settlements with all known peers and total amount sent or received - tags: - - Settlements - responses: - "200": - description: Settlements with all known peers and total amount sent or received - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Settlements" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/timesettlements": - get: - summary: Get time based settlements with all known peers and total amount sent or received - tags: - - Settlements - responses: - "200": - description: Time based settlements with all known peers and total amount sent or received - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/Settlements" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/topology": - get: - description: Get topology of known network - tags: - - Connectivity - responses: - "200": - description: Swarm topology of the bee node - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BzzTopology" - - "/welcome-message": - get: - summary: Get configured P2P welcome message - tags: - - Connectivity - responses: - "200": - description: Welcome message - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/WelcomeMessage" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - post: - summary: Set P2P welcome message - tags: - - Connectivity - requestBody: - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/WelcomeMessage" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/HealthStatus" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chequebook/cashout/{peer-id}": - get: - summary: Get last cashout action for the peer - parameters: - - in: path - name: peer-id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - tags: - - Chequebook - responses: - "200": - description: Cashout status - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwapCashoutStatus" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - post: - summary: Cashout the last cheque for the peer - parameters: - - in: path - name: peer-id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - - $ref: "SwarmCommon.yaml#/components/parameters/GasPriceParameter" - - $ref: "SwarmCommon.yaml#/components/parameters/GasLimitParameter" - tags: - - Chequebook - responses: - "201": - description: OK - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionResponse" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "429": - $ref: "SwarmCommon.yaml#/components/responses/429" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chequebook/cheque/{peer-id}": - get: - summary: Get last cheques for the peer - parameters: - - in: path - name: peer-id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - required: true - description: Swarm address of peer - tags: - - Chequebook - responses: - "200": - description: Last cheques - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/ChequePeerResponse" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chequebook/cheque": - get: - summary: Get last cheques for all peers - tags: - - Chequebook - responses: - "200": - description: Last cheques - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/ChequeAllPeersResponse" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chequebook/deposit": - post: - summary: Deposit tokens from overlay address into chequebook - parameters: - - in: query - name: amount - schema: - type: integer - required: true - description: amount of tokens to deposit - - $ref: "SwarmCommon.yaml#/components/parameters/GasPriceParameter" - tags: - - Chequebook - responses: - "201": - description: Transaction hash of the deposit transaction - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/chequebook/withdraw": - post: - summary: Withdraw tokens from the chequebook to the overlay address - parameters: - - in: query - name: amount - schema: - type: integer - required: true - description: amount of tokens to withdraw - - $ref: "SwarmCommon.yaml#/components/parameters/GasPriceParameter" - tags: - - Chequebook - responses: - "201": - description: Transaction hash of the withdraw transaction - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/redistributionstate": - get: - summary: Get current status of node in redistribution game - tags: - - RedistributionState - responses: - "200": - description: Redistribution status info - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/RedistributionStatusResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - "/wallet": - get: - summary: Get wallet balance for BZZ and xDai - tags: - - Wallet - responses: - "200": - description: Wallet balance info - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/WalletResponse" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - "/wallet/withdraw/{coin}": - post: - summary: Allows withdrawals of BZZ or xDAI to provided (whitelisted) address - tags: - - Wallet - parameters: - - in: query - name: amount - required: true - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BigInt" - - in: query - name: address - required: true - schema: - $ref: "SwarmCommon.yaml#/components/schemas/EthereumAddress" - - in: path - name: coin - required: true - schema: - $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" - responses: - "200": - content: - application/json: - schema: - $ref: 'SwarmCommon.yaml#/components/schemas/WalletTxResponse' - description: OK - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - description: Amount greater than ballance or coin is other than BZZ/xDAI - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/transactions": - get: - summary: Get list of pending transactions - tags: - - Transaction - responses: - "200": - description: List of pending transactions - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/PendingTransactionsResponse" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/transactions/{txHash}": - get: - summary: Get information about a sent transaction - parameters: - - in: path - name: txHash - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionHash" - required: true - description: Hash of the transaction - tags: - - Transaction - responses: - "200": - description: Get info about transaction - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionInfo" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - post: - summary: Rebroadcast existing transaction - parameters: - - in: path - name: txHash - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionHash" - required: true - description: Hash of the transaction - tags: - - Transaction - responses: - "200": - description: Hash of the transaction - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionResponse" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - delete: - summary: Cancel existing transaction - parameters: - - in: path - name: txHash - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionHash" - required: true - description: Hash of the transaction - - $ref: "SwarmCommon.yaml#/components/parameters/GasPriceParameter" - tags: - - Transaction - responses: - "200": - description: Hash of the transaction - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/TransactionResponse" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/stamps": - get: - summary: Get stamps for this node - tags: - - Postage Stamps - responses: - "200": - description: Returns an array of postage batches. - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/DebugPostageBatchesResponse" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - - default: - description: Default response - - "/stamps/{batch_id}": - parameters: - - in: path - name: batch_id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchID" - required: true - description: Swarm address of the stamp - get: - summary: Get an individual postage batch status - tags: - - Postage Stamps - responses: - "200": - description: Returns an individual postage batch state - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/DebugPostageBatch" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response - - "/stamps/{batch_id}/buckets": - parameters: - - in: path - name: batch_id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchID" - required: true - description: Swarm address of the stamp - get: - summary: Get extended bucket data of a batch - tags: - - Postage Stamps - responses: - "200": - description: Returns extended bucket data of the provided batch ID - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/PostageStampBuckets" - "404": - $ref: "SwarmCommon.yaml#/components/responses/404" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response - - "/stamps/{amount}/{depth}": - post: - summary: Buy a new postage batch. - description: Be aware, this endpoint creates an on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance! - tags: - - Postage Stamps - parameters: - - in: path - name: amount - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BigInt" - required: true - description: Amount of BZZ added that the postage batch will have. - - in: path - name: depth - schema: - type: integer - required: true - description: Batch depth which specifies how many chunks can be signed with the batch. It is a logarithm. Must be higher than default bucket depth (16) - - in: query - name: label - schema: - type: string - required: false - description: An optional label for this batch - - in: header - name: immutable - schema: - type: boolean - required: false - responses: - "201": - description: Returns the newly created postage batch ID - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchIDResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "429": - $ref: "SwarmCommon.yaml#/components/responses/429" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/stamps/topup/{batch_id}/{amount}": - patch: - summary: Top up an existing postage batch. - description: Be aware, this endpoint creates on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance! - tags: - - Postage Stamps - parameters: - - in: path - name: batch_id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchID" - required: true - description: Batch ID to top up - - in: path - name: amount - schema: - type: integer - required: true - description: Amount of BZZ per chunk to top up to an existing postage batch. - responses: - "202": - description: Returns the postage batch ID that was topped up - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchIDResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "429": - $ref: "SwarmCommon.yaml#/components/responses/429" - "402": - $ref: "SwarmCommon.yaml#/components/responses/402" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/stamps/dilute/{batch_id}/{depth}": - patch: - summary: Dilute an existing postage batch. - description: Be aware, this endpoint creates on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance! - tags: - - Postage Stamps - parameters: - - in: path - name: batch_id - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchID" - required: true - description: Batch ID to dilute - - in: path - name: depth - schema: - type: integer - required: true - description: New batch depth. Must be higher than the previous depth. - responses: - "202": - description: Returns the postage batch ID that was diluted. - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/BatchIDResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "429": - $ref: "SwarmCommon.yaml#/components/responses/429" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/batches": - get: - summary: Get all globally available batches that were purchased by all nodes. - tags: - - Postage Stamps - responses: - "200": - description: Returns an array of all available and currently valid postage batches. - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/DebugPostageAllBatchesResponse" - - default: - description: Default response - - "/stake/{amount}": - post: - summary: Deposit some amount for staking. - description: Be aware, this endpoint creates an on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance. - tags: - - Staking - parameters: - - in: path - name: amount - schema: - type: string - description: Amount of BZZ added that will be deposited for staking. - - $ref: "SwarmCommon.yaml#/components/parameters/GasPriceParameter" - - $ref: "SwarmCommon.yaml#/components/parameters/GasLimitParameter" - responses: - "200": - $ref: "SwarmCommon.yaml#/components/schemas/StakeDepositResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/stake": - get: - summary: Get the staked amount. - description: This endpoint fetches the staked amount from the blockchain. - tags: - - Staking - responses: - "200": - $ref: "SwarmCommon.yaml#/components/schemas/GetStakeResponse" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - delete: - summary: Withdraw all staked amount. - description: Be aware, this endpoint creates an on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance. - tags: - - Staking - parameters: - - $ref: "SwarmCommon.yaml#/components/parameters/GasPriceParameter" - - $ref: "SwarmCommon.yaml#/components/parameters/GasLimitParameter" - responses: - "200": - $ref: "SwarmCommon.yaml#/components/schemas/WithdrawAllStakeResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - "500": - $ref: "SwarmCommon.yaml#/components/responses/500" - default: - description: Default response - - "/loggers": - get: - summary: Get all available loggers. - tags: - - Logging - responses: - "200": - description: Returns an array of all available loggers, also represented in short form in a tree. - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/LoggerResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response - - "/loggers/{exp}": - get: - summary: Get all available loggers that match the specified expression. - parameters: - - in: path - name: exp - schema: - $ref: "SwarmCommon.yaml#/components/schemas/LoggerExp" - required: true - description: Regular expression or a subsystem that matches the logger(s). - tags: - - Logging - responses: - "200": - description: Returns an array of all available loggers that matches given expression, also represented in short form in a tree. - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/LoggerResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response - put: - summary: Set logger(s) verbosity level. - parameters: - - in: path - name: exp - schema: - $ref: "SwarmCommon.yaml#/components/schemas/LoggerExp" - required: true - description: Regular expression or a subsystem that matches the logger(s). - tags: - - Logging - responses: - "200": - description: The verbosity was changed successfully. - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response - - "/status": - get: - summary: Get the current status snapshot of this node. - tags: - - Node Status - responses: - "200": - description: Returns the current node status snapshot. - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/StatusSnapshotResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response. - - "/status/peers": - get: - summary: Get the current status snapshot of this node connected peers. - tags: - - Node Status - responses: - "200": - description: Returns the status snapshot of this node connected peers - content: - application/json: - schema: - $ref: "SwarmCommon.yaml#/components/schemas/StatusResponse" - "400": - $ref: "SwarmCommon.yaml#/components/responses/400" - default: - description: Default response. diff --git a/packaging/bee.yaml b/packaging/bee.yaml index 560352926e1..6db6944b84c 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -24,10 +24,6 @@ data-dir: /var/lib/bee # db-write-buffer-size: 33554432 ## disables db compactions triggered by seeks # db-disable-seeks-compaction: false -## debug HTTP API listen address (default ":1635") -debug-api-addr: 127.0.0.1:1635 -## enable debug HTTP API -debug-api-enable: true ## cause the node to start in full mode # full-node: false ## NAT exposed address diff --git a/packaging/docker/README.md b/packaging/docker/README.md index cd95f1d131f..bce886018ae 100644 --- a/packaging/docker/README.md +++ b/packaging/docker/README.md @@ -43,7 +43,7 @@ docker-compose pull && docker-compose up -d ## Running multiple Bee nodes It is easy to run multiple bee nodes with docker compose by adding more services to `docker-compose.yaml` To do so, open `docker-compose.yaml`, copy lines 4-54 and past this after line 54 (whole bee-1 section). -In the copied lines, replace all occurrences of `bee-1` with `bee-2` and adjust the `API_ADDR` and `P2P_ADDR` and `DEBUG_API_ADDR` to respectively `1733`, `1734` and `127.0.0.1:1735` +In the copied lines, replace all occurrences of `bee-1` with `bee-2` and adjust the `API_ADDR` and `P2P_ADDR` to respectively `1733`, `1734.` Lastly, add your newly configured services under `volumes` (last lines), such that it looks like: ```yaml volumes: diff --git a/packaging/docker/docker-compose.yml b/packaging/docker/docker-compose.yml index f0b21be7701..56b6581d574 100644 --- a/packaging/docker/docker-compose.yml +++ b/packaging/docker/docker-compose.yml @@ -17,8 +17,6 @@ services: - BEE_DB_BLOCK_CACHE_CAPACITY - BEE_DB_WRITE_BUFFER_SIZE - BEE_DB_DISABLE_SEEKS_COMPACTION - - BEE_DEBUG_API_ADDR - - BEE_DEBUG_API_ENABLE - BEE_FULL_NODE - BEE_NAT_ADDR - BEE_NETWORK_ID @@ -48,7 +46,6 @@ services: ports: - "${API_ADDR:-1633}${BEE_API_ADDR:-:1633}" - "${P2P_ADDR:-1634}${BEE_P2P_ADDR:-:1634}" - - "${DEBUG_API_ADDR:-127.0.0.1:1635}${BEE_DEBUG_API_ADDR:-:1635}" volumes: - bee-1:/home/bee/.bee command: start diff --git a/packaging/docker/env b/packaging/docker/env index a9da56ae85e..f578f1ea9c9 100644 --- a/packaging/docker/env +++ b/packaging/docker/env @@ -26,10 +26,6 @@ # BEE_DB_WRITE_BUFFER_SIZE=33554432 ## disables db compactions triggered by seeks # BEE_DB_DISABLE_SEEKS_COMPACTION=false -## debug HTTP API listen address (default :1635) -# BEE_DEBUG_API_ADDR=:1635 -## enable debug HTTP API -# BEE_DEBUG_API_ENABLE=false ## enable global pinning ## cause the node to start in full mode # BEE_FULL_NODE=false diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index 235ad42e781..79470f1b7a4 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -24,10 +24,6 @@ data-dir: /usr/local/var/lib/swarm-bee # db-write-buffer-size: 33554432 ## disables db compactions triggered by seeks # db-disable-seeks-compaction: false -## debug HTTP API listen address (default ":1635") -debug-api-addr: 127.0.0.1:1635 -## enable debug HTTP API -debug-api-enable: true ## cause the node to start in full mode # full-node: false ## NAT exposed address diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index 212fadca581..5b52b272d31 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -24,10 +24,6 @@ data-dir: /opt/homebrew/var/lib/swarm-bee # db-write-buffer-size: 33554432 ## disables db compactions triggered by seeks # db-disable-seeks-compaction: false -## debug HTTP API listen address (default ":1635") -debug-api-addr: 127.0.0.1:1635 -## enable debug HTTP API -debug-api-enable: true ## cause the node to start in full mode # full-node: false ## NAT exposed address diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index 18c01229b2c..6efdc90bb20 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -14,10 +14,6 @@ config: ./bee.yaml data-dir: ./data ## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes # cache-capacity: 1000000 -## debug HTTP API listen address (default ":1635") -# debug-api-addr: 127.0.0.1:1635 -## enable debug HTTP API -# debug-api-enable: false ## cause the node to start in full mode # full-node: false ## NAT exposed address diff --git a/pkg/accesscontrol/access.go b/pkg/accesscontrol/access.go new file mode 100644 index 00000000000..0b7f9a094ac --- /dev/null +++ b/pkg/accesscontrol/access.go @@ -0,0 +1,160 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" + "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/swarm" + "golang.org/x/crypto/sha3" +) + +//nolint:gochecknoglobals +var ( + hashFunc = sha3.NewLegacyKeccak256 + oneByteArray = []byte{1} + zeroByteArray = []byte{0} +) + +// Decryptor is a read-only interface for the ACT. +type Decryptor interface { + // DecryptRef will return a decrypted reference, for given encrypted reference and grantee. + DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + Session +} + +// Control interface for the ACT (does write operations). +type Control interface { + Decryptor + // AddGrantee adds a new grantee to the ACT. + AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey) error + // EncryptRef encrypts a Swarm reference for a given grantee. + EncryptRef(ctx context.Context, storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) +} + +// ActLogic represents the access control logic. +type ActLogic struct { + Session +} + +var _ Control = (*ActLogic)(nil) + +// EncryptRef encrypts a Swarm reference for a publisher. +func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + accessKey, err := al.getAccessKey(ctx, storage, publisherPubKey) + if err != nil { + return swarm.ZeroAddress, err + } + refCipher := encryption.New(accessKey, 0, 0, hashFunc) + encryptedRef, err := refCipher.Encrypt(ref.Bytes()) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("failed to encrypt reference: %w", err) + } + + return swarm.NewAddress(encryptedRef), nil +} + +// AddGrantee adds a new grantee to the ACT. +func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey) error { + var ( + accessKey encryption.Key + err error + ) + + // Create new access key because grantee is the publisher. + if publisherPubKey.Equal(granteePubKey) { + accessKey = encryption.GenerateRandomKey(encryption.KeyLength) + } else { + // Get previously generated access key. + accessKey, err = al.getAccessKey(ctx, storage, publisherPubKey) + if err != nil { + return err + } + } + + lookupKey, accessKeyDecryptionKey, err := al.getKeys(granteePubKey) + if err != nil { + return err + } + + // Encrypt the access key for the new Grantee. + cipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, 0, hashFunc) + granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) + if err != nil { + return fmt.Errorf("failed to encrypt access key: %w", err) + } + + // Add the new encrypted access key to the Act. + err = storage.Put(ctx, lookupKey, granteeEncryptedAccessKey) + if err != nil { + return fmt.Errorf("failed to put value to KVS: %w", err) + } + + return nil +} + +// Will return the access key for a publisher (public key). +func (al *ActLogic) getAccessKey(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { + publisherLookupKey, publisherAKDecryptionKey, err := al.getKeys(publisherPubKey) + if err != nil { + return nil, err + } + // no need for constructor call if value not found in act. + accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, 0, hashFunc) + encryptedAK, err := storage.Get(ctx, publisherLookupKey) + if err != nil { + switch { + case errors.Is(err, kvs.ErrNotFound): + return nil, ErrNotFound + default: + return nil, fmt.Errorf("failed go get value from KVS: %w", err) + } + } + + accessKey, err := accessKeyDecryptionCipher.Decrypt(encryptedAK) + if err != nil { + return nil, fmt.Errorf("failed to decrypt access key: %w", err) + } + + return accessKey, nil +} + +// Generate lookup key and access key decryption key for a given public key. +func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([]byte, []byte, error) { + nonces := [][]byte{zeroByteArray, oneByteArray} + keys, err := al.Session.Key(publicKey, nonces) + if len(keys) != len(nonces) { + return nil, nil, err + } + return keys[0], keys[1], err +} + +// DecryptRef will return a decrypted reference, for given encrypted reference and publisher. +func (al ActLogic) DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { + accessKey, err := al.getAccessKey(ctx, storage, publisher) + if err != nil { + return swarm.ZeroAddress, err + } + + refCipher := encryption.New(accessKey, 0, 0, hashFunc) + ref, err := refCipher.Decrypt(encryptedRef.Bytes()) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("failed to decrypt reference: %w", err) + } + + return swarm.NewAddress(ref), nil +} + +// NewLogic creates a new ACT Logic from a session. +func NewLogic(s Session) ActLogic { + return ActLogic{ + Session: s, + } +} diff --git a/pkg/accesscontrol/access_test.go b/pkg/accesscontrol/access_test.go new file mode 100644 index 00000000000..0b2eeaa1e56 --- /dev/null +++ b/pkg/accesscontrol/access_test.go @@ -0,0 +1,214 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol_test + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/hex" + "fmt" + "testing" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + kvsmock "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" +) + +func assertNoError(t *testing.T, msg string, err error) { + t.Helper() + if err != nil { + assert.FailNowf(t, err.Error(), msg) + } +} + +func assertError(t *testing.T, msg string, err error) { + t.Helper() + if err == nil { + assert.FailNowf(t, fmt.Sprintf("Expected %s error, got nil", msg), "") + } +} + +// Generates a new test environment with a fix private key. +func setupAccessLogic() accesscontrol.ActLogic { + privateKey := getPrivKey(1) + diffieHellman := accesscontrol.NewDefaultSession(privateKey) + al := accesscontrol.NewLogic(diffieHellman) + + return al +} + +func getPrivKey(keyNumber int) *ecdsa.PrivateKey { + var keyHex string + + switch keyNumber { + case 0: + keyHex = "a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa" + case 1: + keyHex = "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb" + case 2: + keyHex = "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfc" + default: + panic("Invalid key number") + } + + data, err := hex.DecodeString(keyHex) + if err != nil { + panic(err) + } + + privKey, err := crypto.DecodeSecp256k1PrivateKey(data) + if err != nil { + panic(err) + } + + return privKey +} + +func TestDecryptRef_Publisher(t *testing.T) { + t.Parallel() + ctx := context.Background() + id1 := getPrivKey(1) + s := kvsmock.New() + al := setupAccessLogic() + err := al.AddGrantee(ctx, s, &id1.PublicKey, &id1.PublicKey) + assertNoError(t, "AddGrantee", err) + + byteRef, err := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") + assertNoError(t, "DecodeString", err) + expectedRef := swarm.NewAddress(byteRef) + encryptedRef, err := al.EncryptRef(ctx, s, &id1.PublicKey, expectedRef) + assertNoError(t, "al encryptref", err) + + t.Run("decrypt success", func(t *testing.T) { + actualRef, err := al.DecryptRef(ctx, s, encryptedRef, &id1.PublicKey) + assertNoError(t, "decrypt ref", err) + + if !expectedRef.Equal(actualRef) { + assert.FailNowf(t, fmt.Sprintf("DecryptRef gave back wrong Swarm reference! Expedted: %v, actual: %v", expectedRef, actualRef), "") + } + }) + t.Run("decrypt with nil publisher", func(t *testing.T) { + _, err = al.DecryptRef(ctx, s, encryptedRef, nil) + assertError(t, "al decryptref", err) + }) +} + +func TestDecryptRefWithGrantee_Success(t *testing.T) { + t.Parallel() + ctx := context.Background() + id0, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assertNoError(t, "GenerateKey", err) + diffieHellman := accesscontrol.NewDefaultSession(id0) + al := accesscontrol.NewLogic(diffieHellman) + + s := kvsmock.New() + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) + assertNoError(t, "AddGrantee publisher", err) + + id1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assertNoError(t, "GenerateKey", err) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey) + assertNoError(t, "AddGrantee id1", err) + + byteRef, err := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") + assertNoError(t, "DecodeString", err) + + expectedRef := swarm.NewAddress(byteRef) + + encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) + assertNoError(t, "al encryptref", err) + + diffieHellman2 := accesscontrol.NewDefaultSession(id1) + granteeAccessLogic := accesscontrol.NewLogic(diffieHellman2) + actualRef, err := granteeAccessLogic.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) + assertNoError(t, "grantee al decryptref", err) + + if !expectedRef.Equal(actualRef) { + assert.FailNowf(t, fmt.Sprintf("DecryptRef gave back wrong Swarm reference! Expedted: %v, actual: %v", expectedRef, actualRef), "") + } +} + +func TestAddPublisher(t *testing.T) { + t.Parallel() + id0 := getPrivKey(0) + savedLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" + s := kvsmock.New() + ctx := context.Background() + + al := setupAccessLogic() + err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) + assertNoError(t, "AddGrantee", err) + + decodedSavedLookupKey, err := hex.DecodeString(savedLookupKey) + assertNoError(t, "decode LookupKey", err) + + encryptedAccessKey, err := s.Get(ctx, decodedSavedLookupKey) + assertNoError(t, "kvs Get accesskey", err) + + decodedEncryptedAccessKey := hex.EncodeToString(encryptedAccessKey) + + // A random value is returned, so it is only possible to check the length of the returned value + // We know the lookup key because the generated private key is fixed + if len(decodedEncryptedAccessKey) != 64 { + assert.FailNowf(t, fmt.Sprintf("AddGrantee: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)), "") + } +} + +func TestAddNewGranteeToContent(t *testing.T) { + t.Parallel() + id0 := getPrivKey(0) + id1 := getPrivKey(1) + id2 := getPrivKey(2) + ctx := context.Background() + + publisherLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" + firstAddedGranteeLookupKey := "a13678e81f9d939b9401a3ad7e548d2ceb81c50f8c76424296e83a1ad79c0df0" + secondAddedGranteeLookupKey := "d5e9a6499ca74f5b8b958a4b89b7338045b2baa9420e115443a8050e26986564" + + s := kvsmock.New() + al := setupAccessLogic() + err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) + assertNoError(t, "AddGrantee id0", err) + + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey) + assertNoError(t, "AddGrantee id1", err) + + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id2.PublicKey) + assertNoError(t, "AddGrantee id2", err) + + lookupKeyAsByte, err := hex.DecodeString(publisherLookupKey) + assertNoError(t, "publisher lookupkey DecodeString", err) + + result, err := s.Get(ctx, lookupKeyAsByte) + assertNoError(t, "1st kvs get", err) + hexEncodedEncryptedAK := hex.EncodeToString(result) + if len(hexEncodedEncryptedAK) != 64 { + assert.FailNowf(t, fmt.Sprintf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)), "") + } + + lookupKeyAsByte, err = hex.DecodeString(firstAddedGranteeLookupKey) + assertNoError(t, "1st lookupkey DecodeString", err) + + result, err = s.Get(ctx, lookupKeyAsByte) + assertNoError(t, "2nd kvs get", err) + hexEncodedEncryptedAK = hex.EncodeToString(result) + if len(hexEncodedEncryptedAK) != 64 { + assert.FailNowf(t, fmt.Sprintf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)), "") + } + + lookupKeyAsByte, err = hex.DecodeString(secondAddedGranteeLookupKey) + assertNoError(t, "2nd lookupkey DecodeString", err) + + result, err = s.Get(ctx, lookupKeyAsByte) + assertNoError(t, "3rd kvs get", err) + hexEncodedEncryptedAK = hex.EncodeToString(result) + if len(hexEncodedEncryptedAK) != 64 { + assert.FailNowf(t, fmt.Sprintf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)), "") + } +} diff --git a/pkg/accesscontrol/controller.go b/pkg/accesscontrol/controller.go new file mode 100644 index 00000000000..b0a33855e55 --- /dev/null +++ b/pkg/accesscontrol/controller.go @@ -0,0 +1,294 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package accesscontrol provides functionalities needed +// for managing access control on Swarm +package accesscontrol + +import ( + "context" + "crypto/ecdsa" + "fmt" + "io" + "time" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" + "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +// Grantees represents an interface for managing and retrieving grantees for a publisher. +type Grantees interface { + // UpdateHandler manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. + // Only the publisher can make changes to the grantee list. + UpdateHandler(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeRef swarm.Address, historyRef swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) + // Get returns the list of grantees for the given publisher. + // The list is accessible only by the publisher. + Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglRef swarm.Address) ([]*ecdsa.PublicKey, error) +} + +// Controller represents an interface for managing access control on Swarm. +// It provides methods for handling downloads, uploads and updates for grantee lists and references. +type Controller interface { + Grantees + // DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. + DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRef swarm.Address, timestamp int64) (swarm.Address, error) + // UploadHandler encrypts the reference and stores it in the history as the latest update. + UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRef swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + io.Closer +} + +// ControllerStruct represents a controller for access control logic. +type ControllerStruct struct { + access ActLogic +} + +var _ Controller = (*ControllerStruct)(nil) + +// NewController creates a new access controller with the given access logic. +func NewController(access ActLogic) *ControllerStruct { + return &ControllerStruct{ + access: access, + } +} + +// DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. +func (c *ControllerStruct) DownloadHandler( + ctx context.Context, + ls file.LoadSaver, + encryptedRef swarm.Address, + publisher *ecdsa.PublicKey, + historyRef swarm.Address, + timestamp int64, +) (swarm.Address, error) { + _, act, err := c.getHistoryAndAct(ctx, ls, historyRef, publisher, timestamp) + if err != nil { + return swarm.ZeroAddress, err + } + + return c.access.DecryptRef(ctx, act, encryptedRef, publisher) +} + +// UploadHandler encrypts the reference and stores it in the history as the latest update. +func (c *ControllerStruct) UploadHandler( + ctx context.Context, + ls file.LoadSaver, + reference swarm.Address, + publisher *ecdsa.PublicKey, + historyRef swarm.Address, +) (swarm.Address, swarm.Address, swarm.Address, error) { + history, act, err := c.getHistoryAndAct(ctx, ls, historyRef, publisher, time.Now().Unix()) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + actRef := swarm.ZeroAddress + newHistoryRef := historyRef + if historyRef.IsZero() { + newHistoryRef, actRef, err = c.saveHistoryAndAct(ctx, history, nil, act) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + + encryptedRef, err := c.access.EncryptRef(ctx, act, publisher, reference) + return actRef, newHistoryRef, encryptedRef, err +} + +// UpdateHandler manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. +// Only the publisher can make changes to the grantee list. +// Limitation: If an upadate is called again within a second from the latest upload/update then mantaray save fails with ErrInvalidInput, +// because the key (timestamp) is already present, hence a new fork is not created. +func (c *ControllerStruct) UpdateHandler( + ctx context.Context, + ls file.LoadSaver, + gls file.LoadSaver, + encryptedglRef swarm.Address, + historyRef swarm.Address, + publisher *ecdsa.PublicKey, + addList []*ecdsa.PublicKey, + removeList []*ecdsa.PublicKey, +) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { + history, act, err := c.getHistoryAndAct(ctx, ls, historyRef, publisher, time.Now().Unix()) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + + gl, err := c.getGranteeList(ctx, gls, encryptedglRef, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + if len(addList) != 0 { + err = gl.Add(addList) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + granteesToAdd := addList + if len(removeList) != 0 { + err = gl.Remove(removeList) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + // generate new access key and new act, only if history was not newly created + if !historyRef.IsZero() { + act, err = c.newActWithPublisher(ctx, ls, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + granteesToAdd = gl.Get() + } + + for _, grantee := range granteesToAdd { + err := c.access.AddGrantee(ctx, act, publisher, grantee) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + + granteeRef, err := gl.Save(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + + egranteeRef, err := c.encryptRefForPublisher(publisher, granteeRef) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + // need to re-initialize history, because Lookup loads the forks causing the manifest save to skip the root node + if !historyRef.IsZero() { + history, err = NewHistoryReference(ls, historyRef) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + + mtdt := map[string]string{"encryptedglref": egranteeRef.String()} + hRef, actRef, err := c.saveHistoryAndAct(ctx, history, &mtdt, act) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + + return granteeRef, egranteeRef, hRef, actRef, nil +} + +// Get returns the list of grantees for the given publisher. +// The list is accessible only by the publisher. +func (c *ControllerStruct) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglRef swarm.Address) ([]*ecdsa.PublicKey, error) { + gl, err := c.getGranteeList(ctx, ls, encryptedglRef, publisher) + if err != nil { + return nil, err + } + return gl.Get(), nil +} + +func (c *ControllerStruct) newActWithPublisher(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey) (kvs.KeyValueStore, error) { + act, err := kvs.New(ls) + if err != nil { + return nil, err + } + err = c.access.AddGrantee(ctx, act, publisher, publisher) + if err != nil { + return nil, err + } + + return act, nil +} + +func (c *ControllerStruct) getHistoryAndAct(ctx context.Context, ls file.LoadSaver, historyRef swarm.Address, publisher *ecdsa.PublicKey, timestamp int64) (history History, act kvs.KeyValueStore, err error) { + if historyRef.IsZero() { + history, err = NewHistory(ls) + if err != nil { + return nil, nil, err + } + act, err = c.newActWithPublisher(ctx, ls, publisher) + if err != nil { + return nil, nil, err + } + } else { + history, err = NewHistoryReference(ls, historyRef) + if err != nil { + return nil, nil, err + } + entry, err := history.Lookup(ctx, timestamp) + if err != nil { + return nil, nil, err + } + act, err = kvs.NewReference(ls, entry.Reference()) + if err != nil { + return nil, nil, err + } + } + + return history, act, nil +} + +func (c *ControllerStruct) saveHistoryAndAct(ctx context.Context, history History, mtdt *map[string]string, act kvs.KeyValueStore) (swarm.Address, swarm.Address, error) { + actRef, err := act.Save(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, err + } + err = history.Add(ctx, actRef, nil, mtdt) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, err + } + historyRef, err := history.Store(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, err + } + + return historyRef, actRef, nil +} + +func (c *ControllerStruct) getGranteeList(ctx context.Context, ls file.LoadSaver, encryptedglRef swarm.Address, publisher *ecdsa.PublicKey) (gl GranteeList, err error) { + if encryptedglRef.IsZero() { + gl = NewGranteeList(ls) + } else { + granteeref, err := c.decryptRefForPublisher(publisher, encryptedglRef) + if err != nil { + return nil, err + } + + gl, err = NewGranteeListReference(ctx, ls, granteeref) + if err != nil { + return nil, err + } + } + + return gl, nil +} + +func (c *ControllerStruct) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + keys, err := c.access.Session.Key(publisherPubKey, [][]byte{oneByteArray}) + if err != nil { + return swarm.ZeroAddress, err + } + refCipher := encryption.New(keys[0], 0, 0, hashFunc) + encryptedRef, err := refCipher.Encrypt(ref.Bytes()) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("failed to encrypt reference: %w", err) + } + + return swarm.NewAddress(encryptedRef), nil +} + +func (c *ControllerStruct) decryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, encryptedRef swarm.Address) (swarm.Address, error) { + keys, err := c.access.Session.Key(publisherPubKey, [][]byte{oneByteArray}) + if err != nil { + return swarm.ZeroAddress, err + } + refCipher := encryption.New(keys[0], 0, 0, hashFunc) + ref, err := refCipher.Decrypt(encryptedRef.Bytes()) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("failed to decrypt reference: %w", err) + } + + return swarm.NewAddress(ref), nil +} + +// Close simply returns nil +func (c *ControllerStruct) Close() error { + return nil +} diff --git a/pkg/accesscontrol/controller_test.go b/pkg/accesscontrol/controller_test.go new file mode 100644 index 00000000000..cf1eba59a56 --- /dev/null +++ b/pkg/accesscontrol/controller_test.go @@ -0,0 +1,335 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol_test + +import ( + "context" + "crypto/ecdsa" + "reflect" + "testing" + "time" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" + encryption "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" +) + +//nolint:errcheck,gosec,wrapcheck +func getHistoryFixture(t *testing.T, ctx context.Context, ls file.LoadSaver, al accesscontrol.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { + t.Helper() + h, err := accesscontrol.NewHistory(ls) + if err != nil { + return swarm.ZeroAddress, err + } + pk1 := getPrivKey(1) + pk2 := getPrivKey(2) + + kvs0, err := kvs.New(ls) + assertNoError(t, "kvs0 create", err) + al.AddGrantee(ctx, kvs0, publisher, publisher) + kvs0Ref, err := kvs0.Save(ctx) + assertNoError(t, "kvs0 save", err) + kvs1, err := kvs.New(ls) + assertNoError(t, "kvs1 create", err) + al.AddGrantee(ctx, kvs1, publisher, publisher) + al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey) + kvs1Ref, err := kvs1.Save(ctx) + assertNoError(t, "kvs1 save", err) + kvs2, err := kvs.New(ls) + assertNoError(t, "kvs2 create", err) + al.AddGrantee(ctx, kvs2, publisher, publisher) + al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey) + kvs2Ref, err := kvs2.Save(ctx) + assertNoError(t, "kvs2 save", err) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + + h.Add(ctx, kvs0Ref, &thirdTime, nil) + h.Add(ctx, kvs1Ref, &firstTime, nil) + h.Add(ctx, kvs2Ref, &secondTime, nil) + return h.Store(ctx) +} + +func TestController_UploadHandler(t *testing.T) { + t.Parallel() + ctx := context.Background() + publisher := getPrivKey(0) + diffieHellman := accesscontrol.NewDefaultSession(publisher) + al := accesscontrol.NewLogic(diffieHellman) + c := accesscontrol.NewController(al) + ls := createLs() + + t.Run("New upload", func(t *testing.T) { + ref := swarm.RandAddress(t) + _, hRef, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) + assertNoError(t, "UploadHandler", err) + + h, err := accesscontrol.NewHistoryReference(ls, hRef) + assertNoError(t, "create history ref", err) + entry, err := h.Lookup(ctx, time.Now().Unix()) + assertNoError(t, "history lookup", err) + actRef := entry.Reference() + act, err := kvs.NewReference(ls, actRef) + assertNoError(t, "kvs create ref", err) + expRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) + + assertNoError(t, "encrypt ref", err) + assert.Equal(t, encRef, expRef) + assert.NotEqual(t, hRef, swarm.ZeroAddress) + }) + + t.Run("Upload to same history", func(t *testing.T) { + ref := swarm.RandAddress(t) + _, hRef1, _, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) + assertNoError(t, "1st upload", err) + _, hRef2, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, hRef1) + assertNoError(t, "2nd upload", err) + h, err := accesscontrol.NewHistoryReference(ls, hRef2) + assertNoError(t, "create history ref", err) + hRef2, err = h.Store(ctx) + assertNoError(t, "store history", err) + assert.True(t, hRef1.Equal(hRef2)) + + h, err = accesscontrol.NewHistoryReference(ls, hRef2) + assertNoError(t, "create history ref", err) + entry, err := h.Lookup(ctx, time.Now().Unix()) + assertNoError(t, "history lookup", err) + actRef := entry.Reference() + act, err := kvs.NewReference(ls, actRef) + assertNoError(t, "kvs create ref", err) + expRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) + + assertNoError(t, "encrypt ref", err) + assert.Equal(t, expRef, encRef) + assert.NotEqual(t, hRef2, swarm.ZeroAddress) + }) +} + +func TestController_PublisherDownload(t *testing.T) { + t.Parallel() + ctx := context.Background() + publisher := getPrivKey(0) + diffieHellman := accesscontrol.NewDefaultSession(publisher) + al := accesscontrol.NewLogic(diffieHellman) + c := accesscontrol.NewController(al) + ls := createLs() + ref := swarm.RandAddress(t) + href, err := getHistoryFixture(t, ctx, ls, al, &publisher.PublicKey) + assertNoError(t, "history fixture create", err) + h, err := accesscontrol.NewHistoryReference(ls, href) + assertNoError(t, "create history ref", err) + entry, err := h.Lookup(ctx, time.Now().Unix()) + assertNoError(t, "history lookup", err) + actRef := entry.Reference() + act, err := kvs.NewReference(ls, actRef) + assertNoError(t, "kvs create ref", err) + encRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) + assertNoError(t, "encrypt ref", err) + + dref, err := c.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, href, time.Now().Unix()) + assertNoError(t, "download by publisher", err) + assert.Equal(t, ref, dref) +} + +func TestController_GranteeDownload(t *testing.T) { + t.Parallel() + ctx := context.Background() + publisher := getPrivKey(0) + grantee := getPrivKey(2) + publisherDH := accesscontrol.NewDefaultSession(publisher) + publisherAL := accesscontrol.NewLogic(publisherDH) + + diffieHellman := accesscontrol.NewDefaultSession(grantee) + al := accesscontrol.NewLogic(diffieHellman) + ls := createLs() + c := accesscontrol.NewController(al) + ref := swarm.RandAddress(t) + href, err := getHistoryFixture(t, ctx, ls, publisherAL, &publisher.PublicKey) + assertNoError(t, "history fixture create", err) + h, err := accesscontrol.NewHistoryReference(ls, href) + assertNoError(t, "history fixture create", err) + ts := time.Date(2001, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + entry, err := h.Lookup(ctx, ts) + assertNoError(t, "history lookup", err) + actRef := entry.Reference() + act, err := kvs.NewReference(ls, actRef) + assertNoError(t, "kvs create ref", err) + encRef, err := publisherAL.EncryptRef(ctx, act, &publisher.PublicKey, ref) + + assertNoError(t, "encrypt ref", err) + dref, err := c.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, href, ts) + assertNoError(t, "download by grantee", err) + assert.Equal(t, ref, dref) +} + +func TestController_UpdateHandler(t *testing.T) { + t.Parallel() + ctx := context.Background() + publisher := getPrivKey(1) + diffieHellman := accesscontrol.NewDefaultSession(publisher) + al := accesscontrol.NewLogic(diffieHellman) + keys, err := al.Session.Key(&publisher.PublicKey, [][]byte{{1}}) + assertNoError(t, "Session key", err) + refCipher := encryption.New(keys[0], 0, 0, sha3.NewLegacyKeccak256) + ls := createLs() + gls := loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), true, redundancy.NONE)) + c := accesscontrol.NewController(al) + href, err := getHistoryFixture(t, ctx, ls, al, &publisher.PublicKey) + assertNoError(t, "history fixture create", err) + + grantee1 := getPrivKey(0) + grantee := getPrivKey(2) + + t.Run("add to new list", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, _, _, _, err := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandlererror", err) + + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + + assertNoError(t, "create granteelist ref", err) + assert.Len(t, gl.Get(), 1) + }) + t.Run("add to existing list", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandlererror", err) + + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + + assertNoError(t, "create granteelist ref", err) + assert.Len(t, gl.Get(), 1) + + addList = []*ecdsa.PublicKey{&getPrivKey(0).PublicKey} + granteeRef, _, _, _, err = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandler", err) + gl, err = accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + assertNoError(t, "create granteelist ref", err) + assert.Len(t, gl.Get(), 2) + }) + t.Run("add and revoke", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + revokeList := []*ecdsa.PublicKey{&grantee1.PublicKey} + gl := accesscontrol.NewGranteeList(ls) + err = gl.Add([]*ecdsa.PublicKey{&publisher.PublicKey, &grantee1.PublicKey}) + granteeRef, err := gl.Save(ctx) + assertNoError(t, "granteelist save", err) + eglref, err := refCipher.Encrypt(granteeRef.Bytes()) + assertNoError(t, "encrypt granteeref", err) + + granteeRef, _, _, _, err = c.UpdateHandler(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) + assertNoError(t, "UpdateHandler", err) + gl, err = accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + + assertNoError(t, "create granteelist ref", err) + assert.Len(t, gl.Get(), 2) + }) + t.Run("add and revoke then get from history", func(t *testing.T) { + addRevokeList := []*ecdsa.PublicKey{&grantee.PublicKey} + ref := swarm.RandAddress(t) + _, hRef, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) + require.NoError(t, err) + + // Need to wait a second before each update call so that a new history mantaray fork is created for the new key(timestamp) entry + time.Sleep(1 * time.Second) + beforeRevokeTS := time.Now().Unix() + _, egranteeRef, hrefUpdate1, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, hRef, &publisher.PublicKey, addRevokeList, nil) + require.NoError(t, err) + + time.Sleep(1 * time.Second) + granteeRef, _, hrefUpdate2, _, err := c.UpdateHandler(ctx, ls, gls, egranteeRef, hrefUpdate1, &publisher.PublicKey, nil, addRevokeList) + require.NoError(t, err) + + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + require.NoError(t, err) + assert.Empty(t, gl.Get()) + // expect history reference to be different after grantee list update + assert.NotEqual(t, hrefUpdate1, hrefUpdate2) + + granteeDH := accesscontrol.NewDefaultSession(grantee) + granteeAl := accesscontrol.NewLogic(granteeDH) + granteeCtrl := accesscontrol.NewController(granteeAl) + // download with grantee shall still work with the timestamp before the revoke + decRef, err := granteeCtrl.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, beforeRevokeTS) + require.NoError(t, err) + assert.Equal(t, ref, decRef) + + // download with grantee shall NOT work with the latest timestamp + decRef, err = granteeCtrl.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, time.Now().Unix()) + require.Error(t, err) + assert.Equal(t, swarm.ZeroAddress, decRef) + + // publisher shall still be able to download with the timestamp before the revoke + decRef, err = c.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, beforeRevokeTS) + require.NoError(t, err) + assert.Equal(t, ref, decRef) + }) + t.Run("add twice", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey, &grantee.PublicKey} + //nolint:ineffassign,staticcheck,wastedassign + granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, err = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandler", err) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + + assertNoError(t, "create granteelist ref", err) + assert.Len(t, gl.Get(), 1) + }) + t.Run("revoke non-existing", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, _, _, _, err := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandler", err) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + + assertNoError(t, "create granteelist ref", err) + assert.Len(t, gl.Get(), 1) + }) +} + +func TestController_Get(t *testing.T) { + t.Parallel() + ctx := context.Background() + publisher := getPrivKey(1) + caller := getPrivKey(0) + grantee := getPrivKey(2) + diffieHellman1 := accesscontrol.NewDefaultSession(publisher) + diffieHellman2 := accesscontrol.NewDefaultSession(caller) + al1 := accesscontrol.NewLogic(diffieHellman1) + al2 := accesscontrol.NewLogic(diffieHellman2) + ls := createLs() + gls := loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), true, redundancy.NONE)) + c1 := accesscontrol.NewController(al1) + c2 := accesscontrol.NewController(al2) + + t.Run("get by publisher", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, eglRef, _, _, err := c1.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandler", err) + + grantees, err := c1.Get(ctx, ls, &publisher.PublicKey, eglRef) + assertNoError(t, "get by publisher", err) + assert.True(t, reflect.DeepEqual(grantees, addList)) + + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) + assertNoError(t, "create granteelist ref", err) + assert.True(t, reflect.DeepEqual(gl.Get(), addList)) + }) + t.Run("get by non-publisher", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + _, eglRef, _, _, err := c1.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + assertNoError(t, "UpdateHandler", err) + grantees, err := c2.Get(ctx, ls, &publisher.PublicKey, eglRef) + assertError(t, "controller get by non-publisher", err) + assert.Nil(t, grantees) + }) +} diff --git a/pkg/accesscontrol/grantee.go b/pkg/accesscontrol/grantee.go new file mode 100644 index 00000000000..902aebbf433 --- /dev/null +++ b/pkg/accesscontrol/grantee.go @@ -0,0 +1,177 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +const ( + publicKeyLen = 65 +) + +var ( + // ErrNothingToRemove indicates that the remove list is empty. + ErrNothingToRemove = errors.New("nothing to remove") + // ErrNoGranteeFound indicates that the grantee list is empty. + ErrNoGranteeFound = errors.New("no grantee found") + // ErrNothingToAdd indicates that the add list is empty. + ErrNothingToAdd = errors.New("nothing to add") +) + +// GranteeList manages a list of public keys. +type GranteeList interface { + // Add adds a list of public keys to the grantee list. It filters out duplicates. + Add(addList []*ecdsa.PublicKey) error + // Remove removes a list of public keys from the grantee list, if there is any. + Remove(removeList []*ecdsa.PublicKey) error + // Get simply returns the list of public keys. + Get() []*ecdsa.PublicKey + // Save saves the grantee list to the underlying storage and returns the reference. + Save(ctx context.Context) (swarm.Address, error) +} + +// GranteeListStruct represents a list of grantee public keys. +type GranteeListStruct struct { + grantees []*ecdsa.PublicKey + loadSave file.LoadSaver +} + +var _ GranteeList = (*GranteeListStruct)(nil) + +// Get simply returns the list of public keys. +func (g *GranteeListStruct) Get() []*ecdsa.PublicKey { + return g.grantees +} + +// Add adds a list of public keys to the grantee list. It filters out duplicates. +func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { + if len(addList) == 0 { + return ErrNothingToAdd + } + filteredList := make([]*ecdsa.PublicKey, 0, len(addList)) + for _, addkey := range addList { + add := true + for _, granteekey := range g.grantees { + if granteekey.Equal(addkey) { + add = false + break + } + } + for _, filteredkey := range filteredList { + if filteredkey.Equal(addkey) { + add = false + break + } + } + if add { + filteredList = append(filteredList, addkey) + } + } + g.grantees = append(g.grantees, filteredList...) + + return nil +} + +// Save saves the grantee list to the underlying storage and returns the reference. +func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { + data := serialize(g.grantees) + refBytes, err := g.loadSave.Save(ctx, data) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("grantee save error: %w", err) + } + + return swarm.NewAddress(refBytes), nil +} + +// Remove removes a list of public keys from the grantee list, if there is any. +func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { + if len(keysToRemove) == 0 { + return ErrNothingToRemove + } + + if len(g.grantees) == 0 { + return ErrNoGranteeFound + } + grantees := g.grantees + + for _, remove := range keysToRemove { + for i := 0; i < len(grantees); i++ { + if grantees[i].Equal(remove) { + grantees[i] = grantees[len(grantees)-1] + grantees = grantees[:len(grantees)-1] + } + } + } + g.grantees = grantees + + return nil +} + +// NewGranteeList creates a new (and empty) grantee list. +func NewGranteeList(ls file.LoadSaver) *GranteeListStruct { + return &GranteeListStruct{ + grantees: []*ecdsa.PublicKey{}, + loadSave: ls, + } +} + +// NewGranteeListReference loads an existing grantee list. +func NewGranteeListReference(ctx context.Context, ls file.LoadSaver, reference swarm.Address) (*GranteeListStruct, error) { + data, err := ls.Load(ctx, reference.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to load grantee list reference, %w", err) + } + grantees := deserialize(data) + + return &GranteeListStruct{ + grantees: grantees, + loadSave: ls, + }, nil +} + +func serialize(publicKeys []*ecdsa.PublicKey) []byte { + b := make([]byte, 0, len(publicKeys)*publicKeyLen) + for _, key := range publicKeys { + b = append(b, serializePublicKey(key)...) + } + return b +} + +func serializePublicKey(pub *ecdsa.PublicKey) []byte { + return elliptic.Marshal(pub.Curve, pub.X, pub.Y) +} + +func deserialize(data []byte) []*ecdsa.PublicKey { + if len(data) == 0 { + return []*ecdsa.PublicKey{} + } + + p := make([]*ecdsa.PublicKey, 0, len(data)/publicKeyLen) + for i := 0; i < len(data); i += publicKeyLen { + pubKey := deserializeBytes(data[i : i+publicKeyLen]) + if pubKey == nil { + return []*ecdsa.PublicKey{} + } + p = append(p, pubKey) + } + return p +} + +func deserializeBytes(data []byte) *ecdsa.PublicKey { + key, err := btcec.ParsePubKey(data) + if err != nil { + return nil + } + return key.ToECDSA() +} diff --git a/pkg/accesscontrol/grantee_test.go b/pkg/accesscontrol/grantee_test.go new file mode 100644 index 00000000000..f3c54dc907d --- /dev/null +++ b/pkg/accesscontrol/grantee_test.go @@ -0,0 +1,237 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol_test + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" +) + +var mockStorer = mockstorer.New() + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} + +func createLs() file.LoadSaver { + return loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), false, redundancy.NONE)) +} + +func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { + key1, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + if err != nil { + return nil, err + } + key2, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + if err != nil { + return nil, err + } + key3, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + if err != nil { + return nil, err + } + return []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey, &key3.PublicKey}, nil +} + +func TestGranteeAddGet(t *testing.T) { + t.Parallel() + gl := accesscontrol.NewGranteeList(createLs()) + keys, err := generateKeyListFixture() + assertNoError(t, "key generation", err) + + t.Run("Get empty grantee list should return", func(t *testing.T) { + val := gl.Get() + assert.Empty(t, val) + }) + + t.Run("Get should return value equal to put value", func(t *testing.T) { + var ( + keys2, err = generateKeyListFixture() + addList1 = []*ecdsa.PublicKey{keys[0]} + addList2 = []*ecdsa.PublicKey{keys[1], keys[2]} + addList3 = keys2 + ) + assertNoError(t, "key generation", err) + testCases := []struct { + name string + list []*ecdsa.PublicKey + }{ + { + name: "Test list = 1", + list: addList1, + }, + { + name: "Test list = duplicate1", + list: addList1, + }, + { + name: "Test list = 2", + list: addList2, + }, + { + name: "Test list = 3", + list: addList3, + }, + { + name: "Test empty add list", + list: nil, + }, + } + + expList := []*ecdsa.PublicKey{} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := gl.Add(tc.list) + if tc.list == nil { + assertError(t, "granteelist add", err) + } else { + assertNoError(t, "granteelist add", err) + if tc.name != "Test list = duplicate1" { + expList = append(expList, tc.list...) + } + retVal := gl.Get() + assert.Equal(t, expList, retVal) + } + }) + } + }) +} + +func TestGranteeRemove(t *testing.T) { + t.Parallel() + gl := accesscontrol.NewGranteeList(createLs()) + keys, err := generateKeyListFixture() + assertNoError(t, "key generation", err) + + t.Run("Add should NOT return", func(t *testing.T) { + err := gl.Add(keys) + assertNoError(t, "granteelist add", err) + retVal := gl.Get() + assert.Equal(t, keys, retVal) + }) + removeList1 := []*ecdsa.PublicKey{keys[0]} + removeList2 := []*ecdsa.PublicKey{keys[2], keys[1]} + t.Run("Remove the first item should return NO", func(t *testing.T) { + err := gl.Remove(removeList1) + assertNoError(t, "granteelist remove", err) + retVal := gl.Get() + assert.Equal(t, removeList2, retVal) + }) + t.Run("Remove non-existent item should return NO", func(t *testing.T) { + err := gl.Remove(removeList1) + assertNoError(t, "granteelist remove", err) + retVal := gl.Get() + assert.Equal(t, removeList2, retVal) + }) + t.Run("Remove second and third item should return NO", func(t *testing.T) { + err := gl.Remove(removeList2) + assertNoError(t, "granteelist remove", err) + retVal := gl.Get() + assert.Empty(t, retVal) + }) + t.Run("Remove from empty grantee list should return", func(t *testing.T) { + err := gl.Remove(removeList1) + assertError(t, "remove from empty grantee list", err) + retVal := gl.Get() + assert.Empty(t, retVal) + }) + t.Run("Remove empty remove list should return", func(t *testing.T) { + err := gl.Remove(nil) + assertError(t, "remove empty list", err) + retVal := gl.Get() + assert.Empty(t, retVal) + }) +} + +func TestGranteeSave(t *testing.T) { + t.Parallel() + ctx := context.Background() + keys, err := generateKeyListFixture() + assertNoError(t, "key generation", err) + + t.Run("Create grantee list with invalid reference, expect", func(t *testing.T) { + gl, err := accesscontrol.NewGranteeListReference(ctx, createLs(), swarm.RandAddress(t)) + assertError(t, "create grantee list ref", err) + assert.Nil(t, gl) + }) + t.Run("Save empty grantee list return NO", func(t *testing.T) { + gl := accesscontrol.NewGranteeList(createLs()) + _, err := gl.Save(ctx) + assertNoError(t, "granteelist save", err) + }) + t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { + gl := accesscontrol.NewGranteeList(createLs()) + err = gl.Add(keys) + assertNoError(t, "granteelist add", err) + ref, err := gl.Save(ctx) + assertNoError(t, "granteelist save", err) + assert.True(t, ref.IsValidNonEmpty()) + }) + t.Run("Save grantee list with one item, no error, pre-save value exist", func(t *testing.T) { + ls := createLs() + gl1 := accesscontrol.NewGranteeList(ls) + + err := gl1.Add(keys) + assertNoError(t, "granteelist add", err) + + ref, err := gl1.Save(ctx) + assertNoError(t, "1st granteelist save", err) + + gl2, err := accesscontrol.NewGranteeListReference(ctx, ls, ref) + assertNoError(t, "create grantee list ref", err) + val := gl2.Get() + assertNoError(t, "2nd granteelist save", err) + assert.Equal(t, keys, val) + }) + t.Run("Save grantee list and add one item, no error, after-save value exist", func(t *testing.T) { + ls := createLs() + keys2, err := generateKeyListFixture() + assertNoError(t, "key generation", err) + + gl1 := accesscontrol.NewGranteeList(ls) + + err = gl1.Add(keys) + assertNoError(t, "granteelist1 add", err) + + ref, err := gl1.Save(ctx) + assertNoError(t, "granteelist1 save", err) + + gl2, err := accesscontrol.NewGranteeListReference(ctx, ls, ref) + assertNoError(t, "create grantee list ref", err) + err = gl2.Add(keys2) + assertNoError(t, "create grantee list ref", err) + + val := gl2.Get() + assert.Equal(t, append(keys, keys2...), val) + }) +} + +func TestGranteeRemoveTwo(t *testing.T) { + gl := accesscontrol.NewGranteeList(createLs()) + keys, err := generateKeyListFixture() + assertNoError(t, "key generation", err) + err = gl.Add([]*ecdsa.PublicKey{keys[0]}) + assertNoError(t, "1st granteelist add", err) + err = gl.Add([]*ecdsa.PublicKey{keys[0]}) + assertNoError(t, "2nd granteelist add", err) + err = gl.Remove([]*ecdsa.PublicKey{keys[0]}) + assertNoError(t, "granteelist remove", err) +} diff --git a/pkg/accesscontrol/history.go b/pkg/accesscontrol/history.go new file mode 100644 index 00000000000..b8aba6e5cc9 --- /dev/null +++ b/pkg/accesscontrol/history.go @@ -0,0 +1,199 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol + +import ( + "context" + "errors" + "fmt" + "math" + "strconv" + "time" + + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/manifest" + "github.com/ethersphere/bee/v2/pkg/manifest/mantaray" + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +var ( + // ErrEndIteration indicates that the iteration terminated. + ErrEndIteration = errors.New("end iteration") + // ErrUnexpectedType indicates that an error occurred during the mantary-manifest creation. + ErrUnexpectedType = errors.New("unexpected type") + // ErrInvalidTimestamp indicates that the timestamp given to Lookup is invalid. + ErrInvalidTimestamp = errors.New("invalid timestamp") + // ErrNotFound is returned when an Entry is not found in the history. + ErrNotFound = errors.New("access control: not found") +) + +// History represents the interface for managing access control history. +type History interface { + // Add adds a new entry to the access control history with the given timestamp and metadata. + Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error + // Lookup retrieves the entry from the history based on the given timestamp or returns error if not found. + Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) + // Store stores the history to the underlying storage and returns the reference. + Store(ctx context.Context) (swarm.Address, error) +} + +var _ History = (*HistoryStruct)(nil) + +// manifestInterface extends the `manifest.Interface` interface and adds a `Root` method. +type manifestInterface interface { + manifest.Interface + Root() *mantaray.Node +} + +// HistoryStruct represents an access control histroy with a mantaray-based manifest. +type HistoryStruct struct { + manifest manifestInterface + ls file.LoadSaver +} + +// NewHistory creates a new history with a mantaray-based manifest. +func NewHistory(ls file.LoadSaver) (*HistoryStruct, error) { + m, err := manifest.NewMantarayManifest(ls, false) + if err != nil { + return nil, fmt.Errorf("failed to create mantaray manifest: %w", err) + } + + mm, ok := m.(manifestInterface) + if !ok { + return nil, fmt.Errorf("%w: expected MantarayManifest, got %T", ErrUnexpectedType, m) + } + + return &HistoryStruct{manifest: mm, ls: ls}, nil +} + +// NewHistoryReference loads a history with a mantaray-based manifest. +func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (*HistoryStruct, error) { + m, err := manifest.NewMantarayManifestReference(ref, ls) + if err != nil { + return nil, fmt.Errorf("failed to create mantaray manifest reference: %w", err) + } + + mm, ok := m.(manifestInterface) + if !ok { + return nil, fmt.Errorf("%w: expected MantarayManifest, got %T", ErrUnexpectedType, m) + } + + return &HistoryStruct{manifest: mm, ls: ls}, nil +} + +// Add adds a new entry to the access control history with the given timestamp and metadata. +func (h *HistoryStruct) Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error { + mtdt := map[string]string{} + if metadata != nil { + mtdt = *metadata + } + // add timestamps transformed so that the latests timestamp becomes the smallest key. + unixTime := time.Now().Unix() + if timestamp != nil { + unixTime = *timestamp + } + + key := strconv.FormatInt(math.MaxInt64-unixTime, 10) + err := h.manifest.Add(ctx, key, manifest.NewEntry(ref, mtdt)) + if err != nil { + return fmt.Errorf("failed to add to manifest: %w", err) + } + + return nil +} + +// Lookup retrieves the entry from the history based on the given timestamp or returns error if not found. +func (h *HistoryStruct) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) { + if timestamp <= 0 { + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrInvalidTimestamp + } + + reversedTimestamp := math.MaxInt64 - timestamp + node, err := h.lookupNode(ctx, reversedTimestamp) + if err != nil { + switch { + case errors.Is(err, manifest.ErrNotFound): + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrNotFound + default: + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), err + } + } + + if node != nil { + return manifest.NewEntry(swarm.NewAddress(node.Entry()), node.Metadata()), nil + } + + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrNotFound +} + +func (h *HistoryStruct) lookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { + // before node's timestamp is the closest one that is less than or equal to the searched timestamp + // for instance: 2030, 2020, 1994 -> search for 2021 -> before is 2020 + var beforeNode *mantaray.Node + // after node's timestamp is after the latest + // for instance: 2030, 2020, 1994 -> search for 1980 -> after is 1994 + var afterNode *mantaray.Node + + walker := func(pathTimestamp []byte, currNode *mantaray.Node, err error) error { + if err != nil { + return err + } + + if currNode.IsValueType() && len(currNode.Entry()) > 0 { + afterNode = currNode + + match, err := isBeforeMatch(pathTimestamp, searchedTimestamp) + if match { + beforeNode = currNode + // return error to stop the walk, this is how WalkNode works... + return ErrEndIteration + } + + return err + } + + return nil + } + + rootNode := h.manifest.Root() + err := rootNode.WalkNode(ctx, []byte{}, h.ls, walker) + + if err != nil && !errors.Is(err, ErrEndIteration) { + return nil, fmt.Errorf("history lookup node error: %w", err) + } + + if beforeNode != nil { + return beforeNode, nil + } + if afterNode != nil { + return afterNode, nil + } + return nil, nil +} + +// Store stores the history to the underlying storage and returns the reference. +func (h *HistoryStruct) Store(ctx context.Context) (swarm.Address, error) { + return h.manifest.Store(ctx) +} + +func bytesToInt64(b []byte) (int64, error) { + num, err := strconv.ParseInt(string(b), 10, 64) + if err != nil { + return -1, err + } + + return num, nil +} + +func isBeforeMatch(pathTimestamp []byte, searchedTimestamp int64) (bool, error) { + targetTimestamp, err := bytesToInt64(pathTimestamp) + if err != nil { + return false, err + } + if targetTimestamp == 0 { + return false, nil + } + return searchedTimestamp <= targetTimestamp, nil +} diff --git a/pkg/accesscontrol/history_test.go b/pkg/accesscontrol/history_test.go new file mode 100644 index 00000000000..7c4d0ff0168 --- /dev/null +++ b/pkg/accesscontrol/history_test.go @@ -0,0 +1,164 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol_test + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" +) + +func TestHistoryAdd(t *testing.T) { + t.Parallel() + h, err := accesscontrol.NewHistory(nil) + assertNoError(t, "create history", err) + + addr := swarm.NewAddress([]byte("addr")) + + ctx := context.Background() + + err = h.Add(ctx, addr, nil, nil) + assertNoError(t, "history add", err) +} + +func TestSingleNodeHistoryLookup(t *testing.T) { + t.Parallel() + storer := mockstorer.New() + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) + + h, err := accesscontrol.NewHistory(ls) + assertNoError(t, "create history", err) + + testActRef := swarm.RandAddress(t) + err = h.Add(ctx, testActRef, nil, nil) + assertNoError(t, "history add", err) + + _, err = h.Store(ctx) + assertNoError(t, "store history", err) + + searchedTime := time.Now().Unix() + entry, err := h.Lookup(ctx, searchedTime) + assertNoError(t, "history lookup", err) + actRef := entry.Reference() + assert.True(t, actRef.Equal(testActRef)) + assert.Nil(t, entry.Metadata()) +} + +func TestMultiNodeHistoryLookup(t *testing.T) { + t.Parallel() + storer := mockstorer.New() + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) + + h, err := accesscontrol.NewHistory(ls) + assertNoError(t, "create history", err) + + testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + mtdt1 := map[string]string{"firstTime": "1994-04-01"} + err = h.Add(ctx, testActRef1, &firstTime, &mtdt1) + assertNoError(t, "1st history add", err) + + testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) + secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + mtdt2 := map[string]string{"secondTime": "2000-04-01"} + err = h.Add(ctx, testActRef2, &secondTime, &mtdt2) + assertNoError(t, "2nd history add", err) + + testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) + thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + mtdt3 := map[string]string{"thirdTime": "2015-04-01"} + err = h.Add(ctx, testActRef3, &thirdTime, &mtdt3) + assertNoError(t, "3rd history add", err) + + testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) + fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + mtdt4 := map[string]string{"fourthTime": "2020-04-01"} + err = h.Add(ctx, testActRef4, &fourthTime, &mtdt4) + assertNoError(t, "4th history add", err) + + testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) + fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + mtdt5 := map[string]string{"fifthTime": "2030-04-01"} + err = h.Add(ctx, testActRef5, &fifthTime, &mtdt5) + assertNoError(t, "5th history add", err) + + // latest + searchedTime := time.Date(1980, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + entry, err := h.Lookup(ctx, searchedTime) + assertNoError(t, "1st history lookup", err) + actRef := entry.Reference() + assert.True(t, actRef.Equal(testActRef1)) + assert.True(t, reflect.DeepEqual(mtdt1, entry.Metadata())) + + // before first time + searchedTime = time.Date(2021, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + entry, err = h.Lookup(ctx, searchedTime) + assertNoError(t, "2nd history lookup", err) + actRef = entry.Reference() + assert.True(t, actRef.Equal(testActRef4)) + assert.True(t, reflect.DeepEqual(mtdt4, entry.Metadata())) + + // same time + searchedTime = time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + entry, err = h.Lookup(ctx, searchedTime) + assertNoError(t, "3rd history lookup", err) + actRef = entry.Reference() + assert.True(t, actRef.Equal(testActRef2)) + assert.True(t, reflect.DeepEqual(mtdt2, entry.Metadata())) + + // after time + searchedTime = time.Date(2045, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + entry, err = h.Lookup(ctx, searchedTime) + assertNoError(t, "4th history lookup", err) + actRef = entry.Reference() + assert.True(t, actRef.Equal(testActRef5)) + assert.True(t, reflect.DeepEqual(mtdt5, entry.Metadata())) +} + +func TestHistoryStore(t *testing.T) { + t.Parallel() + storer := mockstorer.New() + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) + + h1, err := accesscontrol.NewHistory(ls) + assertNoError(t, "create history", err) + + testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + mtdt1 := map[string]string{"firstTime": "1994-04-01"} + err = h1.Add(ctx, testActRef1, &firstTime, &mtdt1) + assertNoError(t, "history add", err) + + href1, err := h1.Store(ctx) + assertNoError(t, "store history", err) + + h2, err := accesscontrol.NewHistoryReference(ls, href1) + assertNoError(t, "create history ref", err) + + entry1, err := h2.Lookup(ctx, firstTime) + assertNoError(t, "history lookup", err) + actRef1 := entry1.Reference() + assert.True(t, actRef1.Equal(testActRef1)) + assert.True(t, reflect.DeepEqual(mtdt1, entry1.Metadata())) +} + +func pipelineFactory(s storage.Putter, encrypt bool) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(context.Background(), s, encrypt, 0) + } +} diff --git a/pkg/accesscontrol/kvs/kvs.go b/pkg/accesscontrol/kvs/kvs.go new file mode 100644 index 00000000000..a5f644e60fe --- /dev/null +++ b/pkg/accesscontrol/kvs/kvs.go @@ -0,0 +1,106 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package kvs provides functionalities needed +// for storing key-value pairs on Swarm. +// +//nolint:ireturn +package kvs + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/manifest" + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +var ( + // ErrNothingToSave indicates that no new key-value pair was added to the store. + ErrNothingToSave = errors.New("nothing to save") + // ErrNotFound is returned when an Entry is not found in the storage. + ErrNotFound = errors.New("kvs entry not found") +) + +// KeyValueStore represents a key-value store. +type KeyValueStore interface { + // Get retrieves the value associated with the given key. + Get(ctx context.Context, key []byte) ([]byte, error) + // Put stores the given key-value pair in the store. + Put(ctx context.Context, key, value []byte) error + // Save saves key-value pair to the underlying storage and returns the reference. + Save(ctx context.Context) (swarm.Address, error) +} + +type keyValueStore struct { + manifest manifest.Interface + putCnt int +} + +var _ KeyValueStore = (*keyValueStore)(nil) + +// Get retrieves the value associated with the given key. +func (s *keyValueStore) Get(ctx context.Context, key []byte) ([]byte, error) { + entry, err := s.manifest.Lookup(ctx, hex.EncodeToString(key)) + if err != nil { + switch { + case errors.Is(err, manifest.ErrNotFound): + return nil, ErrNotFound + default: + return nil, fmt.Errorf("failed to get value from manifest %w", err) + } + } + ref := entry.Reference() + return ref.Bytes(), nil +} + +// Put stores the given key-value pair in the store. +func (s *keyValueStore) Put(ctx context.Context, key []byte, value []byte) error { + err := s.manifest.Add(ctx, hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) + if err != nil { + return fmt.Errorf("failed to put value to manifest %w", err) + } + s.putCnt++ + return nil +} + +// Save saves key-value pair to the underlying storage and returns the reference. +func (s *keyValueStore) Save(ctx context.Context) (swarm.Address, error) { + if s.putCnt == 0 { + return swarm.ZeroAddress, ErrNothingToSave + } + ref, err := s.manifest.Store(ctx) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("failed to store manifest %w", err) + } + s.putCnt = 0 + return ref, nil +} + +// New creates a new key-value store with a simple manifest. +func New(ls file.LoadSaver) (KeyValueStore, error) { + m, err := manifest.NewSimpleManifest(ls) + if err != nil { + return nil, fmt.Errorf("failed to create simple manifest: %w", err) + } + + return &keyValueStore{ + manifest: m, + }, nil +} + +// NewReference loads a key-value store with a simple manifest. +func NewReference(ls file.LoadSaver, ref swarm.Address) (KeyValueStore, error) { + m, err := manifest.NewSimpleManifestReference(ref, ls) + if err != nil { + return nil, fmt.Errorf("failed to create simple manifest reference: %w", err) + } + + return &keyValueStore{ + manifest: m, + }, nil +} diff --git a/pkg/accesscontrol/kvs/kvs_test.go b/pkg/accesscontrol/kvs/kvs_test.go new file mode 100644 index 00000000000..ca2227e8f11 --- /dev/null +++ b/pkg/accesscontrol/kvs/kvs_test.go @@ -0,0 +1,184 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//nolint:ireturn +package kvs_test + +import ( + "context" + "testing" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" +) + +//nolint:gochecknoglobals +var mockStorer = mockstorer.New() + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} + +func createLs() file.LoadSaver { + return loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), false, redundancy.NONE)) +} + +func keyValuePair(t *testing.T) ([]byte, []byte) { + t.Helper() + return swarm.RandAddress(t).Bytes(), swarm.RandAddress(t).Bytes() +} + +func TestKvs(t *testing.T) { + t.Parallel() + s, err := kvs.New(createLs()) + assert.NoError(t, err) + + key, val := keyValuePair(t) + ctx := context.Background() + + t.Run("Get non-existent key should return error", func(t *testing.T) { + _, err := s.Get(ctx, []byte{1}) + assert.Error(t, err) + }) + + t.Run("Multiple Get with same key, no error", func(t *testing.T) { + err := s.Put(ctx, key, val) + assert.NoError(t, err) + + // get #1 + v, err := s.Get(ctx, key) + assert.NoError(t, err) + assert.Equal(t, val, v) + // get #2 + v, err = s.Get(ctx, key) + assert.NoError(t, err) + assert.Equal(t, val, v) + }) + + t.Run("Get should return value equal to put value", func(t *testing.T) { + var ( + key1 = []byte{1} + key2 = []byte{2} + key3 = []byte{3} + ) + testCases := []struct { + name string + key []byte + val []byte + }{ + { + name: "Test key = 1", + key: key1, + val: []byte{11}, + }, + { + name: "Test key = 2", + key: key2, + val: []byte{22}, + }, + { + name: "Test overwrite key = 1", + key: key1, + val: []byte{111}, + }, + { + name: "Test key = 3", + key: key3, + val: []byte{33}, + }, + { + name: "Test key = 3 with same value", + key: key3, + val: []byte{33}, + }, + { + name: "Test key = 3 with value for key1", + key: key3, + val: []byte{11}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := s.Put(ctx, tc.key, tc.val) + assert.NoError(t, err) + retVal, err := s.Get(ctx, tc.key) + assert.NoError(t, err) + assert.Equal(t, tc.val, retVal) + }) + } + }) +} + +func TestKvs_Save(t *testing.T) { + t.Parallel() + ctx := context.Background() + + key1, val1 := keyValuePair(t) + key2, val2 := keyValuePair(t) + t.Run("Save empty KVS return error", func(t *testing.T) { + s, err := kvs.New(createLs()) + assert.NoError(t, err) + _, err = s.Save(ctx) + assert.ErrorIs(t, err, kvs.ErrNothingToSave) + }) + t.Run("Save not empty KVS return valid swarm address", func(t *testing.T) { + s, err := kvs.New(createLs()) + assert.NoError(t, err) + err = s.Put(ctx, key1, val1) + assert.NoError(t, err) + ref, err := s.Save(ctx) + assert.NoError(t, err) + assert.True(t, ref.IsValidNonEmpty()) + }) + t.Run("Save KVS with one item, no error, pre-save value exist", func(t *testing.T) { + ls := createLs() + s1, err := kvs.New(ls) + assert.NoError(t, err) + + err = s1.Put(ctx, key1, val1) + assert.NoError(t, err) + + ref, err := s1.Save(ctx) + assert.NoError(t, err) + + s2, err := kvs.NewReference(ls, ref) + assert.NoError(t, err) + + val, err := s2.Get(ctx, key1) + assert.NoError(t, err) + assert.Equal(t, val1, val) + }) + t.Run("Save KVS and add one item, no error, after-save value exist", func(t *testing.T) { + ls := createLs() + + kvs1, err := kvs.New(ls) + assert.NoError(t, err) + + err = kvs1.Put(ctx, key1, val1) + assert.NoError(t, err) + ref, err := kvs1.Save(ctx) + assert.NoError(t, err) + + // New KVS + kvs2, err := kvs.NewReference(ls, ref) + assert.NoError(t, err) + err = kvs2.Put(ctx, key2, val2) + assert.NoError(t, err) + + val, err := kvs2.Get(ctx, key2) + assert.NoError(t, err) + assert.Equal(t, val2, val) + }) +} diff --git a/pkg/accesscontrol/kvs/mock/kvs.go b/pkg/accesscontrol/kvs/mock/kvs.go new file mode 100644 index 00000000000..0d5b1eb8569 --- /dev/null +++ b/pkg/accesscontrol/kvs/mock/kvs.go @@ -0,0 +1,78 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package mock provides an in-memory key-value store implementation. +package mock + +import ( + "context" + "encoding/hex" + "sync" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +var lock = &sync.Mutex{} + +type single struct { + memoryMock map[string]map[string][]byte +} + +var singleInMemorySwarm *single + +func getInMemorySwarm() *single { + if singleInMemorySwarm == nil { + lock.Lock() + defer lock.Unlock() + if singleInMemorySwarm == nil { + singleInMemorySwarm = &single{ + memoryMock: make(map[string]map[string][]byte), + } + } + } + return singleInMemorySwarm +} + +func getMemory() map[string]map[string][]byte { + ch := make(chan *single) + go func() { + ch <- getInMemorySwarm() + }() + mem := <-ch + return mem.memoryMock +} + +type mockKeyValueStore struct { + address swarm.Address +} + +var _ kvs.KeyValueStore = (*mockKeyValueStore)(nil) + +func (m *mockKeyValueStore) Get(_ context.Context, key []byte) ([]byte, error) { + mem := getMemory() + val := mem[m.address.String()][hex.EncodeToString(key)] + return val, nil +} + +func (m *mockKeyValueStore) Put(_ context.Context, key []byte, value []byte) error { + mem := getMemory() + if _, ok := mem[m.address.String()]; !ok { + mem[m.address.String()] = make(map[string][]byte) + } + mem[m.address.String()][hex.EncodeToString(key)] = value + return nil +} + +func (m *mockKeyValueStore) Save(ctx context.Context) (swarm.Address, error) { + return m.address, nil +} + +func New() kvs.KeyValueStore { + return &mockKeyValueStore{address: swarm.EmptyAddress} +} + +func NewReference(address swarm.Address) kvs.KeyValueStore { + return &mockKeyValueStore{address: address} +} diff --git a/pkg/accesscontrol/mock/controller.go b/pkg/accesscontrol/mock/controller.go new file mode 100644 index 00000000000..efbe53c6013 --- /dev/null +++ b/pkg/accesscontrol/mock/controller.go @@ -0,0 +1,205 @@ +// Copyright 2020 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package mock provides a mock implementation for the +// access control functionalities. +// +//nolint:ireturn +package mock + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "fmt" + "time" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "golang.org/x/crypto/sha3" +) + +type mockController struct { + historyMap map[string]accesscontrol.History + refMap map[string]swarm.Address + acceptAll bool + publisher string + encrypter encryption.Interface + ls file.LoadSaver +} + +type optionFunc func(*mockController) + +// Option is an option passed to a mock accesscontrol Controller. +type Option interface { + apply(*mockController) +} + +func (f optionFunc) apply(r *mockController) { f(r) } + +// New creates a new mock accesscontrol Controller. +func New(o ...Option) accesscontrol.Controller { + storer := mockstorer.New() + m := &mockController{ + historyMap: make(map[string]accesscontrol.History), + refMap: make(map[string]swarm.Address), + publisher: "", + encrypter: encryption.New(encryption.Key("b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b"), 0, 0, sha3.NewLegacyKeccak256), + ls: loadsave.New(storer.ChunkStore(), storer.Cache(), requestPipelineFactory(context.Background(), storer.Cache(), false, redundancy.NONE)), + } + for _, v := range o { + v.apply(m) + } + + return m +} + +// WithAcceptAll sets the mock to return fixed references on every call to DownloadHandler. +func WithAcceptAll() Option { + return optionFunc(func(m *mockController) { m.acceptAll = true }) +} + +// WithHistory sets the mock to use the given history reference. +func WithHistory(h accesscontrol.History, ref string) Option { + return optionFunc(func(m *mockController) { + m.historyMap = map[string]accesscontrol.History{ref: h} + }) +} + +// WithPublisher sets the mock to use the given reference as the publisher address. +func WithPublisher(ref string) Option { + return optionFunc(func(m *mockController) { + m.publisher = ref + m.encrypter = encryption.New(encryption.Key(ref), 0, 0, sha3.NewLegacyKeccak256) + }) +} + +func (m *mockController) DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { + if m.acceptAll { + return swarm.ParseHexAddress("36e6c1bbdfee6ac21485d5f970479fd1df458d36df9ef4e8179708ed46da557f") + } + + publicKeyBytes := crypto.EncodeSecp256k1PublicKey(publisher) + p := hex.EncodeToString(publicKeyBytes) + if m.publisher != "" && m.publisher != p { + return swarm.ZeroAddress, accesscontrol.ErrInvalidPublicKey + } + + h, exists := m.historyMap[historyRootHash.String()] + if !exists { + return swarm.ZeroAddress, accesscontrol.ErrNotFound + } + entry, err := h.Lookup(ctx, timestamp) + if err != nil { + return swarm.ZeroAddress, err + } + kvsRef := entry.Reference() + if kvsRef.IsZero() { + return swarm.ZeroAddress, err + } + return m.refMap[encryptedRef.String()], nil +} + +func (m *mockController) UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { + historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") + kvsRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") + if m.acceptAll { + encryptedRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") + return kvsRef, historyRef, encryptedRef, nil + } + var ( + h accesscontrol.History + exists bool + ) + + publicKeyBytes := crypto.EncodeSecp256k1PublicKey(publisher) + p := hex.EncodeToString(publicKeyBytes) + if m.publisher != "" && m.publisher != p { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, accesscontrol.ErrInvalidPublicKey + } + + now := time.Now().Unix() + if !historyRootHash.IsZero() { + historyRef = historyRootHash + h, exists = m.historyMap[historyRef.String()] + if !exists { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, accesscontrol.ErrNotFound + } + entry, err := h.Lookup(ctx, now) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + kvsRef := entry.Reference() + if kvsRef.IsZero() { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("kvs not found") + } + } else { + h, _ = accesscontrol.NewHistory(m.ls) + _ = h.Add(ctx, kvsRef, &now, nil) + historyRef, _ = h.Store(ctx) + m.historyMap[historyRef.String()] = h + } + + encryptedRef, _ := m.encrypter.Encrypt(reference.Bytes()) + m.refMap[(hex.EncodeToString(encryptedRef))] = reference + return kvsRef, historyRef, swarm.NewAddress(encryptedRef), nil +} + +func (m *mockController) Close() error { + return nil +} + +func (m *mockController) UpdateHandler(_ context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { + if historyref.Equal(swarm.EmptyAddress) || encryptedglref.Equal(swarm.EmptyAddress) { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, accesscontrol.ErrNotFound + } + historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") + glRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") + eglRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") + actref, _ := swarm.ParseHexAddress("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") + return glRef, eglRef, historyRef, actref, nil +} + +func (m *mockController) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { + if m.publisher == "" { + return nil, fmt.Errorf("granteelist not found") + } + keys := []string{ + "a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa", + "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb", + "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfc", + } + pubkeys := make([]*ecdsa.PublicKey, 0, len(keys)) + for i := range keys { + data, err := hex.DecodeString(keys[i]) + if err != nil { + panic(err) + } + + privKey, err := crypto.DecodeSecp256k1PrivateKey(data) + pubKey := privKey.PublicKey + if err != nil { + panic(err) + } + pubkeys = append(pubkeys, &pubKey) + } + return pubkeys, nil +} + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} + +var _ accesscontrol.Controller = (*mockController)(nil) diff --git a/pkg/accesscontrol/mock/grantee.go b/pkg/accesscontrol/mock/grantee.go new file mode 100644 index 00000000000..6a8f01c87cb --- /dev/null +++ b/pkg/accesscontrol/mock/grantee.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mock + +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +type GranteeListMock interface { + Add(publicKeys []*ecdsa.PublicKey) error + Remove(removeList []*ecdsa.PublicKey) error + Get() []*ecdsa.PublicKey + Save() (swarm.Address, error) +} + +type GranteeListStructMock struct { + grantees []*ecdsa.PublicKey +} + +func (g *GranteeListStructMock) Get() []*ecdsa.PublicKey { + grantees := g.grantees + keys := make([]*ecdsa.PublicKey, len(grantees)) + copy(keys, grantees) + return keys +} + +func (g *GranteeListStructMock) Add(addList []*ecdsa.PublicKey) error { + g.grantees = append(g.grantees, addList...) + return nil +} + +func (g *GranteeListStructMock) Remove(removeList []*ecdsa.PublicKey) error { + for _, remove := range removeList { + for i, grantee := range g.grantees { + if *grantee == *remove { + g.grantees[i] = g.grantees[len(g.grantees)-1] + g.grantees = g.grantees[:len(g.grantees)-1] + } + } + } + + return nil +} + +func (g *GranteeListStructMock) Save() (swarm.Address, error) { + return swarm.EmptyAddress, nil +} + +func NewGranteeList() *GranteeListStructMock { + return &GranteeListStructMock{grantees: []*ecdsa.PublicKey{}} +} diff --git a/pkg/accesscontrol/mock/session.go b/pkg/accesscontrol/mock/session.go new file mode 100644 index 00000000000..6e5d43a9576 --- /dev/null +++ b/pkg/accesscontrol/mock/session.go @@ -0,0 +1,44 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mock + +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/keystore" +) + +type SessionMock struct { + KeyFunc func(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) + key *ecdsa.PrivateKey +} + +func (s *SessionMock) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { + if s.KeyFunc == nil { + return nil, nil + } + return s.KeyFunc(publicKey, nonces) +} + +func NewSessionMock(key *ecdsa.PrivateKey) *SessionMock { + return &SessionMock{key: key} +} + +func NewFromKeystore( + ks keystore.Service, + tag, + password string, + keyFunc func(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error), +) *SessionMock { + key, created, err := ks.Key(tag, password, crypto.EDGSecp256_K1) + if !created || err != nil { + return nil + } + return &SessionMock{ + key: key, + KeyFunc: keyFunc, + } +} diff --git a/pkg/accesscontrol/session.go b/pkg/accesscontrol/session.go new file mode 100644 index 00000000000..a571337c17d --- /dev/null +++ b/pkg/accesscontrol/session.go @@ -0,0 +1,66 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol + +import ( + "crypto/ecdsa" + "errors" + "fmt" + + "github.com/ethersphere/bee/v2/pkg/crypto" +) + +var ( + // ErrInvalidPublicKey is an error that is returned when a public key is nil. + ErrInvalidPublicKey = errors.New("invalid public key") + // ErrSecretKeyInfinity is an error that is returned when the shared secret is a point at infinity. + ErrSecretKeyInfinity = errors.New("shared secret is point at infinity") +) + +// Session represents an interface for a Diffie-Hellmann key derivation +type Session interface { + // Key returns a derived key for each nonce. + Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) +} + +var _ Session = (*SessionStruct)(nil) + +// SessionStruct represents a session with an access control key. +type SessionStruct struct { + key *ecdsa.PrivateKey +} + +// Key returns a derived key for each nonce. +func (s *SessionStruct) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { + if publicKey == nil { + return nil, ErrInvalidPublicKey + } + x, y := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, s.key.D.Bytes()) + if x == nil || y == nil { + return nil, ErrSecretKeyInfinity + } + + if len(nonces) == 0 { + return [][]byte{(*x).Bytes()}, nil + } + + keys := make([][]byte, 0, len(nonces)) + for _, nonce := range nonces { + key, err := crypto.LegacyKeccak256(append(x.Bytes(), nonce...)) + if err != nil { + return nil, fmt.Errorf("failed to get Keccak256 hash: %w", err) + } + keys = append(keys, key) + } + + return keys, nil +} + +// NewDefaultSession creates a new session from a private key. +func NewDefaultSession(key *ecdsa.PrivateKey) *SessionStruct { + return &SessionStruct{ + key: key, + } +} diff --git a/pkg/accesscontrol/session_test.go b/pkg/accesscontrol/session_test.go new file mode 100644 index 00000000000..a8dc4628f83 --- /dev/null +++ b/pkg/accesscontrol/session_test.go @@ -0,0 +1,146 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package accesscontrol_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "testing" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" + "github.com/ethersphere/bee/v2/pkg/crypto" + memkeystore "github.com/ethersphere/bee/v2/pkg/keystore/mem" + "github.com/stretchr/testify/assert" +) + +func mockKeyFunc(*ecdsa.PublicKey, [][]byte) ([][]byte, error) { + return [][]byte{{1}}, nil +} + +func TestSessionNewDefaultSession(t *testing.T) { + pk, err := crypto.GenerateSecp256k1Key() + assertNoError(t, "generating private key", err) + si := accesscontrol.NewDefaultSession(pk) + if si == nil { + assert.FailNow(t, "Session instance is nil") + } +} + +func TestSessionNewFromKeystore(t *testing.T) { + ks := memkeystore.New() + si := mock.NewFromKeystore(ks, "tag", "password", mockKeyFunc) + if si == nil { + assert.FailNow(t, "Session instance is nil") + } +} + +func TestSessionKey(t *testing.T) { + t.Parallel() + + key1, err := crypto.GenerateSecp256k1Key() + assertNoError(t, "key1 GenerateSecp256k1Key", err) + si1 := accesscontrol.NewDefaultSession(key1) + + key2, err := crypto.GenerateSecp256k1Key() + assertNoError(t, "key2 GenerateSecp256k1Key", err) + si2 := accesscontrol.NewDefaultSession(key2) + + nonces := make([][]byte, 2) + for i := range nonces { + if _, err := io.ReadFull(rand.Reader, nonces[i]); err != nil { + assert.FailNow(t, err.Error()) + } + } + + keys1, err := si1.Key(&key2.PublicKey, nonces) + assertNoError(t, "", err) + keys2, err := si2.Key(&key1.PublicKey, nonces) + assertNoError(t, "", err) + + if !bytes.Equal(keys1[0], keys2[0]) { + assert.FailNowf(t, fmt.Sprintf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])), "") + } + if !bytes.Equal(keys1[1], keys2[1]) { + assert.FailNowf(t, fmt.Sprintf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[1]), hex.EncodeToString(keys2[1])), "") + } +} + +func TestSessionKeyWithoutNonces(t *testing.T) { + t.Parallel() + + key1, err := crypto.GenerateSecp256k1Key() + assertNoError(t, "key1 GenerateSecp256k1Key", err) + si1 := accesscontrol.NewDefaultSession(key1) + + key2, err := crypto.GenerateSecp256k1Key() + assertNoError(t, "key2 GenerateSecp256k1Key", err) + si2 := accesscontrol.NewDefaultSession(key2) + + keys1, err := si1.Key(&key2.PublicKey, nil) + assertNoError(t, "session1 key", err) + keys2, err := si2.Key(&key1.PublicKey, nil) + assertNoError(t, "session2 key", err) + + if !bytes.Equal(keys1[0], keys2[0]) { + assert.FailNowf(t, fmt.Sprintf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])), "") + } +} + +func TestSessionKeyFromKeystore(t *testing.T) { + t.Parallel() + + ks := memkeystore.New() + tag1 := "tag1" + tag2 := "tag2" + password1 := "password1" + password2 := "password2" + + si1 := mock.NewFromKeystore(ks, tag1, password1, mockKeyFunc) + exists, err := ks.Exists(tag1) + assertNoError(t, "", err) + if !exists { + assert.FailNow(t, "Key1 should exist") + + } + key1, created, err := ks.Key(tag1, password1, crypto.EDGSecp256_K1) + assertNoError(t, "", err) + if created { + assert.FailNow(t, "Key1 should not be created") + + } + + si2 := mock.NewFromKeystore(ks, tag2, password2, mockKeyFunc) + exists, err = ks.Exists(tag2) + assertNoError(t, "", err) + if !exists { + assert.FailNow(t, "Key2 should exist") + } + key2, created, err := ks.Key(tag2, password2, crypto.EDGSecp256_K1) + assertNoError(t, "", err) + if created { + assert.FailNow(t, "Key2 should not be created") + } + + nonces := make([][]byte, 1) + for i := range nonces { + if _, err := io.ReadFull(rand.Reader, nonces[i]); err != nil { + assert.FailNow(t, err.Error()) + } + } + + keys1, err := si1.Key(&key2.PublicKey, nonces) + assertNoError(t, "", err) + keys2, err := si2.Key(&key1.PublicKey, nonces) + assertNoError(t, "", err) + + if !bytes.Equal(keys1[0], keys2[0]) { + assert.FailNowf(t, fmt.Sprintf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])), "") + } +} diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go new file mode 100644 index 00000000000..8767eb5a823 --- /dev/null +++ b/pkg/api/accesscontrol.go @@ -0,0 +1,565 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package api + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/jsonhttp" + "github.com/ethersphere/bee/v2/pkg/postage" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/gorilla/mux" +) + +type addressKey struct{} + +const granteeListEncrypt = true + +// getAddressFromContext is a helper function to extract the address from the context. +func getAddressFromContext(ctx context.Context) swarm.Address { + v, ok := ctx.Value(addressKey{}).(swarm.Address) + if ok { + return v + } + return swarm.ZeroAddress +} + +// setAddressInContext sets the swarm address in the context. +func setAddressInContext(ctx context.Context, address swarm.Address) context.Context { + return context.WithValue(ctx, addressKey{}, address) +} + +// GranteesPatchRequest represents a request to patch the list of grantees. +type GranteesPatchRequest struct { + // Addlist contains the list of grantees to add. + Addlist []string `json:"add"` + + // Revokelist contains the list of grantees to revoke. + Revokelist []string `json:"revoke"` +} + +// GranteesPatchResponse represents the response structure for patching grantees. +type GranteesPatchResponse struct { + // Reference represents the swarm address. + Reference swarm.Address `json:"ref"` + // HistoryReference represents the reference to the history of an access control entry. + HistoryReference swarm.Address `json:"historyref"` +} + +// GranteesPostRequest represents the request structure for adding grantees. +type GranteesPostRequest struct { + // GranteeList represents the list of grantees to be saves on Swarm. + GranteeList []string `json:"grantees"` +} + +// GranteesPostResponse represents the response structure for adding grantees. +type GranteesPostResponse struct { + // Reference represents the saved grantee list Swarm address. + Reference swarm.Address `json:"ref"` + // HistoryReference represents the reference to the history of an access control entry. + HistoryReference swarm.Address `json:"historyref"` +} + +// GranteesPatch represents a structure for modifying the list of grantees. +type GranteesPatch struct { + // Addlist is a list of ecdsa.PublicKeys to be added to a grantee list. + Addlist []*ecdsa.PublicKey + // Revokelist is a list of ecdsa.PublicKeys to be removed from a grantee list + Revokelist []*ecdsa.PublicKey +} + +// actDecryptionHandler is a middleware that looks up and decrypts the given address, +// if the act headers are present. +func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("act_decryption_handler").Build() + paths := struct { + Address swarm.Address `map:"address,resolve" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + Timestamp *int64 `map:"Swarm-Act-Timestamp"` + Publisher *ecdsa.PublicKey `map:"Swarm-Act-Publisher"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + Cache *bool `map:"Swarm-Cache"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + // Try to download the file wihtout decryption, if the act headers are not present + if headers.Publisher == nil || headers.HistoryAddress == nil { + h.ServeHTTP(w, r) + return + } + + timestamp := time.Now().Unix() + if headers.Timestamp != nil { + timestamp = *headers.Timestamp + } + + cache := true + if headers.Cache != nil { + cache = *headers.Cache + } + ctx := r.Context() + ls := loadsave.NewReadonly(s.storer.Download(cache)) + reference, err := s.accesscontrol.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp) + if err != nil { + logger.Debug("access control download failed", "error", err) + logger.Error(nil, "access control download failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidTimestamp): + jsonhttp.BadRequest(w, "invalid timestamp") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActDownload) + } + return + } + h.ServeHTTP(w, r.WithContext(setAddressInContext(ctx, reference))) + }) + } +} + +// actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key, +// uploads the encrypted reference, history and kvs to the store. +func (s *Service) actEncryptionHandler( + ctx context.Context, + w http.ResponseWriter, + putter storer.PutterSession, + reference swarm.Address, + historyRootHash swarm.Address, +) (swarm.Address, error) { + publisherPublicKey := &s.publicKey + ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + storageReference, historyReference, encryptedReference, err := s.accesscontrol.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash) + if err != nil { + return swarm.ZeroAddress, err + } + // only need to upload history and kvs if a new history is created, + // meaning that the publisher uploaded to the history for the first time + if !historyReference.Equal(historyRootHash) { + err = putter.Done(storageReference) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("done split key-value store failed: %w", err) + } + err = putter.Done(historyReference) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("done split history failed: %w", err) + } + } + + w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String()) + return encryptedReference, nil +} + +// actListGranteesHandler is a middleware that decrypts the given address and returns the list of grantees, +// only the publisher is authorized to access the list. +func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("act_list_grantees_handler").Build() + paths := struct { + GranteesAddress swarm.Address `map:"address,resolve" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + Cache *bool `map:"Swarm-Cache"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + cache := true + if headers.Cache != nil { + cache = *headers.Cache + } + publisher := &s.publicKey + ls := loadsave.NewReadonly(s.storer.Download(cache)) + grantees, err := s.accesscontrol.Get(r.Context(), ls, publisher, paths.GranteesAddress) + if err != nil { + logger.Debug("could not get grantees", "error", err) + logger.Error(nil, "could not get grantees") + jsonhttp.NotFound(w, "granteelist not found") + return + } + granteeSlice := make([]string, len(grantees)) + for i, grantee := range grantees { + granteeSlice[i] = hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(grantee)) + } + jsonhttp.OK(w, granteeSlice) +} + +// actGrantRevokeHandler is a middleware that makes updates to the list of grantees, +// only the publisher is authorized to perform this action. +func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("act_grant_revoke_handler").Build() + + if r.Body == http.NoBody { + logger.Error(nil, "request has no body") + jsonhttp.BadRequest(w, errInvalidRequest) + return + } + + paths := struct { + GranteesAddress swarm.Address `map:"address,resolve" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address" validate:"required"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + historyAddress := swarm.ZeroAddress + if headers.HistoryAddress != nil { + historyAddress = *headers.HistoryAddress + } + + var ( + tag uint64 + err error + deferred = defaultUploadMethod(headers.Deferred) + ) + + if deferred || headers.Pin { + tag, err = s.getOrCreateSessionID(headers.SwarmTag) + if err != nil { + logger.Debug("get or create tag failed", "error", err) + logger.Error(nil, "get or create tag failed") + switch { + case errors.Is(err, storage.ErrNotFound): + jsonhttp.NotFound(w, "tag not found") + default: + jsonhttp.InternalServerError(w, "cannot get or create tag") + } + return + } + } + + body, err := io.ReadAll(r.Body) + if err != nil { + if jsonhttp.HandleBodyReadError(err, w) { + return + } + logger.Debug("read request body failed", "error", err) + logger.Error(nil, "read request body failed") + jsonhttp.InternalServerError(w, "cannot read request") + return + } + + gpr := GranteesPatchRequest{} + if len(body) > 0 { + err = json.Unmarshal(body, &gpr) + if err != nil { + logger.Debug("unmarshal body failed", "error", err) + logger.Error(nil, "unmarshal body failed") + jsonhttp.InternalServerError(w, "error unmarshaling request body") + return + } + } + + grantees := GranteesPatch{} + parsedAddlist, err := parseKeys(gpr.Addlist) + if err != nil { + logger.Debug("add list key parse failed", "error", err) + logger.Error(nil, "add list key parse failed") + jsonhttp.BadRequest(w, "invalid add list") + return + } + grantees.Addlist = append(grantees.Addlist, parsedAddlist...) + + parsedRevokelist, err := parseKeys(gpr.Revokelist) + if err != nil { + logger.Debug("revoke list key parse failed", "error", err) + logger.Error(nil, "revoke list key parse failed") + jsonhttp.BadRequest(w, "invalid revoke list") + return + } + grantees.Revokelist = append(grantees.Revokelist, parsedRevokelist...) + + ctx := r.Context() + putter, err := s.newStamperPutter(ctx, putterOptions{ + BatchID: headers.BatchID, + TagID: tag, + Pin: headers.Pin, + Deferred: deferred, + }) + if err != nil { + logger.Debug("putter failed", "error", err) + logger.Error(nil, "putter failed") + switch { + case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): + jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") + case errors.Is(err, postage.ErrNotFound): + jsonhttp.NotFound(w, "batch with id not found") + case errors.Is(err, errInvalidPostageBatch): + jsonhttp.BadRequest(w, "invalid batch id") + case errors.Is(err, errUnsupportedDevNodeOperation): + jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) + default: + jsonhttp.BadRequest(w, nil) + } + return + } + + granteeref := paths.GranteesAddress + publisher := &s.publicKey + ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) + granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) + if err != nil { + logger.Debug("failed to update grantee list", "error", err) + logger.Error(nil, "failed to update grantee list") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrNoGranteeFound): + jsonhttp.BadRequest(w, "remove from empty grantee list") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActGranteeList) + } + return + } + + err = putter.Done(actref) + if err != nil { + logger.Debug("done split act failed", "error", err) + logger.Error(nil, "done split act failed") + jsonhttp.InternalServerError(w, "done split act failed") + return + } + + err = putter.Done(historyref) + if err != nil { + logger.Debug("done split history failed", "error", err) + logger.Error(nil, "done split history failed") + jsonhttp.InternalServerError(w, "done split history failed") + return + } + + err = putter.Done(granteeref) + if err != nil { + logger.Debug("done split grantees failed", "error", err) + logger.Error(nil, "done split grantees failed") + jsonhttp.InternalServerError(w, "done split grantees failed") + return + } + + jsonhttp.OK(w, GranteesPatchResponse{ + Reference: encryptedglref, + HistoryReference: historyref, + }) +} + +// actCreateGranteesHandler is a middleware that creates a new list of grantees, +// only the publisher is authorized to perform this action. +func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("acthandler").Build() + + if r.Body == http.NoBody { + logger.Error(nil, "request has no body") + jsonhttp.BadRequest(w, errInvalidRequest) + return + } + + headers := struct { + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + historyAddress := swarm.ZeroAddress + if headers.HistoryAddress != nil { + historyAddress = *headers.HistoryAddress + } + + var ( + tag uint64 + err error + deferred = defaultUploadMethod(headers.Deferred) + ) + + if deferred || headers.Pin { + tag, err = s.getOrCreateSessionID(headers.SwarmTag) + if err != nil { + logger.Debug("get or create tag failed", "error", err) + logger.Error(nil, "get or create tag failed") + switch { + case errors.Is(err, storage.ErrNotFound): + jsonhttp.NotFound(w, "tag not found") + default: + jsonhttp.InternalServerError(w, "cannot get or create tag") + } + return + } + } + + body, err := io.ReadAll(r.Body) + if err != nil { + if jsonhttp.HandleBodyReadError(err, w) { + return + } + logger.Debug("read request body failed", "error", err) + logger.Error(nil, "read request body failed") + jsonhttp.InternalServerError(w, "cannot read request") + return + } + + gpr := GranteesPostRequest{} + if len(body) > 0 { + err = json.Unmarshal(body, &gpr) + if err != nil { + logger.Debug("unmarshal body failed", "error", err) + logger.Error(nil, "unmarshal body failed") + jsonhttp.InternalServerError(w, "error unmarshaling request body") + return + } + } + + list, err := parseKeys(gpr.GranteeList) + if err != nil { + logger.Debug("create list key parse failed", "error", err) + logger.Error(nil, "create list key parse failed") + jsonhttp.BadRequest(w, "invalid grantee list") + return + } + + ctx := r.Context() + putter, err := s.newStamperPutter(ctx, putterOptions{ + BatchID: headers.BatchID, + TagID: tag, + Pin: headers.Pin, + Deferred: deferred, + }) + if err != nil { + logger.Debug("putter failed", "error", err) + logger.Error(nil, "putter failed") + switch { + case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): + jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") + case errors.Is(err, postage.ErrNotFound): + jsonhttp.NotFound(w, "batch with id not found") + case errors.Is(err, errInvalidPostageBatch): + jsonhttp.BadRequest(w, "invalid batch id") + case errors.Is(err, errUnsupportedDevNodeOperation): + jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) + default: + jsonhttp.BadRequest(w, nil) + } + return + } + + publisher := &s.publicKey + ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) + granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) + if err != nil { + logger.Debug("failed to create grantee list", "error", err) + logger.Error(nil, "failed to create grantee list") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActGranteeList) + } + return + } + + err = putter.Done(actref) + if err != nil { + logger.Debug("done split act failed", "error", err) + logger.Error(nil, "done split act failed") + jsonhttp.InternalServerError(w, "done split act failed") + return + } + + err = putter.Done(historyref) + if err != nil { + logger.Debug("done split history failed", "error", err) + logger.Error(nil, "done split history failed") + jsonhttp.InternalServerError(w, "done split history failed") + return + } + + err = putter.Done(granteeref) + if err != nil { + logger.Debug("done split grantees failed", "error", err) + logger.Error(nil, "done split grantees failed") + jsonhttp.InternalServerError(w, "done split grantees failed") + return + } + + jsonhttp.Created(w, GranteesPostResponse{ + Reference: encryptedglref, + HistoryReference: historyref, + }) +} + +func parseKeys(list []string) ([]*ecdsa.PublicKey, error) { + parsedList := make([]*ecdsa.PublicKey, 0, len(list)) + for _, g := range list { + h, err := hex.DecodeString(g) + if err != nil { + return []*ecdsa.PublicKey{}, fmt.Errorf("failed to decode grantee: %w", err) + } + k, err := btcec.ParsePubKey(h) + if err != nil { + return []*ecdsa.PublicKey{}, fmt.Errorf("failed to parse grantee public key: %w", err) + } + parsedList = append(parsedList, k.ToECDSA()) + } + + return parsedList, nil +} diff --git a/pkg/api/accesscontrol_test.go b/pkg/api/accesscontrol_test.go new file mode 100644 index 00000000000..e8662a48118 --- /dev/null +++ b/pkg/api/accesscontrol_test.go @@ -0,0 +1,1041 @@ +// Copyright 2020 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package api_test + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "testing" + "time" + + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + mockac "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" + "github.com/ethersphere/bee/v2/pkg/api" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/jsonhttp" + "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest" + "github.com/ethersphere/bee/v2/pkg/log" + mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock" + testingsoc "github.com/ethersphere/bee/v2/pkg/soc/testing" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "gitlab.com/nolash/go-mockbytes" +) + +//nolint:ireturn +func prepareHistoryFixture(storer api.Storer) (accesscontrol.History, swarm.Address) { + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, redundancy.NONE)) + + h, _ := accesscontrol.NewHistory(ls) + + testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + _ = h.Add(ctx, testActRef1, &firstTime, nil) + + testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) + secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + _ = h.Add(ctx, testActRef2, &secondTime, nil) + + testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) + thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + _ = h.Add(ctx, testActRef3, &thirdTime, nil) + + testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) + fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + _ = h.Add(ctx, testActRef4, &fourthTime, nil) + + testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) + fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + _ = h.Add(ctx, testActRef5, &fifthTime, nil) + + ref, _ := h.Store(ctx) + return h, ref +} + +// nolint:paralleltest,tparallel +// TestAccessLogicEachEndpointWithAct [positive tests]: +// On each endpoint: upload w/ "Swarm-Act" header then download and check the decrypted data +func TestAccessLogicEachEndpointWithAct(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + testfile = "testfile1" + storerMock = mockstorer.New() + logger = log.Noop + now = time.Now().Unix() + chunk = swarm.NewChunk( + swarm.MustParseHexAddress("0025737be11979e91654dffd2be817ac1e52a2dadb08c97a7cef12f937e707bc"), + []byte{72, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 149, 179, 31, 244, 146, 247, 129, 123, 132, 248, 215, 77, 44, 47, 91, 248, 229, 215, 89, 156, 210, 243, 3, 110, 204, 74, 101, 119, 53, 53, 145, 188, 193, 153, 130, 197, 83, 152, 36, 140, 150, 209, 191, 214, 193, 4, 144, 121, 32, 45, 205, 220, 59, 227, 28, 43, 161, 51, 108, 14, 106, 180, 135, 2}, + ) + g = mockbytes.New(0, mockbytes.MockTypeStandard).WithModulus(255) + bytedata, _ = g.SequentialBytes(swarm.ChunkSize * 2) + tag, _ = storerMock.NewSession() + sch = testingsoc.GenerateMockSOCWithKey(t, []byte("foo"), pk) + dirdata = []byte("Lorem ipsum dolor sit amet") + socResource = func(owner, id, sig string) string { return fmt.Sprintf("/soc/%s/%s?sig=%s", owner, id, sig) } + ) + + tc := []struct { + name string + downurl string + upurl string + exphash string + data io.Reader + expdata []byte + contenttype string + resp struct { + Reference swarm.Address `json:"reference"` + } + }{ + { + name: "bzz", + upurl: "/bzz?name=sample.html", + downurl: "/bzz", + exphash: "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade", + resp: api.BzzUploadResponse{Reference: swarm.MustParseHexAddress("a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade")}, + data: strings.NewReader(testfile), + expdata: []byte(testfile), + contenttype: "text/html; charset=utf-8", + }, + { + name: "bzz-dir", + upurl: "/bzz?name=ipsum/lorem.txt", + downurl: "/bzz", + exphash: "6561b2a744d2a8f276270585da22e092c07c56624af83ac9969d52b54e87cee6/ipsum/lorem.txt", + resp: api.BzzUploadResponse{Reference: swarm.MustParseHexAddress("6561b2a744d2a8f276270585da22e092c07c56624af83ac9969d52b54e87cee6")}, + data: tarFiles(t, []f{ + { + data: dirdata, + name: "lorem.txt", + dir: "ipsum", + header: http.Header{ + api.ContentTypeHeader: {"text/plain; charset=utf-8"}, + }, + }, + }), + expdata: dirdata, + contenttype: api.ContentTypeTar, + }, + { + name: "bytes", + upurl: "/bytes", + downurl: "/bytes", + exphash: "e30da540bb9e1901169977fcf617f28b7f8df4537de978784f6d47491619a630", + resp: api.BytesPostResponse{Reference: swarm.MustParseHexAddress("e30da540bb9e1901169977fcf617f28b7f8df4537de978784f6d47491619a630")}, + data: bytes.NewReader(bytedata), + expdata: bytedata, + contenttype: "application/octet-stream", + }, + { + name: "chunks", + upurl: "/chunks", + downurl: "/chunks", + exphash: "ca8d2d29466e017cba46d383e7e0794d99a141185ec525086037f25fc2093155", + resp: api.ChunkAddressResponse{Reference: swarm.MustParseHexAddress("ca8d2d29466e017cba46d383e7e0794d99a141185ec525086037f25fc2093155")}, + data: bytes.NewReader(chunk.Data()), + expdata: chunk.Data(), + contenttype: "binary/octet-stream", + }, + { + name: "soc", + upurl: socResource(hex.EncodeToString(sch.Owner), hex.EncodeToString(sch.ID), hex.EncodeToString(sch.Signature)), + downurl: "/chunks", + exphash: "b100d7ce487426b17b98ff779fad4f2dd471d04ab1c8949dd2a1a78fe4a1524e", + resp: api.ChunkAddressResponse{Reference: swarm.MustParseHexAddress("b100d7ce487426b17b98ff779fad4f2dd471d04ab1c8949dd2a1a78fe4a1524e")}, + data: bytes.NewReader(sch.WrappedChunk.Data()), + expdata: sch.Chunk().Data(), + contenttype: "binary/octet-stream", + }, + } + + for _, v := range tc { + upTestOpts := []jsonhttptest.Option{ + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmTagHeader, fmt.Sprintf("%d", tag.TagID)), + jsonhttptest.WithRequestBody(v.data), + jsonhttptest.WithExpectedJSONResponse(v.resp), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, v.contenttype), + } + if v.name == "soc" { + upTestOpts = append(upTestOpts, jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "true")) + } else { + upTestOpts = append(upTestOpts, jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader)) + } + expcontenttype := v.contenttype + if v.name == "bzz-dir" { + expcontenttype = "text/plain; charset=utf-8" + upTestOpts = append(upTestOpts, jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True")) + } + t.Run(v.name, func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), + }) + header := jsonhttptest.Request(t, client, http.MethodPost, v.upurl, http.StatusCreated, + upTestOpts..., + ) + + historyRef := header.Get(api.SwarmActHistoryAddressHeader) + jsonhttptest.Request(t, client, http.MethodGet, v.downurl+"/"+v.exphash, http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse(v.expdata), + jsonhttptest.WithExpectedContentLength(len(v.expdata)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, expcontenttype), + ) + + if v.name != "bzz-dir" && v.name != "soc" && v.name != "chunks" { + t.Run("head", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodHead, v.downurl+"/"+v.exphash, http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithRequestBody(v.data), + jsonhttptest.WithExpectedContentLength(len(v.expdata)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, expcontenttype), + ) + }) + } + }) + } +} + +// TestAccessLogicWithoutActHeader [negative tests]: +// 1. upload w/ "Swarm-Act" header then try to dowload w/o the header. +// 2. upload w/o "Swarm-Act" header then try to dowload w/ the header. +// +//nolint:paralleltest,tparallel +func TestAccessLogicWithoutAct(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + now = time.Now().Unix() + ) + + t.Run("upload-w/-act-then-download-w/o-act", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + var ( + testfile = "testfile1" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "address not found or incorrect", + Code: http.StatusNotFound, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + + t.Run("upload-w/o-act-then-download-w/-act", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), + }) + var ( + rootHash = "0cb947ccbc410c43139ba4409d83bf89114cb0d79556a651c06c888cf73f4d7e" + sampleHtml = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(sampleHtml)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(rootHash), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", rootHash)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(rootHash), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "act or history entry not found", + Code: http.StatusNotFound, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} + +// TestAccessLogicInvalidPath [negative test]: Expect Bad request when the path address is invalid. +// +//nolint:paralleltest,tparallel +func TestAccessLogicInvalidPath(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + _, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + now = time.Now().Unix() + ) + + t.Run("invalid-path-params", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), + }) + encryptedRef := "asd" + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid path params", + Reasons: []jsonhttp.Reason{ + { + Field: "address", + Error: api.HexInvalidByteError('s').Error(), + }, + }, + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestAccessLogicHistory tests: +// [positive tests] 1., 2.: uploading a file w/ and w/o history address then downloading it and checking the data. +// [negative test] 3. uploading a file then downloading it with a wrong history address. +// [negative test] 4. uploading a file to a wrong history address. +// [negative test] 5. downloading a file to w/o history address. +func TestAccessLogicHistory(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + now = time.Now().Unix() + ) + + t.Run("empty-history-upload-then-download-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), + }) + var ( + testfile = "testfile1" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + header := jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + historyRef := header.Get(api.SwarmActHistoryAddressHeader) + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("with-history-upload-then-download-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + var ( + encryptedRef = "c611199e1b3674d6bf89a83e518bd16896bf5315109b4a23dcb4682a02d17b97" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("upload-then-download-wrong-history", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + var ( + testfile = "testfile1" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, "fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396"), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "act or history entry not found", + Code: http.StatusNotFound, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + + t.Run("upload-wrong-history", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), + }) + testfile := "testfile1" + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "act or history entry not found", + Code: http.StatusNotFound, + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) + + t.Run("download-w/o-history", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestAccessLogicTimestamp +// [positive test] 1.: uploading a file w/ ACT then download it w/ timestamp and check the data. +// [negative test] 2.: try to download a file w/o timestamp. +func TestAccessLogicTimestamp(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + ) + t.Run("upload-then-download-with-timestamp-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + var ( + thirdTime = time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + encryptedRef = "c611199e1b3674d6bf89a83e518bd16896bf5315109b4a23dcb4682a02d17b97" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(thirdTime, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("download-w/o-timestamp", func(t *testing.T) { + encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + t.Run("download-w/-invalid-timestamp", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + var ( + invalidTime = int64(-1) + encryptedRef = "c611199e1b3674d6bf89a83e518bd16896bf5315109b4a23dcb4682a02d17b97" + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(invalidTime, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: accesscontrol.ErrInvalidTimestamp.Error(), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestAccessLogicPublisher +// [positive test] 1.: uploading a file w/ ACT then download it w/ the publisher address and check the data. +// [negative test] 2.: expect Bad request when the public key is invalid. +// [negative test] 3.: try to download a file w/ an incorrect publisher address. +// [negative test] 3.: try to download a file w/o a publisher address. +func TestAccessLogicPublisher(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + now = time.Now().Unix() + ) + + t.Run("upload-then-download-w/-publisher-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), + }) + var ( + encryptedRef = "a5a26b4915d7ce1622f9ca52252092cf2445f98d359dabaf52588c05911aaf4f" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("upload-then-download-invalid-publickey", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithPublisher(publisher)), + }) + var ( + publickey = "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb" + encryptedRef = "a5a26b4915d7ce1622f9ca52252092cf2445f98d359dabaf52588c05911aaf4f" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + header := jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + historyRef := header.Get(api.SwarmActHistoryAddressHeader) + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publickey), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid header params", + Reasons: []jsonhttp.Reason{ + { + Field: "Swarm-Act-Publisher", + Error: "malformed public key: invalid length: 32", + }, + }, + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) + + t.Run("download-w/-wrong-publisher", func(t *testing.T) { + var ( + downloader = "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), + }) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, downloader), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: accesscontrol.ErrInvalidPublicKey.Error(), + Code: http.StatusBadRequest, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + + t.Run("re-upload-with-invalid-publickey", func(t *testing.T) { + var ( + downloader = "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2" + testfile = "testfile1" + ) + downloaderClient, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithPublisher(downloader)), + }) + + jsonhttptest.Request(t, downloaderClient, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid public key", + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + + }) + + t.Run("download-w/o-publisher", func(t *testing.T) { + encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), + }) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} + +func TestAccessLogicGrantees(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + addr = swarm.RandAddress(t) + client, _, _, _ = newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), + }) + ) + t.Run("get-grantees", func(t *testing.T) { + var ( + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + ) + clientwihtpublisher, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), + }) + expected := []string{ + "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", + "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2", + "032541acf966823bae26c2c16a7102e728ade3e2e29c11a8a17b29d8eb2bd19302", + } + jsonhttptest.Request(t, clientwihtpublisher, http.MethodGet, "/grantee/"+addr.String(), http.StatusOK, + jsonhttptest.WithExpectedJSONResponse(expected), + ) + }) + + t.Run("get-grantees-unauthorized", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodGet, "/grantee/fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396", http.StatusNotFound, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "granteelist not found", + Code: http.StatusNotFound, + }), + ) + }) + t.Run("get-grantees-invalid-address", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodGet, "/grantee/asd", http.StatusBadRequest, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid path params", + Reasons: []jsonhttp.Reason{ + { + Field: "address", + Error: api.HexInvalidByteError('s').Error(), + }, + }, + }), + ) + }) + t.Run("add-revoke-grantees", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + Revokelist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, addr.String()), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("add-revoke-grantees-wrong-history", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + Revokelist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, swarm.EmptyAddress.String()), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "act or history entry not found", + Code: http.StatusNotFound, + }), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("invlaid-add-grantees", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"random-string"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, addr.String()), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "invalid add list", + Code: http.StatusBadRequest, + }), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("invlaid-revoke-grantees", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Revokelist: []string{"random-string"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, addr.String()), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "invalid revoke list", + Code: http.StatusBadRequest, + }), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("add-revoke-grantees-empty-body", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(bytes.NewReader(nil)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "could not validate request", + Code: http.StatusBadRequest, + }), + ) + }) + t.Run("add-grantee-with-history", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, addr.String()), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("add-grantee-without-history", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + + t.Run("create-granteelist", func(t *testing.T) { + body := api.GranteesPostRequest{ + GranteeList: []string{ + "02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4", + "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", + }, + } + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("create-granteelist-without-stamp", func(t *testing.T) { + body := api.GranteesPostRequest{ + GranteeList: []string{ + "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", + }, + } + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusBadRequest, + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("create-granteelist-empty-body", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(bytes.NewReader(nil)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "could not validate request", + Code: http.StatusBadRequest, + }), + ) + }) + t.Run("create-granteelist-invalid-body", func(t *testing.T) { + body := api.GranteesPostRequest{ + GranteeList: []string{"random-string"}, + } + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(bytes.NewReader(nil)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "invalid grantee list", + Code: http.StatusBadRequest, + }), + jsonhttptest.WithJSONRequestBody(body), + ) + }) +} diff --git a/pkg/api/accounting_test.go b/pkg/api/accounting_test.go index 7a807aa811e..c2db87d2cdb 100644 --- a/pkg/api/accounting_test.go +++ b/pkg/api/accounting_test.go @@ -56,7 +56,6 @@ func TestAccountingInfo(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithPeerAccountingFunc(accountingFunc)}, }) @@ -112,7 +111,6 @@ func TestAccountingInfoError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithPeerAccountingFunc(accountingFunc)}, }) diff --git a/pkg/api/api.go b/pkg/api/api.go index 72100cc0d9b..3f72859a041 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -11,7 +11,6 @@ import ( "crypto/ecdsa" "encoding/base64" "encoding/hex" - "encoding/json" "errors" "fmt" "io" @@ -27,8 +26,8 @@ import ( "unicode/utf8" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/accounting" - "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/pipeline" @@ -85,6 +84,10 @@ const ( SwarmRedundancyFallbackModeHeader = "Swarm-Redundancy-Fallback-Mode" SwarmChunkRetrievalTimeoutHeader = "Swarm-Chunk-Retrieval-Timeout" SwarmLookAheadBufferSizeHeader = "Swarm-Lookahead-Buffer-Size" + SwarmActHeader = "Swarm-Act" + SwarmActTimestampHeader = "Swarm-Act-Timestamp" + SwarmActPublisherHeader = "Swarm-Act-Publisher" + SwarmActHistoryAddressHeader = "Swarm-Act-History-Address" ImmutableHeader = "Immutable" GasPriceHeader = "Gas-Price" @@ -117,6 +120,9 @@ var ( errBatchUnusable = errors.New("batch not usable") errUnsupportedDevNodeOperation = errors.New("operation not supported in dev mode") errOperationSupportedOnlyInFullMode = errors.New("operation is supported only in full mode") + errActDownload = errors.New("act download failed") + errActUpload = errors.New("act upload failed") + errActGranteeList = errors.New("failed to create or update grantee list") ) // Storer interface provides the functionality required from the local storage @@ -136,7 +142,6 @@ type PinIntegrity interface { } type Service struct { - auth auth.Authenticator storer Storer resolver resolver.Interface pss pss.Interface @@ -147,6 +152,7 @@ type Service struct { feedFactory feeds.Factory signer crypto.Signer post postage.Service + accesscontrol accesscontrol.Controller postageContract postagecontract.Interface probe *Probe metricsRegistry *prometheus.Registry @@ -161,7 +167,6 @@ type Service struct { wsWg sync.WaitGroup // wait for all websockets to close on exit quit chan struct{} - // from debug API overlay *swarm.Address publicKey ecdsa.PublicKey pssPublicKey ecdsa.PublicKey @@ -228,7 +233,6 @@ func (s *Service) SetRedistributionAgent(redistributionAgent *storageincentives. type Options struct { CORSAllowedOrigins []string WsPingPeriod time.Duration - Restricted bool } type ExtraOptions struct { @@ -245,6 +249,7 @@ type ExtraOptions struct { Pss pss.Interface FeedFactory feeds.Factory Post postage.Service + AccessControl accesscontrol.Controller PostageContract postagecontract.Interface Staking staking.Contract Steward steward.Interface @@ -314,8 +319,7 @@ func New( } // Configure will create a and initialize a new API service. -func (s *Service) Configure(signer crypto.Signer, auth auth.Authenticator, tracer *tracing.Tracer, o Options, e ExtraOptions, chainID int64, erc20 erc20.Service) { - s.auth = auth +func (s *Service) Configure(signer crypto.Signer, tracer *tracing.Tracer, o Options, e ExtraOptions, chainID int64, erc20 erc20.Service) { s.signer = signer s.Options = o s.tracer = tracer @@ -328,6 +332,7 @@ func (s *Service) Configure(signer crypto.Signer, auth auth.Authenticator, trace s.pss = e.Pss s.feedFactory = e.FeedFactory s.post = e.Post + s.accesscontrol = e.AccessControl s.postageContract = e.PostageContract s.steward = e.Steward s.stakingContract = e.Staking @@ -430,119 +435,6 @@ func (s *Service) resolveNameOrAddress(str string) (swarm.Address, error) { return swarm.ZeroAddress, fmt.Errorf("%w: %w", errInvalidNameOrAddress, err) } -type securityTokenRsp struct { - Key string `json:"key"` -} - -type securityTokenReq struct { - Role string `json:"role"` - Expiry int `json:"expiry"` // duration in seconds -} - -func (s *Service) authHandler(w http.ResponseWriter, r *http.Request) { - _, pass, ok := r.BasicAuth() - - if !ok { - s.logger.Error(nil, "auth handler: missing basic auth") - w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) - jsonhttp.Unauthorized(w, "Unauthorized") - return - } - - if !s.auth.Authorize(pass) { - s.logger.Error(nil, "auth handler: unauthorized") - w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) - jsonhttp.Unauthorized(w, "Unauthorized") - return - } - - body, err := io.ReadAll(r.Body) - if err != nil { - s.logger.Debug("auth handler: read request body failed", "error", err) - s.logger.Error(nil, "auth handler: read request body failed") - jsonhttp.BadRequest(w, "Read request body") - return - } - - var payload securityTokenReq - if err = json.Unmarshal(body, &payload); err != nil { - s.logger.Debug("auth handler: unmarshal request body failed", "error", err) - s.logger.Error(nil, "auth handler: unmarshal request body failed") - jsonhttp.BadRequest(w, "Unmarshal json body") - return - } - - key, err := s.auth.GenerateKey(payload.Role, time.Duration(payload.Expiry)*time.Second) - if errors.Is(err, auth.ErrExpiry) { - s.logger.Debug("auth handler: generate key failed", "error", err) - s.logger.Error(nil, "auth handler: generate key failed") - jsonhttp.BadRequest(w, "Expiry duration must be a positive number") - return - } - if err != nil { - s.logger.Debug("auth handler: add auth token failed", "error", err) - s.logger.Error(nil, "auth handler: add auth token failed") - jsonhttp.InternalServerError(w, "Error generating authorization token") - return - } - - jsonhttp.Created(w, securityTokenRsp{ - Key: key, - }) -} - -func (s *Service) refreshHandler(w http.ResponseWriter, r *http.Request) { - reqToken := r.Header.Get(AuthorizationHeader) - if !strings.HasPrefix(reqToken, "Bearer ") { - jsonhttp.Forbidden(w, "Missing bearer token") - return - } - - keys := strings.Split(reqToken, "Bearer ") - - if len(keys) != 2 || strings.Trim(keys[1], " ") == "" { - jsonhttp.Forbidden(w, "Missing security token") - return - } - - authToken := keys[1] - - body, err := io.ReadAll(r.Body) - if err != nil { - s.logger.Debug("auth handler: read request body failed", "error", err) - s.logger.Error(nil, "auth handler: read request body failed") - jsonhttp.BadRequest(w, "Read request body") - return - } - - var payload securityTokenReq - if err = json.Unmarshal(body, &payload); err != nil { - s.logger.Debug("auth handler: unmarshal request body failed", "error", err) - s.logger.Error(nil, "auth handler: unmarshal request body failed") - jsonhttp.BadRequest(w, "Unmarshal json body") - return - } - - key, err := s.auth.RefreshKey(authToken, time.Duration(payload.Expiry)*time.Second) - if errors.Is(err, auth.ErrTokenExpired) { - s.logger.Debug("auth handler: refresh key failed", "error", err) - s.logger.Error(nil, "auth handler: refresh key failed") - jsonhttp.BadRequest(w, "Token expired") - return - } - - if err != nil { - s.logger.Debug("auth handler: refresh token failed", "error", err) - s.logger.Error(nil, "auth handler: refresh token failed") - jsonhttp.InternalServerError(w, "Error refreshing authorization token") - return - } - - jsonhttp.Created(w, securityTokenRsp{ - Key: key, - }) -} - func (s *Service) newTracingHandler(spanName string) func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -791,15 +683,11 @@ func (p *putterSessionWrapper) Put(ctx context.Context, chunk swarm.Chunk) error } func (p *putterSessionWrapper) Done(ref swarm.Address) error { - err := p.PutterSession.Done(ref) - if err != nil { - return err - } - return p.save() + return errors.Join(p.PutterSession.Done(ref), p.save()) } func (p *putterSessionWrapper) Cleanup() error { - return p.PutterSession.Cleanup() + return errors.Join(p.PutterSession.Cleanup(), p.save()) } func (s *Service) getStamper(batchID []byte) (postage.Stamper, func() error, error) { diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index 6a96812a908..84634930599 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -23,10 +23,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + mockac "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" accountingmock "github.com/ethersphere/bee/v2/pkg/accounting/mock" "github.com/ethersphere/bee/v2/pkg/api" - "github.com/ethersphere/bee/v2/pkg/auth" - mockauth "github.com/ethersphere/bee/v2/pkg/auth/mock" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/pipeline" @@ -102,11 +102,9 @@ type testServerOptions struct { PostageContract postagecontract.Interface StakingContract staking.Contract Post postage.Service + AccessControl accesscontrol.Controller Steward steward.Interface WsHeaders http.Header - Authenticator auth.Authenticator - DebugAPI bool - Restricted bool DirectUpload bool Probe *api.Probe @@ -152,19 +150,16 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. if o.Post == nil { o.Post = mockpost.New() } + if o.AccessControl == nil { + o.AccessControl = mockac.New() + } if o.BatchStore == nil { o.BatchStore = mockbatchstore.New(mockbatchstore.WithAcceptAllExistsFunc()) // default is with accept-all Exists() func } if o.SyncStatus == nil { o.SyncStatus = func() (bool, error) { return true, nil } } - if o.Authenticator == nil { - o.Authenticator = &mockauth.Auth{ - EnforceFunc: func(_, _, _ string) (bool, error) { - return true, nil - }, - } - } + var chanStore *chanStorer topologyDriver := topologymock.NewTopologyDriver(o.TopologyOpts...) @@ -198,6 +193,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. Pss: o.Pss, FeedFactory: o.Feeds, Post: o.Post, + AccessControl: o.AccessControl, PostageContract: o.PostageContract, Steward: o.Steward, SyncStatus: o.SyncStatus, @@ -230,17 +226,14 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. }) testutil.CleanupCloser(t, tracerCloser) - s.Configure(signer, o.Authenticator, noOpTracer, api.Options{ + s.Configure(signer, noOpTracer, api.Options{ CORSAllowedOrigins: o.CORSAllowedOrigins, WsPingPeriod: o.WsPingPeriod, - Restricted: o.Restricted, }, extraOpts, 1, erc20) - if o.DebugAPI { - s.MountTechnicalDebug() - s.MountDebug() - } else { - s.MountAPI() - } + + s.MountTechnicalDebug() + s.MountDebug() + s.MountAPI() if o.DirectUpload { chanStore = newChanStore(o.Storer.PusherFeed()) @@ -251,21 +244,6 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. t.Cleanup(ts.Close) var ( - httpClient = &http.Client{ - Transport: web.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { - u, err := url.Parse(ts.URL + r.URL.String()) - if err != nil { - return nil, err - } - r.URL = u - return ts.Client().Transport.RoundTrip(r) - }), - } - conn *websocket.Conn - err error - ) - - if !o.DebugAPI { httpClient = &http.Client{ Transport: web.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { requestURL := r.URL.String() @@ -287,7 +265,9 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. return transport.RoundTrip(r) }), } - } + conn *websocket.Conn + err error + ) if o.WsPath != "" { u := url.URL{Scheme: "ws", Host: ts.Listener.Addr().String(), Path: o.WsPath} @@ -396,7 +376,7 @@ func TestParseName(t *testing.T) { signer := crypto.NewDefaultSigner(pk) s := api.New(pk.PublicKey, pk.PublicKey, common.Address{}, nil, log, nil, nil, 1, false, false, nil, []string{"*"}, inmemstore.New()) - s.Configure(signer, nil, nil, api.Options{}, api.ExtraOptions{Resolver: tC.res}, 1, nil) + s.Configure(signer, nil, api.Options{}, api.ExtraOptions{Resolver: tC.res}, 1, nil) s.MountAPI() tC := tC diff --git a/pkg/api/auth_test.go b/pkg/api/auth_test.go deleted file mode 100644 index f2cb0a99d59..00000000000 --- a/pkg/api/auth_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2021 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package api_test - -import ( - "errors" - "net/http" - "testing" - - "github.com/ethersphere/bee/v2/pkg/api" - "github.com/ethersphere/bee/v2/pkg/auth/mock" - "github.com/ethersphere/bee/v2/pkg/jsonhttp" - "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest" - "github.com/ethersphere/bee/v2/pkg/log" -) - -// nolint:paralleltest -func TestAuth(t *testing.T) { - var ( - resource = "/auth" - logger = log.Noop - authenticator = &mock.Auth{ - AuthorizeFunc: func(string) bool { return true }, - GenerateKeyFunc: func(string) (string, error) { return "123", nil }, - } - client, _, _, _ = newTestServer(t, testServerOptions{ - Logger: logger, - Restricted: true, - Authenticator: authenticator, - }) - ) - - t.Run("missing authorization header", func(t *testing.T) { - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusUnauthorized, - jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ - Message: "Unauthorized", - Code: http.StatusUnauthorized, - }), - ) - }) - t.Run("missing role", func(t *testing.T) { - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusBadRequest, - jsonhttptest.WithRequestHeader(api.AuthorizationHeader, "Basic dGVzdDp0ZXN0"), - jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ - Message: "Unmarshal json body", - Code: http.StatusBadRequest, - }), - ) - }) - t.Run("bad authorization header", func(t *testing.T) { - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusUnauthorized, - jsonhttptest.WithRequestHeader(api.AuthorizationHeader, "Basic dGV"), - jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ - Message: "Unauthorized", - Code: http.StatusUnauthorized, - }), - ) - }) - t.Run("bad request body", func(t *testing.T) { - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusBadRequest, - jsonhttptest.WithRequestHeader(api.AuthorizationHeader, "Basic dGVzdDp0ZXN0"), - - jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ - Message: "Unmarshal json body", - Code: http.StatusBadRequest, - }), - ) - }) - t.Run("unauthorized", func(t *testing.T) { - original := authenticator.AuthorizeFunc - authenticator.AuthorizeFunc = func(string) bool { return false } - defer func() { - authenticator.AuthorizeFunc = original - }() - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusUnauthorized, - jsonhttptest.WithRequestHeader(api.AuthorizationHeader, "Basic dGVzdDp0ZXN0"), - - jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ - Message: "Unauthorized", - Code: http.StatusUnauthorized, - }), - ) - }) - t.Run("failed to add key", func(t *testing.T) { - original := authenticator.GenerateKeyFunc - authenticator.GenerateKeyFunc = func(s string) (string, error) { - return "", errors.New("error adding key") - } - defer func() { - authenticator.GenerateKeyFunc = original - }() - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusInternalServerError, - jsonhttptest.WithRequestHeader(api.AuthorizationHeader, "Basic dGVzdDp0ZXN0"), - jsonhttptest.WithJSONRequestBody(api.SecurityTokenRequest{ - Role: "consumer", - }), - jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ - Message: "Error generating authorization token", - Code: http.StatusInternalServerError, - }), - ) - }) - t.Run("success", func(t *testing.T) { - jsonhttptest.Request(t, client, http.MethodPost, resource, http.StatusCreated, - jsonhttptest.WithRequestHeader(api.AuthorizationHeader, "Basic dGVzdDp0ZXN0"), - jsonhttptest.WithJSONRequestBody(api.SecurityTokenRequest{ - Role: "consumer", - }), - jsonhttptest.WithExpectedJSONResponse(api.SecurityTokenResponse{ - Key: "123", - }), - ) - }) -} diff --git a/pkg/api/balances_test.go b/pkg/api/balances_test.go index 174196d253a..d5434c3d3c8 100644 --- a/pkg/api/balances_test.go +++ b/pkg/api/balances_test.go @@ -32,7 +32,6 @@ func TestBalances(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithCompensatedBalancesFunc(compensatedBalancesFunc)}, }) @@ -72,7 +71,6 @@ func TestBalancesError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithCompensatedBalancesFunc(compensatedBalancesFunc)}, }) @@ -92,7 +90,6 @@ func TestBalancesPeers(t *testing.T) { return big.NewInt(100000000000000000), nil } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithCompensatedBalanceFunc(compensatedBalanceFunc)}, }) @@ -113,7 +110,6 @@ func TestBalancesPeersError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithCompensatedBalanceFunc(compensatedBalanceFunc)}, }) @@ -133,7 +129,6 @@ func TestBalancesPeersNoBalance(t *testing.T) { return nil, accounting.ErrPeerNoBalance } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithCompensatedBalanceFunc(compensatedBalanceFunc)}, }) @@ -186,7 +181,6 @@ func TestConsumedBalances(t *testing.T) { return ret, err } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithBalancesFunc(balancesFunc)}, }) @@ -227,7 +221,6 @@ func TestConsumedError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithBalancesFunc(balancesFunc)}, }) @@ -247,7 +240,6 @@ func TestConsumedPeers(t *testing.T) { return big.NewInt(1000000000000000000), nil } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithBalanceFunc(balanceFunc)}, }) @@ -268,7 +260,6 @@ func TestConsumedPeersError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithBalanceFunc(balanceFunc)}, }) @@ -288,7 +279,6 @@ func TestConsumedPeersNoBalance(t *testing.T) { return nil, accounting.ErrPeerNoBalance } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, AccountingOpts: []mock.Option{mock.WithBalanceFunc(balanceFunc)}, }) @@ -303,7 +293,7 @@ func TestConsumedPeersNoBalance(t *testing.T) { func Test_peerBalanceHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -352,7 +342,7 @@ func Test_peerBalanceHandler_invalidInputs(t *testing.T) { func Test_compensatedPeerBalanceHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index 9b5d9b902d6..7c9c4dceafa 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -11,11 +11,12 @@ import ( "net/http" "strconv" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/cac" "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/jsonhttp" "github.com/ethersphere/bee/v2/pkg/postage" - storage "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/tracing" "github.com/gorilla/mux" @@ -33,12 +34,14 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { defer span.Finish() headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` - Encrypt bool `map:"Swarm-Encrypt"` - RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Encrypt bool `map:"Swarm-Encrypt"` + RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -100,7 +103,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { } p := requestPipelineFn(putter, headers.Encrypt, headers.RLevel) - address, err := p(ctx, r.Body) + reference, err := p(ctx, r.Body) if err != nil { logger.Debug("split write all failed", "error", err) logger.Error(nil, "split write all failed") @@ -114,9 +117,28 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { return } - span.SetTag("root_address", address) + encryptedReference := reference + if headers.Act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) + if err != nil { + logger.Debug("access control upload failed", "error", err) + logger.Error(nil, "access control upload failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActUpload) + } + return + } + } + span.SetTag("root_address", encryptedReference) - err = putter.Done(address) + err = putter.Done(reference) if err != nil { logger.Debug("done split failed", "error", err) logger.Error(nil, "done split failed") @@ -133,7 +155,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) jsonhttp.Created(w, bytesPostResponse{ - Reference: address, + Reference: encryptedReference, }) } @@ -149,11 +171,16 @@ func (s *Service) bytesGetHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.IsZero() { + address = v + } + additionalHeaders := http.Header{ ContentTypeHeader: {"application/octet-stream"}, } - s.downloadHandler(logger, w, r, paths.Address, additionalHeaders, true, false) + s.downloadHandler(logger, w, r, address, additionalHeaders, true, false) } func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) { @@ -167,11 +194,16 @@ func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.IsZero() { + address = v + } + getter := s.storer.Download(true) - ch, err := getter.Get(r.Context(), paths.Address) + ch, err := getter.Get(r.Context(), address) if err != nil { - logger.Debug("get root chunk failed", "chunk_address", paths.Address, "error", err) + logger.Debug("get root chunk failed", "chunk_address", address, "error", err) logger.Error(nil, "get rook chunk failed") w.WriteHeader(http.StatusNotFound) return diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 65ded851f12..ddfa0768303 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -21,6 +21,7 @@ import ( olog "github.com/opentracing/opentracing-go/log" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/joiner" "github.com/ethersphere/bee/v2/pkg/file/loadsave" @@ -30,8 +31,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/log" "github.com/ethersphere/bee/v2/pkg/manifest" "github.com/ethersphere/bee/v2/pkg/postage" - storage "github.com/ethersphere/bee/v2/pkg/storage" - storer "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/topology" "github.com/ethersphere/bee/v2/pkg/tracing" @@ -63,14 +64,16 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { defer span.Finish() headers := struct { - ContentType string `map:"Content-Type,mimeMediaType" validate:"required"` - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` - Encrypt bool `map:"Swarm-Encrypt"` - IsDir bool `map:"Swarm-Collection"` - RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + ContentType string `map:"Content-Type,mimeMediaType" validate:"required"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Encrypt bool `map:"Swarm-Encrypt"` + IsDir bool `map:"Swarm-Collection"` + RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -132,10 +135,10 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { } if headers.IsDir || headers.ContentType == multiPartFormData { - s.dirUploadHandler(ctx, logger, span, ow, r, putter, r.Header.Get(ContentTypeHeader), headers.Encrypt, tag, headers.RLevel) + s.dirUploadHandler(ctx, logger, span, ow, r, putter, r.Header.Get(ContentTypeHeader), headers.Encrypt, tag, headers.RLevel, headers.Act, headers.HistoryAddress) return } - s.fileUploadHandler(ctx, logger, span, ow, r, putter, headers.Encrypt, tag, headers.RLevel) + s.fileUploadHandler(ctx, logger, span, ow, r, putter, headers.Encrypt, tag, headers.RLevel, headers.Act, headers.HistoryAddress) } // fileUploadResponse is returned when an HTTP request to upload a file is successful @@ -155,6 +158,8 @@ func (s *Service) fileUploadHandler( encrypt bool, tagID uint64, rLevel redundancy.Level, + act bool, + historyAddress swarm.Address, ) { queries := struct { FileName string `map:"name" validate:"startsnotwith=/"` @@ -260,26 +265,46 @@ func (s *Service) fileUploadHandler( } logger.Debug("store", "manifest_reference", manifestReference) + reference := manifestReference + if act { + reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, historyAddress) + if err != nil { + logger.Debug("access control upload failed", "error", err) + logger.Error(nil, "access control upload failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActUpload) + } + return + } + } + err = putter.Done(manifestReference) if err != nil { - logger.Debug("done split failed", "error", err) + logger.Debug("done split failed", "reference", manifestReference, "error", err) logger.Error(nil, "done split failed") jsonhttp.InternalServerError(w, "done split failed") ext.LogError(span, err, olog.String("action", "putter.Done")) return } - span.LogFields(olog.Bool("success", true)) - span.SetTag("root_address", manifestReference) + span.SetTag("root_address", reference) if tagID != 0 { w.Header().Set(SwarmTagHeader, fmt.Sprint(tagID)) span.SetTag("tagID", tagID) } - w.Header().Set(ETagHeader, fmt.Sprintf("%q", manifestReference.String())) + w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference.String())) w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) + jsonhttp.Created(w, bzzUploadResponse{ - Reference: manifestReference, + Reference: reference, }) } @@ -295,11 +320,16 @@ func (s *Service) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.IsZero() { + address = v + } + if strings.HasSuffix(paths.Path, "/") { paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. } - s.serveReference(logger, paths.Address, paths.Path, w, r, false) + s.serveReference(logger, address, paths.Path, w, r, false) } func (s *Service) bzzHeadHandler(w http.ResponseWriter, r *http.Request) { @@ -314,11 +344,16 @@ func (s *Service) bzzHeadHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.IsZero() { + address = v + } + if strings.HasSuffix(paths.Path, "/") { paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. } - s.serveReference(logger, paths.Address, paths.Path, w, r, true) + s.serveReference(logger, address, paths.Path, w, r, true) } func (s *Service) serveReference(logger log.Logger, address swarm.Address, pathVar string, w http.ResponseWriter, r *http.Request, headerOnly bool) { diff --git a/pkg/api/chequebook_test.go b/pkg/api/chequebook_test.go index b9b9cbdef32..e0276654d45 100644 --- a/pkg/api/chequebook_test.go +++ b/pkg/api/chequebook_test.go @@ -40,7 +40,6 @@ func TestChequebookBalance(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{ mock.WithChequebookBalanceFunc(chequebookBalanceFunc), mock.WithChequebookAvailableBalanceFunc(chequebookAvailableBalanceFunc), @@ -71,7 +70,6 @@ func TestChequebookBalanceError(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{mock.WithChequebookBalanceFunc(chequebookBalanceFunc)}, }) @@ -95,7 +93,6 @@ func TestChequebookAvailableBalanceError(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{ mock.WithChequebookBalanceFunc(chequebookBalanceFunc), mock.WithChequebookAvailableBalanceFunc(chequebookAvailableBalanceFunc), @@ -118,7 +115,6 @@ func TestChequebookAddress(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{mock.WithChequebookAddressFunc(chequebookAddressFunc)}, }) @@ -154,7 +150,6 @@ func TestChequebookWithdraw(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{mock.WithChequebookWithdrawFunc(chequebookWithdrawFunc)}, }) @@ -184,7 +179,6 @@ func TestChequebookWithdraw(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{mock.WithChequebookWithdrawFunc(chequebookWithdrawFunc)}, }) @@ -218,7 +212,6 @@ func TestChequebookDeposit(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{mock.WithChequebookDepositFunc(chequebookDepositFunc)}, }) @@ -249,7 +242,6 @@ func TestChequebookDeposit(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, ChequebookOpts: []mock.Option{mock.WithChequebookDepositFunc(chequebookDepositFunc)}, }) @@ -358,7 +350,6 @@ func TestChequebookLastCheques(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithLastReceivedChequesFunc(lastReceivedChequesFunc), swapmock.WithLastSentChequesFunc(lastSentChequesFunc)}, }) @@ -472,7 +463,6 @@ func TestChequebookLastChequesPeer(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithLastReceivedChequeFunc(lastReceivedChequeFunc), swapmock.WithLastSentChequeFunc(lastSentChequeFunc)}, }) @@ -512,7 +502,6 @@ func TestChequebookCashout(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithCashChequeFunc(cashChequeFunc)}, }) @@ -543,7 +532,6 @@ func TestChequebookCashout_CustomGas(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithCashChequeFunc(cashChequeFunc)}, }) @@ -619,7 +607,6 @@ func TestChequebookCashoutStatus(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)}, }) @@ -667,7 +654,6 @@ func TestChequebookCashoutStatus(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)}, }) @@ -706,7 +692,6 @@ func TestChequebookCashoutStatus(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)}, }) @@ -733,7 +718,7 @@ func TestChequebookCashoutStatus(t *testing.T) { func Test_chequebookLastPeerHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go index a572cacdcdf..1d1416636ed 100644 --- a/pkg/api/chunk.go +++ b/pkg/api/chunk.go @@ -12,6 +12,7 @@ import ( "net/http" "strconv" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/cac" "github.com/ethersphere/bee/v2/pkg/soc" @@ -30,8 +31,10 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("post_chunk").Build() headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -139,6 +142,26 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { } } + reference := chunk.Address() + if headers.Act { + reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) + if err != nil { + logger.Debug("access control upload failed", "error", err) + logger.Error(nil, "access control upload failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActUpload) + } + return + } + } + err = putter.Put(r.Context(), chunk) if err != nil { logger.Debug("chunk upload: write chunk failed", "chunk_address", chunk.Address(), "error", err) @@ -165,7 +188,7 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) - jsonhttp.Created(w, chunkAddressResponse{Reference: chunk.Address()}) + jsonhttp.Created(w, chunkAddressResponse{Reference: reference}) } func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { @@ -192,15 +215,20 @@ func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { return } - chunk, err := s.storer.Download(cache).Get(r.Context(), paths.Address) + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.IsZero() { + address = v + } + + chunk, err := s.storer.Download(cache).Get(r.Context(), address) if err != nil { if errors.Is(err, storage.ErrNotFound) { - loggerV1.Debug("chunk not found", "address", paths.Address) + loggerV1.Debug("chunk not found", "address", address) jsonhttp.NotFound(w, "chunk not found") return } - logger.Debug("read chunk failed", "chunk_address", paths.Address, "error", err) + logger.Debug("read chunk failed", "chunk_address", address, "error", err) logger.Error(nil, "read chunk failed") jsonhttp.InternalServerError(w, "read chunk failed") return diff --git a/pkg/api/chunk_address.go b/pkg/api/chunk_address.go index 6f214a0ea03..838c9ce6e8e 100644 --- a/pkg/api/chunk_address.go +++ b/pkg/api/chunk_address.go @@ -23,9 +23,14 @@ func (s *Service) hasChunkHandler(w http.ResponseWriter, r *http.Request) { return } - has, err := s.storer.ChunkStore().Has(r.Context(), paths.Address) + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.IsZero() { + address = v + } + + has, err := s.storer.ChunkStore().Has(r.Context(), address) if err != nil { - logger.Debug("has chunk failed", "chunk_address", paths.Address, "error", err) + logger.Debug("has chunk failed", "chunk_address", address, "error", err) jsonhttp.BadRequest(w, err) return } diff --git a/pkg/api/debugstorage_test.go b/pkg/api/debugstorage_test.go index bb12a5585fd..14a669f6b10 100644 --- a/pkg/api/debugstorage_test.go +++ b/pkg/api/debugstorage_test.go @@ -35,8 +35,7 @@ func TestDebugStorage(t *testing.T) { } ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, - Storer: mockstorer.NewWithDebugInfo(want), + Storer: mockstorer.NewWithDebugInfo(want), }) jsonhttptest.Request(t, ts, http.MethodGet, "/debugstore", http.StatusOK, diff --git a/pkg/api/dirs.go b/pkg/api/dirs.go index f54a02807c9..23be6929acc 100644 --- a/pkg/api/dirs.go +++ b/pkg/api/dirs.go @@ -18,6 +18,7 @@ import ( "strconv" "strings" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/jsonhttp" @@ -47,6 +48,8 @@ func (s *Service) dirUploadHandler( encrypt bool, tag uint64, rLevel redundancy.Level, + act bool, + historyAddress swarm.Address, ) { if r.Body == http.NoBody { logger.Error(nil, "request has no body") @@ -98,6 +101,26 @@ func (s *Service) dirUploadHandler( return } + encryptedReference := reference + if act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, historyAddress) + if err != nil { + logger.Debug("access control upload failed", "error", err) + logger.Error(nil, "access control upload failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActUpload) + } + return + } + } + err = putter.Done(reference) if err != nil { logger.Debug("store dir failed", "error", err) @@ -113,7 +136,7 @@ func (s *Service) dirUploadHandler( } w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) jsonhttp.Created(w, bzzUploadResponse{ - Reference: reference, + Reference: encryptedReference, }) } diff --git a/pkg/api/export_test.go b/pkg/api/export_test.go index 812ce522ba0..6c07c260479 100644 --- a/pkg/api/export_test.go +++ b/pkg/api/export_test.go @@ -18,8 +18,6 @@ type ( TagRequest = tagRequest ListTagsResponse = listTagsResponse IsRetrievableResponse = isRetrievableResponse - SecurityTokenResponse = securityTokenRsp - SecurityTokenRequest = securityTokenReq ) var ( @@ -38,6 +36,8 @@ var ( ErrInvalidNameOrAddress = errInvalidNameOrAddress ErrUnsupportedDevNodeOperation = errUnsupportedDevNodeOperation ErrOperationSupportedOnlyInFullMode = errOperationSupportedOnlyInFullMode + ErrActDownload = errActDownload + ErrActUpload = errActUpload ) var ( diff --git a/pkg/api/feed.go b/pkg/api/feed.go index 09fdf6515ec..7750fd7605c 100644 --- a/pkg/api/feed.go +++ b/pkg/api/feed.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/jsonhttp" @@ -21,8 +22,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/manifest/simple" "github.com/ethersphere/bee/v2/pkg/postage" "github.com/ethersphere/bee/v2/pkg/soc" - storage "github.com/ethersphere/bee/v2/pkg/storage" - storer "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/gorilla/mux" ) @@ -137,9 +138,11 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { } headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -245,6 +248,26 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { return } + encryptedReference := ref + if headers.Act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, ref, headers.HistoryAddress) + if err != nil { + logger.Debug("access control upload failed", "error", err) + logger.Error(nil, "access control upload failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActUpload) + } + return + } + } + err = putter.Done(ref) if err != nil { logger.Debug("done split failed", "error", err) @@ -253,7 +276,7 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { return } - jsonhttp.Created(w, feedReferenceResponse{Reference: ref}) + jsonhttp.Created(w, feedReferenceResponse{Reference: encryptedReference}) } func parseFeedUpdate(ch swarm.Chunk) (swarm.Address, int64, error) { diff --git a/pkg/api/health.go b/pkg/api/health.go index 33b6481b6ee..e153b9114f0 100644 --- a/pkg/api/health.go +++ b/pkg/api/health.go @@ -12,18 +12,16 @@ import ( ) type healthStatusResponse struct { - Status string `json:"status"` - Version string `json:"version"` - APIVersion string `json:"apiVersion"` - DebugAPIVersion string `json:"debugApiVersion"` + Status string `json:"status"` + Version string `json:"version"` + APIVersion string `json:"apiVersion"` } func (s *Service) healthHandler(w http.ResponseWriter, _ *http.Request) { status := s.probe.Healthy() jsonhttp.OK(w, healthStatusResponse{ - Status: status.String(), - Version: bee.Version, - APIVersion: Version, - DebugAPIVersion: DebugVersion, + Status: status.String(), + Version: bee.Version, + APIVersion: Version, }) } diff --git a/pkg/api/health_test.go b/pkg/api/health_test.go index 3446e167895..11c79f5366c 100644 --- a/pkg/api/health_test.go +++ b/pkg/api/health_test.go @@ -23,10 +23,9 @@ func TestHealth(t *testing.T) { // When probe is not set health endpoint should indicate that node is not healthy jsonhttptest.Request(t, testServer, http.MethodGet, "/health", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(api.HealthStatusResponse{ - Status: "nok", - Version: bee.Version, - APIVersion: api.Version, - DebugAPIVersion: api.Version, + Status: "nok", + Version: bee.Version, + APIVersion: api.Version, })) }) @@ -40,28 +39,25 @@ func TestHealth(t *testing.T) { // Current health probe is pending which should indicate that API is not healthy jsonhttptest.Request(t, testServer, http.MethodGet, "/health", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(api.HealthStatusResponse{ - Status: "nok", - Version: bee.Version, - APIVersion: api.Version, - DebugAPIVersion: api.Version, + Status: "nok", + Version: bee.Version, + APIVersion: api.Version, })) // When we set health probe to OK it should indicate that node is healthy probe.SetHealthy(api.ProbeStatusOK) jsonhttptest.Request(t, testServer, http.MethodGet, "/health", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(api.HealthStatusResponse{ - Status: "ok", - Version: bee.Version, - APIVersion: api.Version, - DebugAPIVersion: api.Version, + Status: "ok", + Version: bee.Version, + APIVersion: api.Version, })) // When we set health probe to NOK it should indicate that node is not healthy probe.SetHealthy(api.ProbeStatusNOK) jsonhttptest.Request(t, testServer, http.MethodGet, "/health", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(api.HealthStatusResponse{ - Status: "nok", - Version: bee.Version, - APIVersion: api.Version, - DebugAPIVersion: api.Version, + Status: "nok", + Version: bee.Version, + APIVersion: api.Version, })) }) } diff --git a/pkg/api/logger_test.go b/pkg/api/logger_test.go index b8420b79067..4a3349f6681 100644 --- a/pkg/api/logger_test.go +++ b/pkg/api/logger_test.go @@ -72,7 +72,7 @@ func TestGetLoggers(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) jsonhttptest.Request(t, client, http.MethodGet, "/loggers", http.StatusOK, jsonhttptest.WithUnmarshalJSONResponse(&have), ) @@ -88,7 +88,7 @@ func TestSetLoggerVerbosity(t *testing.T) { api.ReplaceLogSetVerbosityByExp(fn) }(api.LogSetVerbosityByExp) - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) type data struct { exp string @@ -143,7 +143,7 @@ func TestSetLoggerVerbosity(t *testing.T) { func Test_loggerGetHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -192,7 +192,7 @@ func Test_loggerGetHandler_invalidInputs(t *testing.T) { func Test_loggerSetVerbosityHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string diff --git a/pkg/api/p2p_test.go b/pkg/api/p2p_test.go index 5cf9de6124c..933596afff7 100644 --- a/pkg/api/p2p_test.go +++ b/pkg/api/p2p_test.go @@ -40,7 +40,6 @@ func TestAddresses(t *testing.T) { ethereumAddress := common.HexToAddress("abcd") testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PublicKey: privateKey.PublicKey, PSSPublicKey: pssPrivateKey.PublicKey, Overlay: overlay, @@ -79,7 +78,6 @@ func TestAddresses_error(t *testing.T) { testErr := errors.New("test error") testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithAddressesFunc(func() ([]multiaddr.Multiaddr, error) { return nil, testErr })), diff --git a/pkg/api/peer_test.go b/pkg/api/peer_test.go index 0697a6277d2..fdda7b8edf4 100644 --- a/pkg/api/peer_test.go +++ b/pkg/api/peer_test.go @@ -51,7 +51,6 @@ func TestConnect(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithConnectFunc(func(ctx context.Context, addr ma.Multiaddr) (*bzz.Address, error) { if addr.String() == errorUnderlay { return nil, testErr @@ -82,7 +81,6 @@ func TestConnect(t *testing.T) { t.Run("error - add peer", func(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithConnectFunc(func(ctx context.Context, addr ma.Multiaddr) (*bzz.Address, error) { if addr.String() == errorUnderlay { return nil, testErr @@ -109,7 +107,6 @@ func TestDisconnect(t *testing.T) { testErr := errors.New("test error") testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithDisconnectFunc(func(addr swarm.Address, reason string) error { if reason != "user requested disconnect" { return testErr @@ -166,7 +163,6 @@ func TestPeer(t *testing.T) { overlay := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59c") testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithPeersFunc(func() []p2p.Peer { return []p2p.Peer{{Address: overlay}} })), @@ -188,7 +184,6 @@ func TestBlocklistedPeers(t *testing.T) { overlay := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59c") testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithBlocklistedPeersFunc(func() ([]p2p.BlockListedPeer, error) { return []p2p.BlockListedPeer{{Peer: p2p.Peer{Address: overlay}}}, nil })), @@ -206,7 +201,6 @@ func TestBlocklistedPeersErr(t *testing.T) { overlay := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59c") testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithBlocklistedPeersFunc(func() ([]p2p.BlockListedPeer, error) { return []p2p.BlockListedPeer{{Peer: p2p.Peer{Address: overlay}}}, errors.New("some error") })), @@ -224,7 +218,7 @@ func TestBlocklistedPeersErr(t *testing.T) { func Test_peerConnectHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -260,7 +254,7 @@ func Test_peerConnectHandler_invalidInputs(t *testing.T) { func Test_peerDisconnectHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string diff --git a/pkg/api/pingpong_test.go b/pkg/api/pingpong_test.go index 4dffae5b0e6..9de99103619 100644 --- a/pkg/api/pingpong_test.go +++ b/pkg/api/pingpong_test.go @@ -39,7 +39,6 @@ func TestPingpong(t *testing.T) { }) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, Pingpong: pingpongService, }) @@ -79,7 +78,7 @@ func TestPingpong(t *testing.T) { func Test_pingpongHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string diff --git a/pkg/api/postage_test.go b/pkg/api/postage_test.go index ebf808c1c7a..13e3dda13d6 100644 --- a/pkg/api/postage_test.go +++ b/pkg/api/postage_test.go @@ -65,7 +65,6 @@ func TestPostageCreateStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -90,7 +89,6 @@ func TestPostageCreateStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -111,7 +109,6 @@ func TestPostageCreateStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -132,7 +129,6 @@ func TestPostageCreateStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -161,7 +157,6 @@ func TestPostageCreateStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -182,7 +177,6 @@ func TestPostageCreateStamp(t *testing.T) { t.Parallel() ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SyncStatus: func() (bool, error) { return false, nil }, }) @@ -197,7 +191,6 @@ func TestPostageCreateStamp(t *testing.T) { t.Parallel() ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SyncStatus: func() (bool, error) { return true, errors.New("oops") }, }) @@ -223,7 +216,7 @@ func TestPostageGetStamps(t *testing.T) { t.Parallel() bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b)) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, Post: mp, BatchStore: bs, BlockTime: 2 * time.Second}) + ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second}) jsonhttptest.Request(t, ts, http.MethodGet, "/stamps", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(&api.PostageStampsResponse{ @@ -259,7 +252,7 @@ func TestPostageGetStamps(t *testing.T) { } ecs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(15), CurrentPrice: big.NewInt(12)} ebs := mock.New(mock.WithChainState(ecs)) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, Post: emp, BatchStore: ebs, BlockTime: 2 * time.Second}) + ts, _, _, _ := newTestServer(t, testServerOptions{Post: emp, BatchStore: ebs, BlockTime: 2 * time.Second}) jsonhttptest.Request(t, ts, http.MethodGet, "/stamps/"+hex.EncodeToString(eb.ID), http.StatusNotFound, jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{ @@ -281,7 +274,7 @@ func TestGetAllBatches(t *testing.T) { mp := mockpost.New(mockpost.WithIssuer(si)) cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)} bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b)) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, Post: mp, BatchStore: bs, BlockTime: 2 * time.Second}) + ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second}) oneBatch := struct { Batches []api.PostageBatchResponse `json:"batches"` @@ -318,7 +311,7 @@ func TestPostageGetStamp(t *testing.T) { mp := mockpost.New(mockpost.WithIssuer(si)) cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)} bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b)) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, Post: mp, BatchStore: bs, BlockTime: 2 * time.Second}) + ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second}) t.Run("ok", func(t *testing.T) { t.Parallel() @@ -346,7 +339,7 @@ func TestPostageGetBuckets(t *testing.T) { si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true) mp := mockpost.New(mockpost.WithIssuer(si)) - ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, DebugAPI: true}) + ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp}) buckets := make([]api.BucketData, 1024) for i := range buckets { buckets[i] = api.BucketData{BucketID: uint32(i)} @@ -369,7 +362,7 @@ func TestPostageGetBuckets(t *testing.T) { t.Parallel() mpNotFound := mockpost.New() - tsNotFound, _, _, _ := newTestServer(t, testServerOptions{Post: mpNotFound, DebugAPI: true}) + tsNotFound, _, _, _ := newTestServer(t, testServerOptions{Post: mpNotFound}) jsonhttptest.Request(t, tsNotFound, http.MethodGet, "/stamps/"+batchOkStr+"/buckets", http.StatusNotFound) }) @@ -383,7 +376,6 @@ func TestReserveState(t *testing.T) { t.Parallel() ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, BatchStore: mock.New(mock.WithRadius(5)), Storer: mockstorer.New(), }) @@ -397,7 +389,6 @@ func TestReserveState(t *testing.T) { t.Parallel() ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, BatchStore: mock.New(), Storer: mockstorer.New(), }) @@ -418,7 +409,6 @@ func TestChainState(t *testing.T) { CurrentPrice: big.NewInt(5), } ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, BatchStore: mock.New(mock.WithChainState(cs)), BackendOpts: []backendmock.Option{backendmock.WithBlockNumberFunc(func(ctx context.Context) (uint64, error) { return 1, nil @@ -460,7 +450,6 @@ func TestPostageTopUpStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -490,7 +479,6 @@ func TestPostageTopUpStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -512,7 +500,6 @@ func TestPostageTopUpStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -533,7 +520,6 @@ func TestPostageTopUpStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -557,7 +543,6 @@ func TestPostageTopUpStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -595,7 +580,6 @@ func TestPostageDiluteStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -625,7 +609,6 @@ func TestPostageDiluteStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -647,7 +630,6 @@ func TestPostageDiluteStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -668,7 +650,6 @@ func TestPostageDiluteStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -692,7 +673,6 @@ func TestPostageDiluteStamp(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -812,7 +792,6 @@ func TestPostageAccessHandler(t *testing.T) { ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, PostageContract: contract, }) @@ -837,7 +816,7 @@ func TestPostageAccessHandler(t *testing.T) { func Test_postageCreateHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -887,7 +866,7 @@ func Test_postageCreateHandler_invalidInputs(t *testing.T) { func Test_postageGetStampBucketsHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -949,7 +928,7 @@ func Test_postageGetStampBucketsHandler_invalidInputs(t *testing.T) { func Test_postageGetStampHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -1012,7 +991,7 @@ func Test_postageGetStampHandler_invalidInputs(t *testing.T) { func Test_postageTopUpHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -1091,7 +1070,7 @@ func Test_postageTopUpHandler_invalidInputs(t *testing.T) { func Test_postageDiluteHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string diff --git a/pkg/api/redistribution_test.go b/pkg/api/redistribution_test.go index 88bf00abe51..07b4bc28cfe 100644 --- a/pkg/api/redistribution_test.go +++ b/pkg/api/redistribution_test.go @@ -36,7 +36,6 @@ func TestRedistributionStatus(t *testing.T) { t.Errorf("redistribution put state: %v", err) } srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, StateStorer: store, TransactionOpts: []mock.Option{ mock.WithTransactionFeeFunc(func(ctx context.Context, txHash common.Hash) (*big.Int, error) { @@ -61,7 +60,6 @@ func TestRedistributionStatus(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, BeeMode: api.LightMode, StateStorer: statestore.NewStateStore(), TransactionOpts: []mock.Option{ diff --git a/pkg/api/router.go b/pkg/api/router.go index c0c6d02782c..19f4b4fbd04 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -11,7 +11,6 @@ import ( "net/http/pprof" "strings" - "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/jsonhttp" "github.com/ethersphere/bee/v2/pkg/log/httpaccess" "github.com/ethersphere/bee/v2/pkg/swarm" @@ -34,7 +33,7 @@ func (s *Service) MountTechnicalDebug() { s.mountTechnicalDebug() s.Handler = web.ChainHandlers( - httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "debug api access"), + httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "api access"), handlers.CompressHandler, s.corsHandler, web.NoCacheHeadersHandler, @@ -46,7 +45,7 @@ func (s *Service) MountDebug() { s.mountBusinessDebug() s.Handler = web.ChainHandlers( - httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "debug api access"), + httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "api access"), handlers.CompressHandler, s.corsHandler, web.NoCacheHeadersHandler, @@ -142,11 +141,7 @@ func (s *Service) mountTechnicalDebug() { s.router.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) s.router.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) - pprofRootHandlerF := pprof.Index - if s.Restricted { - pprofRootHandlerF = web.ChainHandlers(auth.PermissionCheckHandler(s.auth), web.FinalHandler(http.HandlerFunc(pprof.Index))).ServeHTTP - } - s.router.PathPrefix("/debug/pprof/").Handler(http.HandlerFunc(pprofRootHandlerF)) + s.router.PathPrefix("/debug/pprof/").Handler(http.HandlerFunc(pprof.Index)) s.router.Handle("/debug/vars", expvar.Handler()) s.router.Handle("/loggers", jsonhttp.MethodHandler{ @@ -214,10 +209,12 @@ func (s *Service) mountAPI() { "GET": web.ChainHandlers( s.contentLengthMetricMiddleware(), s.newTracingHandler("bytes-download"), + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bytesGetHandler), ), "HEAD": web.ChainHandlers( s.newTracingHandler("bytes-head"), + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bytesHeadHandler), ), }) @@ -235,8 +232,14 @@ func (s *Service) mountAPI() { )) handle("/chunks/{address}", jsonhttp.MethodHandler{ - "GET": http.HandlerFunc(s.chunkGetHandler), - "HEAD": http.HandlerFunc(s.hasChunkHandler), + "GET": web.ChainHandlers( + s.actDecryptionHandler(), + web.FinalHandlerFunc(s.chunkGetHandler), + ), + "HEAD": web.ChainHandlers( + s.actDecryptionHandler(), + web.FinalHandlerFunc(s.hasChunkHandler), + ), }) handle("/soc/{owner}/{id}", jsonhttp.MethodHandler{ @@ -262,6 +265,21 @@ func (s *Service) mountAPI() { ), }) + handle("/grantee", jsonhttp.MethodHandler{ + "POST": web.ChainHandlers( + web.FinalHandlerFunc(s.actCreateGranteesHandler), + ), + }) + + handle("/grantee/{address}", jsonhttp.MethodHandler{ + "GET": web.ChainHandlers( + web.FinalHandlerFunc(s.actListGranteesHandler), + ), + "PATCH": web.ChainHandlers( + web.FinalHandlerFunc(s.actGrantRevokeHandler), + ), + }) + handle("/bzz/{address}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { u := r.URL u.Path += "/" @@ -272,9 +290,11 @@ func (s *Service) mountAPI() { "GET": web.ChainHandlers( s.contentLengthMetricMiddleware(), s.newTracingHandler("bzz-download"), + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bzzDownloadHandler), ), "HEAD": web.ChainHandlers( + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bzzHeadHandler), ), }) @@ -351,29 +371,10 @@ func (s *Service) mountAPI() { httpaccess.NewHTTPAccessSuppressLogHandler(), web.FinalHandlerFunc(s.healthHandler), )) - - if s.Restricted { - handle("/auth", jsonhttp.MethodHandler{ - "POST": web.ChainHandlers( - jsonhttp.NewMaxBodyBytesHandler(512), - web.FinalHandlerFunc(s.authHandler), - ), - }) - handle("/refresh", jsonhttp.MethodHandler{ - "POST": web.ChainHandlers( - jsonhttp.NewMaxBodyBytesHandler(512), - web.FinalHandlerFunc(s.refreshHandler), - ), - }) - } } func (s *Service) mountBusinessDebug() { handle := func(path string, handler http.Handler) { - s.logger.Warning("DEPRECATION NOTICE: This endpoint is now part of the main Bee API. The Debug API will be removed in the next release, version [2.2.0]. Update your integrations to use the main Bee API to avoid service disruptions.") - if s.Restricted { - handler = web.ChainHandlers(auth.PermissionCheckHandler(s.auth), web.FinalHandler(handler)) - } s.router.Handle(path, handler) s.router.Handle(rootPath+path, handler) } @@ -413,9 +414,9 @@ func (s *Service) mountBusinessDebug() { "DELETE": http.HandlerFunc(s.peerDisconnectHandler), }) - handle("/chunks/{address}", jsonhttp.MethodHandler{ - "GET": http.HandlerFunc(s.hasChunkHandler), - }) + //handle("/chunks/{address}", jsonhttp.MethodHandler{ + // "GET": http.HandlerFunc(s.hasChunkHandler), + //}) handle("/topology", jsonhttp.MethodHandler{ "GET": http.HandlerFunc(s.topologyHandler), diff --git a/pkg/api/settlements_test.go b/pkg/api/settlements_test.go index 86f2e2e42e1..f76997e1b8c 100644 --- a/pkg/api/settlements_test.go +++ b/pkg/api/settlements_test.go @@ -39,7 +39,6 @@ func TestSettlements(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []mock.Option{mock.WithSettlementsSentFunc(settlementsSentFunc), mock.WithSettlementsRecvFunc(settlementsRecvFunc)}, }) @@ -90,7 +89,6 @@ func TestSettlementsError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []mock.Option{mock.WithSettlementsSentFunc(settlementsSentFunc)}, }) @@ -110,7 +108,6 @@ func TestSettlementsPeers(t *testing.T) { return big.NewInt(1000000000000000000), nil } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []mock.Option{mock.WithSettlementSentFunc(settlementSentFunc)}, }) @@ -138,7 +135,6 @@ func TestSettlementsPeersNoSettlements(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []mock.Option{ mock.WithSettlementSentFunc(errFunc), mock.WithSettlementRecvFunc(noErrFunc), @@ -158,7 +154,6 @@ func TestSettlementsPeersNoSettlements(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []mock.Option{ mock.WithSettlementSentFunc(noErrFunc), mock.WithSettlementRecvFunc(errFunc), @@ -178,7 +173,7 @@ func TestSettlementsPeersNoSettlements(t *testing.T) { func Test_peerSettlementsHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -233,7 +228,6 @@ func TestSettlementsPeersError(t *testing.T) { return nil, wantErr } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, SwapOpts: []mock.Option{mock.WithSettlementSentFunc(settlementSentFunc)}, }) diff --git a/pkg/api/soc.go b/pkg/api/soc.go index 0abf338deb9..dea71963aa7 100644 --- a/pkg/api/soc.go +++ b/pkg/api/soc.go @@ -9,6 +9,7 @@ import ( "io" "net/http" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/cac" "github.com/ethersphere/bee/v2/pkg/jsonhttp" "github.com/ethersphere/bee/v2/pkg/postage" @@ -43,8 +44,10 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { } headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - Pin bool `map:"Swarm-Pin"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + Pin bool `map:"Swarm-Pin"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -155,6 +158,26 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { return } + reference := sch.Address() + if headers.Act { + reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) + if err != nil { + logger.Debug("access control upload failed", "error", err) + logger.Error(nil, "access control upload failed") + switch { + case errors.Is(err, accesscontrol.ErrNotFound): + jsonhttp.NotFound(w, "act or history entry not found") + case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): + jsonhttp.BadRequest(w, "invalid public key") + case errors.Is(err, accesscontrol.ErrUnexpectedType): + jsonhttp.BadRequest(w, "failed to create history") + default: + jsonhttp.InternalServerError(w, errActUpload) + } + return + } + } + err = putter.Put(r.Context(), sch) if err != nil { logger.Debug("write chunk failed", "chunk_address", sch.Address(), "error", err) @@ -171,5 +194,5 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { return } - jsonhttp.Created(w, chunkAddressResponse{Reference: sch.Address()}) + jsonhttp.Created(w, socPostResponse{Reference: reference}) } diff --git a/pkg/api/staking_test.go b/pkg/api/staking_test.go index e6f4d0b2296..0d6dac27587 100644 --- a/pkg/api/staking_test.go +++ b/pkg/api/staking_test.go @@ -39,7 +39,7 @@ func TestDepositStake(t *testing.T) { return txHash, nil }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusOK) }) @@ -52,7 +52,7 @@ func TestDepositStake(t *testing.T) { return common.Hash{}, staking.ErrInsufficientStakeAmount }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodPost, depositStake(invalidMinStake), http.StatusBadRequest, jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake amount"})) }) @@ -65,7 +65,7 @@ func TestDepositStake(t *testing.T) { return common.Hash{}, staking.ErrInsufficientFunds }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusBadRequest) jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "out of funds"}) }) @@ -78,7 +78,7 @@ func TestDepositStake(t *testing.T) { return common.Hash{}, fmt.Errorf("some error") }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusInternalServerError) jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot stake"}) }) @@ -96,7 +96,6 @@ func TestDepositStake(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, StakingContract: contract, }) @@ -117,7 +116,7 @@ func TestGetStake(t *testing.T) { return big.NewInt(1), nil }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(&api.GetStakeResponse{StakedAmount: bigint.Wrap(big.NewInt(1))})) }) @@ -130,7 +129,7 @@ func TestGetStake(t *testing.T) { return big.NewInt(0), fmt.Errorf("get stake failed") }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contractWithError}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError}) jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusInternalServerError, jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "get staked amount failed"})) }) @@ -139,7 +138,7 @@ func TestGetStake(t *testing.T) { func Test_stakingDepositHandler_invalidInputs(t *testing.T) { t.Parallel() - client, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + client, _, _, _ := newTestServer(t, testServerOptions{}) tests := []struct { name string @@ -185,7 +184,7 @@ func TestWithdrawAllStake(t *testing.T) { return txHash, nil }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusOK, jsonhttptest.WithExpectedJSONResponse( &api.WithdrawAllStakeResponse{TxHash: txHash.String()})) }) @@ -198,7 +197,7 @@ func TestWithdrawAllStake(t *testing.T) { return common.Hash{}, staking.ErrInsufficientStake }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusBadRequest, jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake to withdraw"})) }) @@ -211,7 +210,7 @@ func TestWithdrawAllStake(t *testing.T) { return common.Hash{}, fmt.Errorf("some error") }), ) - ts, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true, StakingContract: contract}) + ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusInternalServerError) jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot withdraw stake"}) }) @@ -229,7 +228,6 @@ func TestWithdrawAllStake(t *testing.T) { }), ) ts, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, StakingContract: contract, }) diff --git a/pkg/api/status_test.go b/pkg/api/status_test.go index cc2c568abf4..8dadff7484c 100644 --- a/pkg/api/status_test.go +++ b/pkg/api/status_test.go @@ -58,7 +58,6 @@ func TestGetStatus(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ BeeMode: mode, - DebugAPI: true, NodeStatus: statusSvc, }) @@ -71,8 +70,7 @@ func TestGetStatus(t *testing.T) { t.Parallel() client, _, _, _ := newTestServer(t, testServerOptions{ - BeeMode: api.DevMode, - DebugAPI: true, + BeeMode: api.DevMode, NodeStatus: status.NewService( log.Noop, nil, diff --git a/pkg/api/topology_test.go b/pkg/api/topology_test.go index ebd22532f3b..11dd6703791 100644 --- a/pkg/api/topology_test.go +++ b/pkg/api/topology_test.go @@ -14,7 +14,7 @@ import ( func TestTopologyOK(t *testing.T) { t.Parallel() - testServer, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + testServer, _, _, _ := newTestServer(t, testServerOptions{}) var body []byte opts := jsonhttptest.WithPutResponseBody(&body) diff --git a/pkg/api/transaction_test.go b/pkg/api/transaction_test.go index 46e066c4091..c23cc9cf3f5 100644 --- a/pkg/api/transaction_test.go +++ b/pkg/api/transaction_test.go @@ -42,7 +42,6 @@ func TestTransactionStoredTransaction(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) { return &transaction.StoredTransaction{ @@ -84,7 +83,6 @@ func TestTransactionStoredTransaction(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) { return nil, transaction.ErrUnknownTransaction @@ -103,7 +101,6 @@ func TestTransactionStoredTransaction(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) { return nil, errors.New("err") @@ -155,7 +152,6 @@ func TestTransactionList(t *testing.T) { } testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithPendingTransactionsFunc(func() ([]common.Hash, error) { return []common.Hash{txHash1, txHash2}, nil @@ -210,7 +206,6 @@ func TestTransactionListError(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithPendingTransactionsFunc(func() ([]common.Hash, error) { return nil, errors.New("err") @@ -233,7 +228,6 @@ func TestTransactionListError(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithPendingTransactionsFunc(func() ([]common.Hash, error) { return []common.Hash{txHash1}, nil @@ -261,7 +255,6 @@ func TestTransactionResend(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error { return nil @@ -280,7 +273,6 @@ func TestTransactionResend(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error { return transaction.ErrUnknownTransaction @@ -300,7 +292,6 @@ func TestTransactionResend(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error { return transaction.ErrAlreadyImported @@ -320,7 +311,6 @@ func TestTransactionResend(t *testing.T) { t.Parallel() testServer, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, TransactionOpts: []mock.Option{ mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error { return errors.New("err") diff --git a/pkg/api/version.go b/pkg/api/version.go index b9bf36154c4..44086d8bc47 100644 --- a/pkg/api/version.go +++ b/pkg/api/version.go @@ -6,4 +6,3 @@ package api // Version is set in the build process. var Version = "0.0.0" -var DebugVersion = "0.0.0" diff --git a/pkg/api/wallet_test.go b/pkg/api/wallet_test.go index c6f7449d51a..d5c09efaeeb 100644 --- a/pkg/api/wallet_test.go +++ b/pkg/api/wallet_test.go @@ -28,7 +28,6 @@ func TestWallet(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, Erc20Opts: []erc20mock.Option{ erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) { return big.NewInt(10000000000000000), nil @@ -54,7 +53,6 @@ func TestWallet(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, BackendOpts: []backendmock.Option{ backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) { return new(big.Int), nil @@ -73,7 +71,6 @@ func TestWallet(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, Erc20Opts: []erc20mock.Option{ erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) { return new(big.Int), nil @@ -95,7 +92,7 @@ func TestWalletWithdraw(t *testing.T) { t.Run("address not whitelisted", func(t *testing.T) { t.Parallel() - srv, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + srv, _, _, _ := newTestServer(t, testServerOptions{}) jsonhttptest.Request(t, srv, http.MethodPost, "/wallet/withdraw/BZZ?address=0xaf&amount=99999999", http.StatusBadRequest, jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ @@ -107,7 +104,7 @@ func TestWalletWithdraw(t *testing.T) { t.Run("invalid coin type", func(t *testing.T) { t.Parallel() - srv, _, _, _ := newTestServer(t, testServerOptions{DebugAPI: true}) + srv, _, _, _ := newTestServer(t, testServerOptions{}) jsonhttptest.Request(t, srv, http.MethodPost, "/wallet/withdraw/BTC?address=0xaf&amount=99999999", http.StatusBadRequest, jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ @@ -120,7 +117,6 @@ func TestWalletWithdraw(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", }) @@ -135,7 +131,6 @@ func TestWalletWithdraw(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", Erc20Opts: []erc20mock.Option{ erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) { @@ -155,7 +150,6 @@ func TestWalletWithdraw(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", Erc20Opts: []erc20mock.Option{ erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) { @@ -177,7 +171,6 @@ func TestWalletWithdraw(t *testing.T) { txHash := common.HexToHash("0x00f") srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", Erc20Opts: []erc20mock.Option{ erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) { @@ -205,7 +198,6 @@ func TestWalletWithdraw(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", }) @@ -220,7 +212,6 @@ func TestWalletWithdraw(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", BackendOpts: []backendmock.Option{ backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) { @@ -240,7 +231,6 @@ func TestWalletWithdraw(t *testing.T) { t.Parallel() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", BackendOpts: []backendmock.Option{ backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) { @@ -262,7 +252,6 @@ func TestWalletWithdraw(t *testing.T) { txHash := common.HexToHash("0x00f") srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, WhitelistedAddr: "0xaf", BackendOpts: []backendmock.Option{ backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) { diff --git a/pkg/api/welcome_message_test.go b/pkg/api/welcome_message_test.go index 39d7c19a039..6eb6670e8ab 100644 --- a/pkg/api/welcome_message_test.go +++ b/pkg/api/welcome_message_test.go @@ -23,7 +23,6 @@ func TestGetWelcomeMessage(t *testing.T) { const DefaultTestWelcomeMessage = "Hello World!" srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithGetWelcomeMessageFunc(func() string { return DefaultTestWelcomeMessage }))}) @@ -75,8 +74,7 @@ func TestSetWelcomeMessage(t *testing.T) { mockP2P := mock.New() srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, - P2P: mockP2P, + P2P: mockP2P, }) if tC.wantMessage == "" { @@ -112,7 +110,6 @@ func TestSetWelcomeMessageInternalServerError(t *testing.T) { testURL := "/welcome-message" srv, _, _, _ := newTestServer(t, testServerOptions{ - DebugAPI: true, P2P: mock.New(mock.WithSetWelcomeMessageFunc(func(string) error { return testError })), diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go deleted file mode 100644 index a5aaec89dd2..00000000000 --- a/pkg/auth/auth.go +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2021 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package auth - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/md5" - "crypto/rand" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "io" - "time" - - "github.com/casbin/casbin/v2" - "github.com/casbin/casbin/v2/model" - "github.com/ethersphere/bee/v2/pkg/log" - "golang.org/x/crypto/bcrypt" -) - -// loggerName is the tree path name of the logger for this package. -const loggerName = "auth" - -type Authenticator interface { - Authorize(string) bool - GenerateKey(string, time.Duration) (string, error) - RefreshKey(string, time.Duration) (string, error) - Enforce(string, string, string) (bool, error) -} - -type authRecord struct { - Role string `json:"r"` - Expiry time.Time `json:"e"` -} - -type authenticator struct { - passwordHash []byte - ciph *encrypter - enforcer *casbin.Enforcer - log log.Logger -} - -func New(encryptionKey, passwordHash string, logger log.Logger) (*authenticator, error) { - m, err := model.NewModelFromString(` - [request_definition] - r = sub, obj, act - - [role_definition] - g = _, _ - - [policy_definition] - p = sub, obj, act - - [policy_effect] - e = some(where (p.eft == allow)) - - [matchers] - m = (g(r.sub, p.sub) || r.sub == p.sub) && (keyMatch(r.obj, p.obj) || keyMatch(r.obj, '/v1'+p.obj)) && regexMatch(r.act, p.act)`) - - if err != nil { - return nil, err - } - - e, err := casbin.NewEnforcer(m) - if err != nil { - return nil, err - } - - if err := applyPolicies(e); err != nil { - return nil, err - } - - ciph, err := newEncrypter([]byte(encryptionKey)) - if err != nil { - return nil, err - } - - auth := authenticator{ - enforcer: e, - ciph: ciph, - passwordHash: []byte(passwordHash), - log: logger.WithName(loggerName).Register(), - } - - return &auth, nil -} - -func (a *authenticator) Authorize(password string) bool { - return nil == bcrypt.CompareHashAndPassword(a.passwordHash, []byte(password)) -} - -var ErrExpiry = errors.New("expiry duration must be a positive number") - -func (a *authenticator) GenerateKey(role string, expiryDuration time.Duration) (string, error) { - if expiryDuration == 0 { - return "", ErrExpiry - } - - ar := authRecord{ - Role: role, - Expiry: time.Now().Add(expiryDuration), - } - - data, err := json.Marshal(ar) - if err != nil { - return "", err - } - - encryptedBytes, err := a.ciph.encrypt(data) - if err != nil { - return "", err - } - - apiKey := base64.StdEncoding.EncodeToString(encryptedBytes) - - return apiKey, nil -} - -var ErrTokenExpired = errors.New("token expired") - -func (a *authenticator) RefreshKey(apiKey string, expiryDuration time.Duration) (string, error) { - if expiryDuration == 0 { - return "", ErrExpiry - } - - decoded, err := base64.StdEncoding.DecodeString(apiKey) - if err != nil { - return "", err - } - - decryptedBytes, err := a.ciph.decrypt(decoded) - if err != nil { - return "", err - } - - var ar authRecord - if err := json.Unmarshal(decryptedBytes, &ar); err != nil { - return "", err - } - - if time.Now().After(ar.Expiry) { - return "", ErrTokenExpired - } - - ar.Expiry = time.Now().Add(expiryDuration) - - data, err := json.Marshal(ar) - if err != nil { - return "", err - } - - encryptedBytes, err := a.ciph.encrypt(data) - if err != nil { - return "", err - } - - apiKey = base64.StdEncoding.EncodeToString(encryptedBytes) - - return apiKey, nil -} - -func (a *authenticator) Enforce(apiKey, obj, act string) (bool, error) { - decoded, err := base64.StdEncoding.DecodeString(apiKey) - if err != nil { - a.log.Error(err, "decode token failed") - return false, err - } - - decryptedBytes, err := a.ciph.decrypt(decoded) - if err != nil { - a.log.Error(err, "decrypt token failed") - return false, err - } - - var ar authRecord - if err := json.Unmarshal(decryptedBytes, &ar); err != nil { - a.log.Error(err, "unmarshal token failed") - return false, err - } - - if time.Now().After(ar.Expiry) { - a.log.Error(nil, "token expired") - return false, ErrTokenExpired - } - - allow, err := a.enforcer.Enforce(ar.Role, obj, act) - if err != nil { - a.log.Error(err, "enforce failed") - return false, err - } - - return allow, nil -} - -type encrypter struct { - gcm cipher.AEAD -} - -func newEncrypter(key []byte) (*encrypter, error) { - hasher := md5.New() - _, err := hasher.Write(key) - if err != nil { - return nil, err - } - hash := hex.EncodeToString(hasher.Sum(nil)) - block, err := aes.NewCipher([]byte(hash)) - if err != nil { - return nil, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - return &encrypter{ - gcm: gcm, - }, nil -} - -func (e encrypter) encrypt(data []byte) ([]byte, error) { - nonce := make([]byte, e.gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - ciphertext := e.gcm.Seal(nonce, nonce, data, nil) - return ciphertext, nil -} - -func (e encrypter) decrypt(data []byte) ([]byte, error) { - nonceSize := e.gcm.NonceSize() - nonce, ciphertext := data[:nonceSize], data[nonceSize:] - plaintext, err := e.gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, err - } - return plaintext, nil -} - -func applyPolicies(e *casbin.Enforcer) error { - _, err := e.AddPolicies([][]string{ - {"consumer", "/bytes/*", "(GET)|(HEAD)"}, - {"creator", "/bytes", "POST"}, - {"consumer", "/chunks/*", "GET"}, - {"creator", "/chunks", "POST"}, - {"consumer", "/bzz/*", "GET"}, - {"creator", "/bzz/*", "PATCH"}, - {"creator", "/bzz", "POST"}, - {"creator", "/bzz?*", "POST"}, - {"consumer", "/bzz/*/*", "(GET)|(HEAD)"}, - {"creator", "/tags", "GET"}, - {"creator", "/tags?*", "GET"}, - {"creator", "/tags", "POST"}, - {"creator", "/tags/*", "(GET)|(DELETE)|(PATCH)"}, - {"creator", "/pins/*", "(GET)|(DELETE)|(POST)"}, - {"maintainer", "/pins", "GET"}, - {"creator", "/pss/send/*", "POST"}, - {"consumer", "/pss/subscribe/*", "GET"}, - {"creator", "/soc/*/*", "POST"}, - {"creator", "/feeds/*/*", "POST"}, - {"consumer", "/feeds/*/*", "GET"}, - {"maintainer", "/stamps", "GET"}, - {"maintainer", "/stamps/*", "GET"}, - {"maintainer", "/stamps/*/*", "POST"}, - {"maintainer", "/stamps/topup/*/*", "PATCH"}, - {"maintainer", "/stamps/dilute/*/*", "PATCH"}, - {"maintainer", "/stake", "(GET)|(DELETE)"}, - {"maintainer", "/stake/*", "POST"}, - {"maintainer", "/addresses", "GET"}, - {"maintainer", "/blocklist", "GET"}, - {"maintainer", "/connect/*", "POST"}, - {"maintainer", "/peers", "GET"}, - {"maintainer", "/peers/*", "DELETE"}, - {"maintainer", "/pingpong/*", "POST"}, - {"maintainer", "/topology", "GET"}, - {"maintainer", "/welcome-message", "(GET)|(POST)"}, - {"maintainer", "/balances", "GET"}, - {"maintainer", "/balances/*", "GET"}, - {"maintainer", "/accounting", "GET"}, - {"maintainer", "/chequebook/cashout/*", "GET"}, - {"accountant", "/chequebook/cashout/*", "POST"}, - {"accountant", "/chequebook/withdraw", "POST"}, - {"accountant", "/chequebook/withdraw?*", "POST"}, - {"accountant", "/chequebook/deposit", "POST"}, - {"accountant", "/chequebook/deposit?*", "POST"}, - {"maintainer", "/chequebook/cheque/*", "GET"}, - {"maintainer", "/chequebook/cheque", "GET"}, - {"maintainer", "/chequebook/address", "GET"}, - {"maintainer", "/chequebook/balance", "GET"}, - {"maintainer", "/wallet", "GET"}, - {"maintainer", "/wallet/withdraw/*", "POST"}, - {"maintainer", "/chunks/*", "(GET)|(DELETE)"}, - {"maintainer", "/reservestate", "GET"}, - {"maintainer", "/chainstate", "GET"}, - {"maintainer", "/settlements/*", "GET"}, - {"maintainer", "/settlements", "GET"}, - {"maintainer", "/transactions", "GET"}, - {"consumer", "/transactions/*", "GET"}, - {"accountant", "/transactions/*", "(POST)|(DELETE)"}, - {"consumer", "/consumed", "GET"}, - {"consumer", "/consumed/*", "GET"}, - {"consumer", "/chunks/stream", "GET"}, - {"creator", "/stewardship/*", "GET"}, - {"consumer", "/stewardship/*", "PUT"}, - {"maintainer", "/redistributionstate", "GET"}, - {"maintainer", "/debugstore", "GET"}, - {"consumer", "/rchash", "GET"}, - {"maintainer", "/debug/*", "GET"}, - {"maintainer", "/metrics", "GET"}, - {"maintainer", "/node", "GET"}, - {"maintainer", "/loggers", "GET"}, - {"maintainer", "/loggers/*", "(GET)|(PUT)"}, - {"maintainer", "/status", "GET"}, - {"maintainer", "/status/peers", "GET"}, - {"maintainer", "/rcash/*", "GET"}, - {"maintainer", "/batches", "GET"}, - {"maintainer", "/timesettlements", "GET"}, - }) - - if err != nil { - return err - } - - // consumer > creator > accountant > maintainer - _, err = e.AddGroupingPolicies([][]string{ - {"creator", "consumer"}, - {"accountant", "creator"}, - {"maintainer", "accountant"}, - }) - - return err -} diff --git a/pkg/auth/auth_test.go b/pkg/auth/auth_test.go deleted file mode 100644 index 0d107a764a2..00000000000 --- a/pkg/auth/auth_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2021 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package auth_test - -import ( - "errors" - "testing" - "time" - - "github.com/ethersphere/bee/v2/pkg/auth" - "github.com/ethersphere/bee/v2/pkg/log" -) - -const ( - encryptionKey = "mZIODMvjsiS2VdK1xgI1cOTizhGVNoVz" - passwordHash = "$2a$12$mZIODMvjsiS2VdK1xgI1cOTizhGVNoVz2Xn48H8ddFFLzX2B3lD3m" -) - -func TestAuthorize(t *testing.T) { - t.Parallel() - - a, err := auth.New(encryptionKey, passwordHash, log.Noop) - if err != nil { - t.Error(err) - } - - tt := []struct { - desc string - pass string - expected bool - }{ - { - desc: "correct credentials", - pass: "test", - expected: true, - }, { - desc: "wrong password", - pass: "notTest", - expected: false, - }, - } - for _, tC := range tt { - tC := tC - t.Run(tC.desc, func(t *testing.T) { - t.Parallel() - - res := a.Authorize(tC.pass) - if res != tC.expected { - t.Error("unexpected result", res) - } - }) - } -} - -func TestExpiry(t *testing.T) { - t.Parallel() - - const expiryDuration = time.Millisecond * 10 - - a, err := auth.New(encryptionKey, passwordHash, log.Noop) - if err != nil { - t.Error(err) - } - - key, err := a.GenerateKey("consumer", expiryDuration) - if err != nil { - t.Errorf("expected no error, got: %v", err) - } - - time.Sleep(expiryDuration * 2) - - result, err := a.Enforce(key, "/bytes/1", "GET") - if !errors.Is(err, auth.ErrTokenExpired) { - t.Errorf("expected token expired error, got: %v", err) - } - - if result { - t.Errorf("expected %v, got %v", false, result) - } -} - -func TestEnforce(t *testing.T) { - t.Parallel() - - const expiryDuration = time.Second - - a, err := auth.New(encryptionKey, passwordHash, log.Noop) - if err != nil { - t.Error(err) - } - - tt := []struct { - desc string - role, resource, action string - expected bool - }{ - { - desc: "success", - role: "maintainer", - resource: "/pingpong/someone", - action: "POST", - expected: true, - }, { - desc: "success with query param", - role: "creator", - resource: "/bzz?name=some-name", - action: "POST", - expected: true, - }, - { - desc: "bad role", - role: "consumer", - resource: "/pingpong/some-other-peer", - action: "POST", - }, - { - desc: "bad resource", - role: "maintainer", - resource: "/i-dont-exist", - action: "POST", - }, - { - desc: "bad action", - role: "maintainer", - resource: "/pingpong/someone", - action: "DELETE", - }, - } - - for _, tC := range tt { - tC := tC - t.Run(tC.desc, func(t *testing.T) { - t.Parallel() - - apiKey, err := a.GenerateKey(tC.role, expiryDuration) - - if err != nil { - t.Errorf("expected no error, got: %v", err) - } - - result, err := a.Enforce(apiKey, tC.resource, tC.action) - - if err != nil { - t.Errorf("expected no error, got: %v", err) - } - - if result != tC.expected { - t.Errorf("request from user with %s on object %s: expected %v, got %v", tC.role, tC.resource, tC.expected, result) - } - }) - } -} diff --git a/pkg/auth/handler.go b/pkg/auth/handler.go deleted file mode 100644 index 6a54b9269be..00000000000 --- a/pkg/auth/handler.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2021 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package auth - -import ( - "errors" - "net/http" - "strings" - - "github.com/ethersphere/bee/v2/pkg/jsonhttp" -) - -type auth interface { - Enforce(string, string, string) (bool, error) -} - -func PermissionCheckHandler(auth auth) func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - reqToken := r.Header.Get("Authorization") - if !strings.HasPrefix(reqToken, "Bearer ") { - jsonhttp.Forbidden(w, "Missing bearer token") - return - } - - keys := strings.Split(reqToken, "Bearer ") - - if len(keys) != 2 || strings.Trim(keys[1], " ") == "" { - jsonhttp.Unauthorized(w, "Missing security token") - return - } - - apiKey := keys[1] - - allowed, err := auth.Enforce(apiKey, r.URL.Path, r.Method) - if errors.Is(err, ErrTokenExpired) { - jsonhttp.Unauthorized(w, "Token expired") - return - } - - if err != nil { - jsonhttp.InternalServerError(w, "Error occurred while validating the security token") - return - } - - if !allowed { - jsonhttp.Forbidden(w, "Provided security token does not grant access to the resource") - return - } - - h.ServeHTTP(w, r) - }) - } -} diff --git a/pkg/auth/main_test.go b/pkg/auth/main_test.go deleted file mode 100644 index be220f06760..00000000000 --- a/pkg/auth/main_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package auth_test - -import ( - "testing" - - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m) -} diff --git a/pkg/auth/mock/auth.go b/pkg/auth/mock/auth.go deleted file mode 100644 index ef8ebd30b75..00000000000 --- a/pkg/auth/mock/auth.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mock - -import "time" - -type Auth struct { - AuthorizeFunc func(string) bool - GenerateKeyFunc func(string) (string, error) - EnforceFunc func(string, string, string) (bool, error) -} - -func (ma *Auth) Authorize(u string) bool { - if ma.AuthorizeFunc == nil { - return true - } - return ma.AuthorizeFunc(u) -} -func (ma *Auth) GenerateKey(k string, _ time.Duration) (string, error) { - if ma.GenerateKeyFunc == nil { - return "", nil - } - return ma.GenerateKeyFunc(k) -} -func (ma *Auth) RefreshKey(k string, _ time.Duration) (string, error) { - if ma.GenerateKeyFunc == nil { - return "", nil - } - return ma.GenerateKeyFunc(k) -} -func (ma *Auth) Enforce(a1 string, a2 string, a3 string) (bool, error) { - return ma.EnforceFunc(a1, a2, a3) -} diff --git a/pkg/manifest/mantaray.go b/pkg/manifest/mantaray.go index f3b86e06b66..4666a80c910 100644 --- a/pkg/manifest/mantaray.go +++ b/pkg/manifest/mantaray.go @@ -54,6 +54,10 @@ func NewMantarayManifestReference( }, nil } +func (m *mantarayManifest) Root() *mantaray.Node { + return m.trie +} + func (m *mantarayManifest) Type() string { return ManifestMantarayContentType } diff --git a/pkg/manifest/mantaray/node.go b/pkg/manifest/mantaray/node.go index 5a59c010d52..9561c299db5 100644 --- a/pkg/manifest/mantaray/node.go +++ b/pkg/manifest/mantaray/node.go @@ -16,13 +16,8 @@ const ( ) var ( - ZeroObfuscationKey []byte -) - -// nolint:gochecknoinits -func init() { ZeroObfuscationKey = make([]byte, 32) -} +) // Error used when lookup path does not match var ( diff --git a/pkg/node/devnode.go b/pkg/node/devnode.go index 1b090654295..016c762af77 100644 --- a/pkg/node/devnode.go +++ b/pkg/node/devnode.go @@ -17,9 +17,9 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" mockAccounting "github.com/ethersphere/bee/v2/pkg/accounting/mock" "github.com/ethersphere/bee/v2/pkg/api" - "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/bzz" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/feeds/factory" @@ -61,29 +61,25 @@ import ( ) type DevBee struct { - tracerCloser io.Closer - stateStoreCloser io.Closer - localstoreCloser io.Closer - apiCloser io.Closer - pssCloser io.Closer - errorLogWriter io.Writer - apiServer *http.Server - debugAPIServer *http.Server + tracerCloser io.Closer + stateStoreCloser io.Closer + localstoreCloser io.Closer + apiCloser io.Closer + pssCloser io.Closer + accesscontrolCloser io.Closer + errorLogWriter io.Writer + apiServer *http.Server } type DevOptions struct { Logger log.Logger APIAddr string - DebugAPIAddr string CORSAllowedOrigins []string DBOpenFilesLimit uint64 ReserveCapacity uint64 DBWriteBufferSize uint64 DBBlockCacheCapacity uint64 DBDisableSeeksCompaction bool - Restricted bool - TokenEncryptionKey string - AdminPasswordHash string } // NewDevBee starts the bee instance in 'development' mode @@ -141,15 +137,6 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { return nil, fmt.Errorf("blockchain address: %w", err) } - var authenticator auth.Authenticator - - if o.Restricted { - if authenticator, err = auth.New(o.TokenEncryptionKey, o.AdminPasswordHash, logger); err != nil { - return nil, fmt.Errorf("authenticator: %w", err) - } - logger.Info("starting with restricted APIs") - } - var mockTransaction = transactionmock.New(transactionmock.WithPendingTransactionsFunc(func() ([]common.Hash, error) { return []common.Hash{common.HexToHash("abcd")}, nil }), transactionmock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error { @@ -194,37 +181,6 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { } }(probe) - var debugApiService *api.Service - - if o.DebugAPIAddr != "" { - debugAPIListener, err := net.Listen("tcp", o.DebugAPIAddr) - if err != nil { - return nil, fmt.Errorf("debug api listener: %w", err) - } - - debugApiService = api.New(mockKey.PublicKey, mockKey.PublicKey, overlayEthAddress, nil, logger, mockTransaction, batchStore, api.DevMode, true, true, chainBackend, o.CORSAllowedOrigins, inmemstore.New()) - debugAPIServer := &http.Server{ - IdleTimeout: 30 * time.Second, - ReadHeaderTimeout: 3 * time.Second, - Handler: debugApiService, - ErrorLog: stdlog.New(b.errorLogWriter, "", 0), - } - - debugApiService.MountTechnicalDebug() - debugApiService.SetProbe(probe) - - go func() { - logger.Info("starting debug api server", "address", debugAPIListener.Addr()) - - if err := debugAPIServer.Serve(debugAPIListener); err != nil && !errors.Is(err, http.ErrServerClosed) { - logger.Debug("debug api server failed to start", "error", err) - logger.Error(nil, "debug api server failed to start") - } - }() - - b.debugAPIServer = debugAPIServer - } - localStore, err := storer.New(context.Background(), "", &storer.Options{ Logger: logger, CacheCapacity: 1_000_000, @@ -234,6 +190,11 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { } b.localstoreCloser = localStore + session := accesscontrol.NewDefaultSession(mockKey) + actLogic := accesscontrol.NewLogic(session) + accesscontrol := accesscontrol.NewController(actLogic) + b.accesscontrolCloser = accesscontrol + pssService := pss.New(mockKey, logger) b.pssCloser = pssService @@ -383,6 +344,7 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { Pss: pssService, FeedFactory: mockFeeds, Post: post, + AccessControl: accesscontrol, PostageContract: postageContract, Staking: mockStaking, Steward: mockSteward, @@ -400,30 +362,17 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { apiService := api.New(mockKey.PublicKey, mockKey.PublicKey, overlayEthAddress, nil, logger, mockTransaction, batchStore, api.DevMode, true, true, chainBackend, o.CORSAllowedOrigins, inmemstore.New()) - apiService.Configure(signer, authenticator, tracer, api.Options{ + apiService.Configure(signer, tracer, api.Options{ CORSAllowedOrigins: o.CORSAllowedOrigins, WsPingPeriod: 60 * time.Second, - Restricted: o.Restricted, }, debugOpts, 1, erc20) apiService.MountTechnicalDebug() + apiService.MountDebug() apiService.MountAPI() - apiService.SetProbe(probe) + apiService.SetProbe(probe) apiService.SetP2P(p2ps) apiService.SetSwarmAddress(&swarmAddress) - apiService.MountDebug() - - if o.DebugAPIAddr != "" { - debugApiService.SetP2P(p2ps) - debugApiService.SetSwarmAddress(&swarmAddress) - debugApiService.MountDebug() - - debugApiService.Configure(signer, authenticator, tracer, api.Options{ - CORSAllowedOrigins: o.CORSAllowedOrigins, - WsPingPeriod: 60 * time.Second, - Restricted: o.Restricted, - }, debugOpts, 1, erc20) - } apiListener, err := net.Listen("tcp", o.APIAddr) if err != nil { @@ -477,20 +426,12 @@ func (b *DevBee) Shutdown() error { return nil }) } - if b.debugAPIServer != nil { - eg.Go(func() error { - if err := b.debugAPIServer.Shutdown(ctx); err != nil { - return fmt.Errorf("debug api server: %w", err) - } - return nil - }) - } - if err := eg.Wait(); err != nil { mErr = multierror.Append(mErr, err) } tryClose(b.pssCloser, "pss") + tryClose(b.accesscontrolCloser, "accesscontrol") tryClose(b.tracerCloser, "tracer") tryClose(b.stateStoreCloser, "statestore") tryClose(b.localstoreCloser, "localstore") diff --git a/pkg/node/node.go b/pkg/node/node.go index fa3f3f861b5..198b02ad6cb 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -24,10 +24,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/accounting" "github.com/ethersphere/bee/v2/pkg/addressbook" "github.com/ethersphere/bee/v2/pkg/api" - "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/config" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/feeds/factory" @@ -89,7 +89,6 @@ type Bee struct { ctxCancel context.CancelFunc apiCloser io.Closer apiServer *http.Server - debugAPIServer *http.Server resolverCloser io.Closer errorLogWriter io.Writer tracerCloser io.Closer @@ -117,6 +116,7 @@ type Bee struct { shutdownInProgress bool shutdownMutex sync.Mutex syncingStopped *syncutil.Signaler + accesscontrolCloser io.Closer } type Options struct { @@ -127,7 +127,6 @@ type Options struct { DBBlockCacheCapacity uint64 DBDisableSeeksCompaction bool APIAddr string - DebugAPIAddr string Addr string NATAddr string EnableWS bool @@ -164,9 +163,6 @@ type Options struct { MutexProfile bool StaticNodes []swarm.Address AllowPrivateCIDRs bool - Restricted bool - TokenEncryptionKey string - AdminPasswordHash string UsePostageSnapshot bool EnableStorageIncentives bool StatestoreCacheCapacity uint64 @@ -202,6 +198,7 @@ func NewBee( logger log.Logger, libp2pPrivateKey, pssPrivateKey *ecdsa.PrivateKey, + session accesscontrol.Session, o *Options, ) (b *Bee, err error) { tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ @@ -370,16 +367,6 @@ func NewBee( b.transactionCloser = tracerCloser b.transactionMonitorCloser = transactionMonitor - var authenticator auth.Authenticator - - if o.Restricted { - if authenticator, err = auth.New(o.TokenEncryptionKey, o.AdminPasswordHash, logger); err != nil { - return nil, fmt.Errorf("authenticator: %w", err) - } - logger.Info("starting with restricted APIs") - } - - // set up basic debug api endpoints for debugging and /health endpoint beeNodeMode := api.LightMode if o.FullNodeMode { beeNodeMode = api.FullMode @@ -405,59 +392,6 @@ func NewBee( b.stamperStoreCloser = stamperStore var apiService *api.Service - var debugService *api.Service - - if o.DebugAPIAddr != "" { - if o.MutexProfile { - _ = runtime.SetMutexProfileFraction(1) - } - - if o.BlockProfile { - runtime.SetBlockProfileRate(1) - } - - debugAPIListener, err := net.Listen("tcp", o.DebugAPIAddr) - if err != nil { - return nil, fmt.Errorf("debug api listener: %w", err) - } - - debugService = api.New( - *publicKey, - pssPrivateKey.PublicKey, - overlayEthAddress, - o.WhitelistedWithdrawalAddress, - logger, - transactionService, - batchStore, - beeNodeMode, - o.ChequebookEnable, - o.SwapEnable, - chainBackend, - o.CORSAllowedOrigins, - stamperStore, - ) - debugService.Restricted = o.Restricted - debugService.MountTechnicalDebug() - debugService.SetProbe(probe) - - debugAPIServer := &http.Server{ - IdleTimeout: 30 * time.Second, - ReadHeaderTimeout: 3 * time.Second, - Handler: debugService, - ErrorLog: stdlog.New(b.errorLogWriter, "", 0), - } - - go func() { - logger.Info("starting debug server", "address", debugAPIListener.Addr()) - - if err := debugAPIServer.Serve(debugAPIListener); err != nil && !errors.Is(err, http.ErrServerClosed) { - logger.Debug("debug api server failed to start", "error", err) - logger.Error(nil, "debug api server failed to start") - } - }() - - b.debugAPIServer = debugAPIServer - } if o.APIAddr != "" { if o.MutexProfile { @@ -487,7 +421,6 @@ func NewBee( o.CORSAllowedOrigins, stamperStore, ) - apiService.Restricted = o.Restricted apiService.MountTechnicalDebug() apiService.SetProbe(probe) @@ -635,9 +568,7 @@ func NewBee( var registry *promc.Registry - if debugService != nil { - registry = debugService.MetricsRegistry() - } else if apiService != nil { + if apiService != nil { registry = apiService.MetricsRegistry() } @@ -778,6 +709,10 @@ func NewBee( b.localstoreCloser = localStore evictFn = func(id []byte) error { return localStore.EvictBatch(context.Background(), id) } + actLogic := accesscontrol.NewLogic(session) + accesscontrol := accesscontrol.NewController(actLogic) + b.accesscontrolCloser = accesscontrol + var ( syncErr atomic.Value syncStatus atomic.Value @@ -1093,6 +1028,7 @@ func NewBee( Pss: pssService, FeedFactory: feedFactory, Post: post, + AccessControl: accesscontrol, PostageContract: postageStampContractService, Staking: stakingContract, Steward: steward, @@ -1147,79 +1083,18 @@ func NewBee( apiService.MustRegisterMetrics(swapService.Metrics()...) } - apiService.Configure(signer, authenticator, tracer, api.Options{ + apiService.Configure(signer, tracer, api.Options{ CORSAllowedOrigins: o.CORSAllowedOrigins, WsPingPeriod: 60 * time.Second, - Restricted: o.Restricted, }, extraOpts, chainID, erc20Service) - apiService.MountAPI() apiService.MountDebug() + apiService.MountAPI() apiService.SetSwarmAddress(&swarmAddress) apiService.SetRedistributionAgent(agent) } - if o.DebugAPIAddr != "" { - // register metrics from components - debugService.MustRegisterMetrics(p2ps.Metrics()...) - debugService.MustRegisterMetrics(pingPong.Metrics()...) - debugService.MustRegisterMetrics(acc.Metrics()...) - debugService.MustRegisterMetrics(localStore.Metrics()...) - debugService.MustRegisterMetrics(kad.Metrics()...) - debugService.MustRegisterMetrics(saludService.Metrics()...) - debugService.MustRegisterMetrics(stateStoreMetrics.Metrics()...) - - if pullerService != nil { - debugService.MustRegisterMetrics(pullerService.Metrics()...) - } - - if agent != nil { - debugService.MustRegisterMetrics(agent.Metrics()...) - } - - debugService.MustRegisterMetrics(pushSyncProtocol.Metrics()...) - debugService.MustRegisterMetrics(pusherService.Metrics()...) - debugService.MustRegisterMetrics(pullSyncProtocol.Metrics()...) - debugService.MustRegisterMetrics(retrieval.Metrics()...) - debugService.MustRegisterMetrics(lightNodes.Metrics()...) - debugService.MustRegisterMetrics(hive.Metrics()...) - - if bs, ok := batchStore.(metrics.Collector); ok { - debugService.MustRegisterMetrics(bs.Metrics()...) - } - if ls, ok := eventListener.(metrics.Collector); ok { - debugService.MustRegisterMetrics(ls.Metrics()...) - } - if pssServiceMetrics, ok := pssService.(metrics.Collector); ok { - debugService.MustRegisterMetrics(pssServiceMetrics.Metrics()...) - } - if swapBackendMetrics, ok := chainBackend.(metrics.Collector); ok { - debugService.MustRegisterMetrics(swapBackendMetrics.Metrics()...) - } - if apiService != nil { - debugService.MustRegisterMetrics(apiService.Metrics()...) - } - if l, ok := logger.(metrics.Collector); ok { - debugService.MustRegisterMetrics(l.Metrics()...) - } - debugService.MustRegisterMetrics(pseudosettleService.Metrics()...) - if swapService != nil { - debugService.MustRegisterMetrics(swapService.Metrics()...) - } - - debugService.Configure(signer, authenticator, tracer, api.Options{ - CORSAllowedOrigins: o.CORSAllowedOrigins, - WsPingPeriod: 60 * time.Second, - Restricted: o.Restricted, - }, extraOpts, chainID, erc20Service) - - debugService.SetP2P(p2ps) - debugService.SetSwarmAddress(&swarmAddress) - debugService.MountDebug() - debugService.SetRedistributionAgent(agent) - } - if err := kad.Start(ctx); err != nil { return nil, err } @@ -1283,15 +1158,6 @@ func (b *Bee) Shutdown() error { return nil }) } - if b.debugAPIServer != nil { - eg.Go(func() error { - if err := b.debugAPIServer.Shutdown(ctx); err != nil { - return fmt.Errorf("debug api server: %w", err) - } - return nil - }) - } - if err := eg.Wait(); err != nil { mErr = multierror.Append(mErr, err) } @@ -1355,6 +1221,7 @@ func (b *Bee) Shutdown() error { c() } + tryClose(b.accesscontrolCloser, "accesscontrol") tryClose(b.tracerCloser, "tracer") tryClose(b.topologyCloser, "topology driver") tryClose(b.storageIncetivesCloser, "storage incentives agent") diff --git a/pkg/pushsync/pushsync.go b/pkg/pushsync/pushsync.go index 90af7cfe8eb..3feeed67bf4 100644 --- a/pkg/pushsync/pushsync.go +++ b/pkg/pushsync/pushsync.go @@ -339,7 +339,7 @@ func (ps *PushSync) pushToClosest(ctx context.Context, ch swarm.Chunk, origin bo resultChan := make(chan receiptResult) - retryC := make(chan struct{}, parallelForwards) + retryC := make(chan struct{}, max(1, parallelForwards)) retry := func() { select { diff --git a/pkg/retrieval/retrieval.go b/pkg/retrieval/retrieval.go index 0bb0668f700..53452cec757 100644 --- a/pkg/retrieval/retrieval.go +++ b/pkg/retrieval/retrieval.go @@ -263,7 +263,7 @@ func (s *Service) RetrieveChunk(ctx context.Context, chunkAddr, sourcePeerAddr s go func() { span, _, ctx := s.tracer.FollowSpanFromContext(spanCtx, "retrieve-chunk", s.logger, opentracing.Tag{Key: "address", Value: chunkAddr.String()}) defer span.Finish() - s.retrieveChunk(ctx, quit, chunkAddr, peer, resultC, action, origin, span) + s.retrieveChunk(ctx, quit, chunkAddr, peer, resultC, action, span) }() case res := <-resultC: @@ -297,7 +297,7 @@ func (s *Service) RetrieveChunk(ctx context.Context, chunkAddr, sourcePeerAddr s return v, nil } -func (s *Service) retrieveChunk(ctx context.Context, quit chan struct{}, chunkAddr, peer swarm.Address, result chan retrievalResult, action accounting.Action, isOrigin bool, span opentracing.Span) { +func (s *Service) retrieveChunk(ctx context.Context, quit chan struct{}, chunkAddr, peer swarm.Address, result chan retrievalResult, action accounting.Action, span opentracing.Span) { var ( startTime = time.Now() diff --git a/pkg/soc/testing/soc.go b/pkg/soc/testing/soc.go index e5325f464b1..20bfe74c9cd 100644 --- a/pkg/soc/testing/soc.go +++ b/pkg/soc/testing/soc.go @@ -5,6 +5,7 @@ package testing import ( + "crypto/ecdsa" "testing" "github.com/ethersphere/bee/v2/pkg/cac" @@ -70,3 +71,38 @@ func GenerateMockSOC(t *testing.T, data []byte) *MockSOC { WrappedChunk: ch, } } + +// GenerateMockSOCWithKey generates a valid mocked SOC from given data and key. +func GenerateMockSOCWithKey(t *testing.T, data []byte, privKey *ecdsa.PrivateKey) *MockSOC { + t.Helper() + + signer := crypto.NewDefaultSigner(privKey) + owner, err := signer.EthereumAddress() + if err != nil { + t.Fatal(err) + } + + ch, err := cac.New(data) + if err != nil { + t.Fatal(err) + } + + id := make([]byte, swarm.HashSize) + hasher := swarm.NewHasher() + _, err = hasher.Write(append(id, ch.Address().Bytes()...)) + if err != nil { + t.Fatal(err) + } + + signature, err := signer.Sign(hasher.Sum(nil)) + if err != nil { + t.Fatal(err) + } + + return &MockSOC{ + ID: id, + Owner: owner.Bytes(), + Signature: signature, + WrappedChunk: ch, + } +} diff --git a/pkg/topology/kademlia/doc.go b/pkg/topology/kademlia/doc.go index 39fb7c25624..2407d289535 100644 --- a/pkg/topology/kademlia/doc.go +++ b/pkg/topology/kademlia/doc.go @@ -10,7 +10,7 @@ A thorough explanation of the logic in the `manage()` forever loop: The `manageC` channel gets triggered every time there's a change in the information regarding peers we know about. This can be a result of: (1) A peer has disconnected from us (2) A peer has been added to the list of -known peers (from discovery, debugapi, bootnode flag or just because it +known peers (from discovery, api, bootnode flag or just because it was persisted in the address book and the node has been restarted). So the information has been changed, and potentially upon disconnection,