Commit e8752f4e authored by Elad's avatar Elad Committed by Balint Gabor

cmd/swarm, swarm: added access control functionality (#17404)

Co-authored-by: 's avatarJanos Guljas <janos@resenje.org>
Co-authored-by: 's avatarAnton Evangelatov <anton.evangelatov@gmail.com>
Co-authored-by: 's avatarBalint Gabor <balint.g@gmail.com>
parent 040aa2bb
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/api/client"
"gopkg.in/urfave/cli.v1"
)
var salt = make([]byte, 32)
func init() {
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
}
func accessNewPass(ctx *cli.Context) {
args := ctx.Args()
if len(args) != 1 {
utils.Fatalf("Expected 1 argument - the ref")
}
var (
ae *api.AccessEntry
accessKey []byte
err error
ref = args[0]
password = getPassPhrase("", 0, makePasswordList(ctx))
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
accessKey, ae, err = api.DoPasswordNew(ctx, password, salt)
if err != nil {
utils.Fatalf("error getting session key: %v", err)
}
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
if dryRun {
err = printManifests(m, nil)
if err != nil {
utils.Fatalf("had an error printing the manifests: %v", err)
}
} else {
utils.Fatalf("uploading manifests")
err = uploadManifests(ctx, m, nil)
if err != nil {
utils.Fatalf("had an error uploading the manifests: %v", err)
}
}
}
func accessNewPK(ctx *cli.Context) {
args := ctx.Args()
if len(args) != 1 {
utils.Fatalf("Expected 1 argument - the ref")
}
var (
ae *api.AccessEntry
sessionKey []byte
err error
ref = args[0]
privateKey = getPrivKey(ctx)
granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name)
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
sessionKey, ae, err = api.DoPKNew(ctx, privateKey, granteePublicKey, salt)
if err != nil {
utils.Fatalf("error getting session key: %v", err)
}
m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
if dryRun {
err = printManifests(m, nil)
if err != nil {
utils.Fatalf("had an error printing the manifests: %v", err)
}
} else {
err = uploadManifests(ctx, m, nil)
if err != nil {
utils.Fatalf("had an error uploading the manifests: %v", err)
}
}
}
func accessNewACT(ctx *cli.Context) {
args := ctx.Args()
if len(args) != 1 {
utils.Fatalf("Expected 1 argument - the ref")
}
var (
ae *api.AccessEntry
actManifest *api.Manifest
accessKey []byte
err error
ref = args[0]
grantees = []string{}
actFilename = ctx.String(SwarmAccessGrantKeysFlag.Name)
privateKey = getPrivKey(ctx)
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
bytes, err := ioutil.ReadFile(actFilename)
if err != nil {
utils.Fatalf("had an error reading the grantee public key list")
}
grantees = strings.Split(string(bytes), "\n")
accessKey, ae, actManifest, err = api.DoACTNew(ctx, privateKey, salt, grantees)
if err != nil {
utils.Fatalf("error generating ACT manifest: %v", err)
}
if err != nil {
utils.Fatalf("error getting session key: %v", err)
}
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
if err != nil {
utils.Fatalf("error generating root access manifest: %v", err)
}
if dryRun {
err = printManifests(m, actManifest)
if err != nil {
utils.Fatalf("had an error printing the manifests: %v", err)
}
} else {
err = uploadManifests(ctx, m, actManifest)
if err != nil {
utils.Fatalf("had an error uploading the manifests: %v", err)
}
}
}
func printManifests(rootAccessManifest, actManifest *api.Manifest) error {
js, err := json.Marshal(rootAccessManifest)
if err != nil {
return err
}
fmt.Println(string(js))
if actManifest != nil {
js, err := json.Marshal(actManifest)
if err != nil {
return err
}
fmt.Println(string(js))
}
return nil
}
func uploadManifests(ctx *cli.Context, rootAccessManifest, actManifest *api.Manifest) error {
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client := client.NewClient(bzzapi)
var (
key string
err error
)
if actManifest != nil {
key, err = client.UploadManifest(actManifest, false)
if err != nil {
return err
}
rootAccessManifest.Entries[0].Access.Act = key
}
key, err = client.UploadManifest(rootAccessManifest, false)
if err != nil {
return err
}
fmt.Println(key)
return nil
}
// makePasswordList reads password lines from the file specified by the global --password flag
// and also by the same subcommand --password flag.
// This function ia a fork of utils.MakePasswordList to lookup cli context for subcommand.
// Function ctx.SetGlobal is not setting the global flag value that can be accessed
// by ctx.GlobalString using the current version of cli package.
func makePasswordList(ctx *cli.Context) []string {
path := ctx.GlobalString(utils.PasswordFileFlag.Name)
if path == "" {
path = ctx.String(utils.PasswordFileFlag.Name)
if path == "" {
return nil
}
}
text, err := ioutil.ReadFile(path)
if err != nil {
utils.Fatalf("Failed to read password file: %v", err)
}
lines := strings.Split(string(text), "\n")
// Sanitise DOS line endings.
for i := range lines {
lines[i] = strings.TrimRight(lines[i], "\r")
}
return lines
}
This diff is collapsed.
...@@ -78,6 +78,7 @@ const ( ...@@ -78,6 +78,7 @@ const (
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH" SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY" SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY" SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
GETH_ENV_DATADIR = "GETH_DATADIR" GETH_ENV_DATADIR = "GETH_DATADIR"
) )
......
...@@ -68,18 +68,36 @@ func download(ctx *cli.Context) { ...@@ -68,18 +68,36 @@ func download(ctx *cli.Context) {
utils.Fatalf("could not parse uri argument: %v", err) utils.Fatalf("could not parse uri argument: %v", err)
} }
dl := func(credentials string) error {
// assume behaviour according to --recursive switch // assume behaviour according to --recursive switch
if isRecursive { if isRecursive {
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest); err != nil { if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil {
utils.Fatalf("encoutered an error while downloading directory: %v", err) if err == swarm.ErrUnauthorized {
return err
}
return fmt.Errorf("directory %s: %v", uri.Path, err)
} }
} else { } else {
// we are downloading a file // we are downloading a file
log.Debug(fmt.Sprintf("downloading file/path from a manifest. hash: %s, path:%s", uri.Addr, uri.Path)) log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path)
err := client.DownloadFile(uri.Addr, uri.Path, dest) err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials)
if err != nil { if err != nil {
utils.Fatalf("could not download %s from given address: %s. error: %v", uri.Path, uri.Addr, err) if err == swarm.ErrUnauthorized {
return err
}
return fmt.Errorf("file %s from address: %s: %v", uri.Path, uri.Addr, err)
}
}
return nil
} }
if passwords := makePasswordList(ctx); passwords != nil {
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
err = dl(password)
} else {
err = dl("")
}
if err != nil {
utils.Fatalf("download: %v", err)
} }
} }
...@@ -44,7 +44,7 @@ func list(ctx *cli.Context) { ...@@ -44,7 +44,7 @@ func list(ctx *cli.Context) {
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client := swarm.NewClient(bzzapi) client := swarm.NewClient(bzzapi)
list, err := client.List(manifest, prefix) list, err := client.List(manifest, prefix, "")
if err != nil { if err != nil {
utils.Fatalf("Failed to generate file and directory list: %s", err) utils.Fatalf("Failed to generate file and directory list: %s", err)
} }
......
...@@ -155,6 +155,14 @@ var ( ...@@ -155,6 +155,14 @@ var (
Name: "defaultpath", Name: "defaultpath",
Usage: "path to file served for empty url path (none)", Usage: "path to file served for empty url path (none)",
} }
SwarmAccessGrantKeyFlag = cli.StringFlag{
Name: "grant-key",
Usage: "grants a given public key access to an ACT",
}
SwarmAccessGrantKeysFlag = cli.StringFlag{
Name: "grant-keys",
Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
}
SwarmUpFromStdinFlag = cli.BoolFlag{ SwarmUpFromStdinFlag = cli.BoolFlag{
Name: "stdin", Name: "stdin",
Usage: "reads data to be uploaded from stdin", Usage: "reads data to be uploaded from stdin",
...@@ -167,6 +175,15 @@ var ( ...@@ -167,6 +175,15 @@ var (
Name: "encrypt", Name: "encrypt",
Usage: "use encrypted upload", Usage: "use encrypted upload",
} }
SwarmAccessPasswordFlag = cli.StringFlag{
Name: "password",
Usage: "Password",
EnvVar: SWARM_ACCESS_PASSWORD,
}
SwarmDryRunFlag = cli.BoolFlag{
Name: "dry-run",
Usage: "dry-run",
}
CorsStringFlag = cli.StringFlag{ CorsStringFlag = cli.StringFlag{
Name: "corsdomain", Name: "corsdomain",
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')", Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
...@@ -252,6 +269,61 @@ func init() { ...@@ -252,6 +269,61 @@ func init() {
Flags: []cli.Flag{SwarmEncryptedFlag}, Flags: []cli.Flag{SwarmEncryptedFlag},
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash", Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
}, },
{
CustomHelpTemplate: helpTemplate,
Name: "access",
Usage: "encrypts a reference and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root manifest",
Subcommands: []cli.Command{
{
CustomHelpTemplate: helpTemplate,
Name: "new",
Usage: "encrypts a reference and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
Subcommands: []cli.Command{
{
Action: accessNewPass,
CustomHelpTemplate: helpTemplate,
Flags: []cli.Flag{
utils.PasswordFileFlag,
SwarmDryRunFlag,
},
Name: "pass",
Usage: "encrypts a reference with a password and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
},
{
Action: accessNewPK,
CustomHelpTemplate: helpTemplate,
Flags: []cli.Flag{
utils.PasswordFileFlag,
SwarmDryRunFlag,
SwarmAccessGrantKeyFlag,
},
Name: "pk",
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
},
{
Action: accessNewACT,
CustomHelpTemplate: helpTemplate,
Flags: []cli.Flag{
SwarmAccessGrantKeysFlag,
SwarmDryRunFlag,
},
Name: "act",
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
},
},
},
},
},
{ {
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
Name: "resource", Name: "resource",
...@@ -306,14 +378,11 @@ func init() { ...@@ -306,14 +378,11 @@ func init() {
{ {
Action: download, Action: download,
Name: "down", Name: "down",
Flags: []cli.Flag{SwarmRecursiveFlag}, Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
Usage: "downloads a swarm manifest or a file inside a manifest", Usage: "downloads a swarm manifest or a file inside a manifest",
ArgsUsage: " <uri> [<dir>]", ArgsUsage: " <uri> [<dir>]",
Description: ` Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.
`,
}, },
{ {
Name: "manifest", Name: "manifest",
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
...@@ -413,16 +482,14 @@ pv(1) tool to get a progress bar: ...@@ -413,16 +482,14 @@ pv(1) tool to get a progress bar:
Name: "import", Name: "import",
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)", Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
ArgsUsage: "<chunkdb> <file>", ArgsUsage: "<chunkdb> <file>",
Description: ` Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
Import chunks from a tar archive into a local chunk database (use - to read from stdin).
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
The import may be quite large, consider piping the input through the Unix The import may be quite large, consider piping the input through the Unix
pv(1) tool to get a progress bar: pv(1) tool to get a progress bar:
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks - pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
`,
}, },
{ {
Action: dbClean, Action: dbClean,
...@@ -535,6 +602,7 @@ func version(ctx *cli.Context) error { ...@@ -535,6 +602,7 @@ func version(ctx *cli.Context) error {
func bzzd(ctx *cli.Context) error { func bzzd(ctx *cli.Context) error {
//build a valid bzzapi.Config from all available sources: //build a valid bzzapi.Config from all available sources:
//default config, file config, command line and env vars //default config, file config, command line and env vars
bzzconfig, err := buildConfig(ctx) bzzconfig, err := buildConfig(ctx)
if err != nil { if err != nil {
utils.Fatalf("unable to configure swarm: %v", err) utils.Fatalf("unable to configure swarm: %v", err)
...@@ -557,6 +625,7 @@ func bzzd(ctx *cli.Context) error { ...@@ -557,6 +625,7 @@ func bzzd(ctx *cli.Context) error {
if err != nil { if err != nil {
utils.Fatalf("can't create node: %v", err) utils.Fatalf("can't create node: %v", err)
} }
//a few steps need to be done after the config phase is completed, //a few steps need to be done after the config phase is completed,
//due to overriding behavior //due to overriding behavior
initSwarmNode(bzzconfig, stack, ctx) initSwarmNode(bzzconfig, stack, ctx)
......
...@@ -18,10 +18,12 @@ package main ...@@ -18,10 +18,12 @@ package main
import ( import (
"context" "context"
"crypto/ecdsa"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync" "sync"
...@@ -181,6 +183,7 @@ type testNode struct { ...@@ -181,6 +183,7 @@ type testNode struct {
Enode string Enode string
Dir string Dir string
IpcPath string IpcPath string
PrivateKey *ecdsa.PrivateKey
Client *rpc.Client Client *rpc.Client
Cmd *cmdtest.TestCmd Cmd *cmdtest.TestCmd
} }
...@@ -289,7 +292,11 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode { ...@@ -289,7 +292,11 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
func newTestNode(t *testing.T, dir string) *testNode { func newTestNode(t *testing.T, dir string) *testNode {
conf, account := getTestAccount(t, dir) conf, account := getTestAccount(t, dir)
node := &testNode{Dir: dir} ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
node := &testNode{Dir: dir, PrivateKey: pk}
// assign ports // assign ports
ports, err := getAvailableTCPPorts(2) ports, err := getAvailableTCPPorts(2)
......
This diff is collapsed.
This diff is collapsed.
...@@ -19,6 +19,7 @@ package api ...@@ -19,6 +19,7 @@ package api
import ( import (
"context" "context"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
...@@ -28,10 +29,17 @@ import ( ...@@ -28,10 +29,17 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/sctx"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
func init() {
loglevel := flag.Int("loglevel", 2, "loglevel")
flag.Parse()
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
}
func testAPI(t *testing.T, f func(*API, bool)) { func testAPI(t *testing.T, f func(*API, bool)) {
datadir, err := ioutil.TempDir("", "bzz-test") datadir, err := ioutil.TempDir("", "bzz-test")
if err != nil { if err != nil {
...@@ -42,7 +50,7 @@ func testAPI(t *testing.T, f func(*API, bool)) { ...@@ -42,7 +50,7 @@ func testAPI(t *testing.T, f func(*API, bool)) {
if err != nil { if err != nil {
return return
} }
api := NewAPI(fileStore, nil, nil) api := NewAPI(fileStore, nil, nil, nil)
f(api, false) f(api, false)
f(api, true) f(api, true)
} }
...@@ -85,7 +93,7 @@ func expResponse(content string, mimeType string, status int) *Response { ...@@ -85,7 +93,7 @@ func expResponse(content string, mimeType string, status int) *Response {
func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse { func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse {
addr := storage.Address(common.Hex2Bytes(bzzhash)) addr := storage.Address(common.Hex2Bytes(bzzhash))
reader, mimeType, status, _, err := api.Get(context.TODO(), addr, path) reader, mimeType, status, _, err := api.Get(context.TODO(), NOOPDecrypt, addr, path)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
...@@ -229,7 +237,7 @@ func TestAPIResolve(t *testing.T) { ...@@ -229,7 +237,7 @@ func TestAPIResolve(t *testing.T) {
if x.immutable { if x.immutable {
uri.Scheme = "bzz-immutable" uri.Scheme = "bzz-immutable"
} }
res, err := api.Resolve(context.TODO(), uri) res, err := api.ResolveURI(context.TODO(), uri, "")
if err == nil { if err == nil {
if x.expectErr != nil { if x.expectErr != nil {
t.Fatalf("expected error %q, got result %q", x.expectErr, res) t.Fatalf("expected error %q, got result %q", x.expectErr, res)
...@@ -373,3 +381,55 @@ func TestMultiResolver(t *testing.T) { ...@@ -373,3 +381,55 @@ func TestMultiResolver(t *testing.T) {
}) })
} }
} }
func TestDecryptOriginForbidden(t *testing.T) {
ctx := context.TODO()
ctx = sctx.SetHost(ctx, "swarm-gateways.net")
me := &ManifestEntry{
Access: &AccessEntry{Type: AccessTypePass},
}
api := NewAPI(nil, nil, nil, nil)
f := api.Decryptor(ctx, "")
err := f(me)
if err != ErrDecryptDomainForbidden {
t.Fatalf("should fail with ErrDecryptDomainForbidden, got %v", err)
}
}
func TestDecryptOrigin(t *testing.T) {
for _, v := range []struct {
host string
expectError error
}{
{
host: "localhost",
expectError: ErrDecrypt,
},
{
host: "127.0.0.1",
expectError: ErrDecrypt,
},
{
host: "swarm-gateways.net",
expectError: ErrDecryptDomainForbidden,
},
} {
ctx := context.TODO()
ctx = sctx.SetHost(ctx, v.host)
me := &ManifestEntry{
Access: &AccessEntry{Type: AccessTypePass},
}
api := NewAPI(nil, nil, nil, nil)
f := api.Decryptor(ctx, "")
err := f(me)
if err != v.expectError {
t.Fatalf("should fail with %v, got %v", v.expectError, err)
}
}
}
...@@ -43,6 +43,10 @@ var ( ...@@ -43,6 +43,10 @@ var (
DefaultClient = NewClient(DefaultGateway) DefaultClient = NewClient(DefaultGateway)
) )
var (
ErrUnauthorized = errors.New("unauthorized")
)
func NewClient(gateway string) *Client { func NewClient(gateway string) *Client {
return &Client{ return &Client{
Gateway: gateway, Gateway: gateway,
...@@ -188,7 +192,7 @@ func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bo ...@@ -188,7 +192,7 @@ func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bo
// DownloadDirectory downloads the files contained in a swarm manifest under // DownloadDirectory downloads the files contained in a swarm manifest under
// the given path into a local directory (existing files will be overwritten) // the given path into a local directory (existing files will be overwritten)
func (c *Client) DownloadDirectory(hash, path, destDir string) error { func (c *Client) DownloadDirectory(hash, path, destDir, credentials string) error {
stat, err := os.Stat(destDir) stat, err := os.Stat(destDir)
if err != nil { if err != nil {
return err return err
...@@ -201,13 +205,20 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error { ...@@ -201,13 +205,20 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error {
if err != nil { if err != nil {
return err return err
} }
if credentials != "" {
req.SetBasicAuth("", credentials)
}
req.Header.Set("Accept", "application/x-tar") req.Header.Set("Accept", "application/x-tar")
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != http.StatusOK { switch res.StatusCode {
case http.StatusOK:
case http.StatusUnauthorized:
return ErrUnauthorized
default:
return fmt.Errorf("unexpected HTTP status: %s", res.Status) return fmt.Errorf("unexpected HTTP status: %s", res.Status)
} }
tr := tar.NewReader(res.Body) tr := tar.NewReader(res.Body)
...@@ -248,7 +259,7 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error { ...@@ -248,7 +259,7 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error {
// DownloadFile downloads a single file into the destination directory // DownloadFile downloads a single file into the destination directory
// if the manifest entry does not specify a file name - it will fallback // if the manifest entry does not specify a file name - it will fallback
// to the hash of the file as a filename // to the hash of the file as a filename
func (c *Client) DownloadFile(hash, path, dest string) error { func (c *Client) DownloadFile(hash, path, dest, credentials string) error {
hasDestinationFilename := false hasDestinationFilename := false
if stat, err := os.Stat(dest); err == nil { if stat, err := os.Stat(dest); err == nil {
hasDestinationFilename = !stat.IsDir() hasDestinationFilename = !stat.IsDir()
...@@ -261,9 +272,9 @@ func (c *Client) DownloadFile(hash, path, dest string) error { ...@@ -261,9 +272,9 @@ func (c *Client) DownloadFile(hash, path, dest string) error {
} }
} }
manifestList, err := c.List(hash, path) manifestList, err := c.List(hash, path, credentials)
if err != nil { if err != nil {
return fmt.Errorf("could not list manifest: %v", err) return err
} }
switch len(manifestList.Entries) { switch len(manifestList.Entries) {
...@@ -280,13 +291,19 @@ func (c *Client) DownloadFile(hash, path, dest string) error { ...@@ -280,13 +291,19 @@ func (c *Client) DownloadFile(hash, path, dest string) error {
if err != nil { if err != nil {
return err return err
} }
if credentials != "" {
req.SetBasicAuth("", credentials)
}
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err
} }
defer res.Body.Close() defer res.Body.Close()
switch res.StatusCode {
if res.StatusCode != http.StatusOK { case http.StatusOK:
case http.StatusUnauthorized:
return ErrUnauthorized
default:
return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode) return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode)
} }
filename := "" filename := ""
...@@ -367,13 +384,24 @@ func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) { ...@@ -367,13 +384,24 @@ func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) {
// - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt] // - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt]
// //
// where entries ending with "/" are common prefixes. // where entries ending with "/" are common prefixes.
func (c *Client) List(hash, prefix string) (*api.ManifestList, error) { func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) {
res, err := http.DefaultClient.Get(c.Gateway + "/bzz-list:/" + hash + "/" + prefix) req, err := http.NewRequest(http.MethodGet, c.Gateway+"/bzz-list:/"+hash+"/"+prefix, nil)
if err != nil {
return nil, err
}
if credentials != "" {
req.SetBasicAuth("", credentials)
}
res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != http.StatusOK { switch res.StatusCode {
case http.StatusOK:
case http.StatusUnauthorized:
return nil, ErrUnauthorized
default:
return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status)
} }
var list api.ManifestList var list api.ManifestList
......
...@@ -228,7 +228,7 @@ func TestClientUploadDownloadDirectory(t *testing.T) { ...@@ -228,7 +228,7 @@ func TestClientUploadDownloadDirectory(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
if err := client.DownloadDirectory(hash, "", tmp); err != nil { if err := client.DownloadDirectory(hash, "", tmp, ""); err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, file := range testDirFiles { for _, file := range testDirFiles {
...@@ -265,7 +265,7 @@ func testClientFileList(toEncrypt bool, t *testing.T) { ...@@ -265,7 +265,7 @@ func testClientFileList(toEncrypt bool, t *testing.T) {
} }
ls := func(prefix string) []string { ls := func(prefix string) []string {
list, err := client.List(hash, prefix) list, err := client.List(hash, prefix, "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"encoding/binary"
"errors"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/swarm/storage/encryption"
)
type RefEncryption struct {
spanEncryption encryption.Encryption
dataEncryption encryption.Encryption
span []byte
}
func NewRefEncryption(refSize int) *RefEncryption {
span := make([]byte, 8)
binary.LittleEndian.PutUint64(span, uint64(refSize))
return &RefEncryption{
spanEncryption: encryption.New(0, uint32(refSize/32), sha3.NewKeccak256),
dataEncryption: encryption.New(refSize, 0, sha3.NewKeccak256),
span: span,
}
}
func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) {
encryptedSpan, err := re.spanEncryption.Encrypt(re.span, key)
if err != nil {
return nil, err
}
encryptedData, err := re.dataEncryption.Encrypt(ref, key)
if err != nil {
return nil, err
}
encryptedRef := make([]byte, len(ref)+8)
copy(encryptedRef[:8], encryptedSpan)
copy(encryptedRef[8:], encryptedData)
return encryptedRef, nil
}
func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) {
decryptedSpan, err := re.spanEncryption.Decrypt(ref[:8], key)
if err != nil {
return nil, err
}
size := binary.LittleEndian.Uint64(decryptedSpan)
if size != uint64(len(ref)-8) {
return nil, errors.New("invalid span in encrypted reference")
}
decryptedRef, err := re.dataEncryption.Decrypt(ref[8:], key)
if err != nil {
return nil, err
}
return decryptedRef, nil
}
...@@ -191,7 +191,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error { ...@@ -191,7 +191,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error {
if err != nil { if err != nil {
return err return err
} }
addr, err := fs.api.Resolve(context.TODO(), uri) addr, err := fs.api.Resolve(context.TODO(), uri.Addr)
if err != nil { if err != nil {
return err return err
} }
...@@ -202,7 +202,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error { ...@@ -202,7 +202,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error {
} }
quitC := make(chan bool) quitC := make(chan bool)
trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC) trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt)
if err != nil { if err != nil {
log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err))
return err return err
......
...@@ -64,7 +64,7 @@ func TestApiDirUpload0(t *testing.T) { ...@@ -64,7 +64,7 @@ func TestApiDirUpload0(t *testing.T) {
checkResponse(t, resp, exp) checkResponse(t, resp, exp)
addr := storage.Address(common.Hex2Bytes(bzzhash)) addr := storage.Address(common.Hex2Bytes(bzzhash))
_, _, _, _, err = api.Get(context.TODO(), addr, "") _, _, _, _, err = api.Get(context.TODO(), NOOPDecrypt, addr, "")
if err == nil { if err == nil {
t.Fatalf("expected error: %v", err) t.Fatalf("expected error: %v", err)
} }
...@@ -143,7 +143,7 @@ func TestApiDirUploadModify(t *testing.T) { ...@@ -143,7 +143,7 @@ func TestApiDirUploadModify(t *testing.T) {
exp = expResponse(content, "text/css", 0) exp = expResponse(content, "text/css", 0)
checkResponse(t, resp, exp) checkResponse(t, resp, exp)
_, _, _, _, err = api.Get(context.TODO(), addr, "") _, _, _, _, err = api.Get(context.TODO(), nil, addr, "")
if err == nil { if err == nil {
t.Errorf("expected error: %v", err) t.Errorf("expected error: %v", err)
} }
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/sctx"
"github.com/ethereum/go-ethereum/swarm/spancontext" "github.com/ethereum/go-ethereum/swarm/spancontext"
"github.com/pborman/uuid" "github.com/pborman/uuid"
) )
...@@ -35,6 +36,15 @@ func SetRequestID(h http.Handler) http.Handler { ...@@ -35,6 +36,15 @@ func SetRequestID(h http.Handler) http.Handler {
}) })
} }
func SetRequestHost(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(sctx.SetHost(r.Context(), r.Host))
log.Info("setting request host", "ruid", GetRUID(r.Context()), "host", sctx.GetHost(r.Context()))
h.ServeHTTP(w, r)
})
}
func ParseURI(h http.Handler) http.Handler { func ParseURI(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
...@@ -87,7 +97,7 @@ func RecoverPanic(h http.Handler) http.Handler { ...@@ -87,7 +97,7 @@ func RecoverPanic(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Error("panic recovery!", "stack trace", debug.Stack(), "url", r.URL.String(), "headers", r.Header) log.Error("panic recovery!", "stack trace", string(debug.Stack()), "url", r.URL.String(), "headers", r.Header)
} }
}() }()
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
......
...@@ -79,7 +79,7 @@ func RespondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg s ...@@ -79,7 +79,7 @@ func RespondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg s
} }
func RespondError(w http.ResponseWriter, r *http.Request, msg string, code int) { func RespondError(w http.ResponseWriter, r *http.Request, msg string, code int) {
log.Debug("RespondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) log.Debug("RespondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code)
RespondTemplate(w, r, "error", msg, code) RespondTemplate(w, r, "error", msg, code)
} }
......
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
...@@ -97,6 +96,7 @@ func NewServer(api *api.API, corsString string) *Server { ...@@ -97,6 +96,7 @@ func NewServer(api *api.API, corsString string) *Server {
defaultMiddlewares := []Adapter{ defaultMiddlewares := []Adapter{
RecoverPanic, RecoverPanic,
SetRequestID, SetRequestID,
SetRequestHost,
InitLoggingResponseWriter, InitLoggingResponseWriter,
ParseURI, ParseURI,
InstrumentOpenTracing, InstrumentOpenTracing,
...@@ -169,6 +169,7 @@ func NewServer(api *api.API, corsString string) *Server { ...@@ -169,6 +169,7 @@ func NewServer(api *api.API, corsString string) *Server {
} }
func (s *Server) ListenAndServe(addr string) error { func (s *Server) ListenAndServe(addr string) error {
s.listenAddr = addr
return http.ListenAndServe(addr, s) return http.ListenAndServe(addr, s)
} }
...@@ -179,15 +180,23 @@ func (s *Server) ListenAndServe(addr string) error { ...@@ -179,15 +180,23 @@ func (s *Server) ListenAndServe(addr string) error {
type Server struct { type Server struct {
http.Handler http.Handler
api *api.API api *api.API
listenAddr string
} }
func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) {
log.Debug("handleBzzGet", "ruid", GetRUID(r.Context())) log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()), "uri", r.RequestURI)
if r.Header.Get("Accept") == "application/x-tar" { if r.Header.Get("Accept") == "application/x-tar" {
uri := GetURI(r.Context()) uri := GetURI(r.Context())
reader, err := s.api.GetDirectoryTar(r.Context(), uri) _, credentials, _ := r.BasicAuth()
reader, err := s.api.GetDirectoryTar(r.Context(), s.api.Decryptor(r.Context(), credentials), uri)
if err != nil { if err != nil {
if isDecryptError(err) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", uri.Address().String()))
RespondError(w, r, err.Error(), http.StatusUnauthorized)
return
}
RespondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError) RespondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError)
return
} }
defer reader.Close() defer reader.Close()
...@@ -287,7 +296,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) { ...@@ -287,7 +296,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
var addr storage.Address var addr storage.Address
if uri.Addr != "" && uri.Addr != "encrypt" { if uri.Addr != "" && uri.Addr != "encrypt" {
addr, err = s.api.Resolve(r.Context(), uri) addr, err = s.api.Resolve(r.Context(), uri.Addr)
if err != nil { if err != nil {
postFilesFail.Inc(1) postFilesFail.Inc(1)
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError) RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError)
...@@ -563,7 +572,7 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *http.Request) { ...@@ -563,7 +572,7 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *http.Request) {
// resolve the content key. // resolve the content key.
manifestAddr := uri.Address() manifestAddr := uri.Address()
if manifestAddr == nil { if manifestAddr == nil {
manifestAddr, err = s.api.Resolve(r.Context(), uri) manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr)
if err != nil { if err != nil {
getFail.Inc(1) getFail.Inc(1)
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
...@@ -682,62 +691,21 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) { ...@@ -682,62 +691,21 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) {
uri := GetURI(r.Context()) uri := GetURI(r.Context())
log.Debug("handle.get", "ruid", ruid, "uri", uri) log.Debug("handle.get", "ruid", ruid, "uri", uri)
getCount.Inc(1) getCount.Inc(1)
_, pass, _ := r.BasicAuth()
var err error addr, err := s.api.ResolveURI(r.Context(), uri, pass)
addr := uri.Address()
if addr == nil {
addr, err = s.api.Resolve(r.Context(), uri)
if err != nil { if err != nil {
getFail.Inc(1) getFail.Inc(1)
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
return return
} }
} else {
w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
}
log.Debug("handle.get: resolved", "ruid", ruid, "key", addr) log.Debug("handle.get: resolved", "ruid", ruid, "key", addr)
// if path is set, interpret <key> as a manifest and return the // if path is set, interpret <key> as a manifest and return the
// raw entry at the given path // raw entry at the given path
if uri.Path != "" {
walker, err := s.api.NewManifestWalker(r.Context(), addr, nil)
if err != nil {
getFail.Inc(1)
RespondError(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest)
return
}
var entry *api.ManifestEntry
walker.Walk(func(e *api.ManifestEntry) error {
// if the entry matches the path, set entry and stop
// the walk
if e.Path == uri.Path {
entry = e
// return an error to cancel the walk
return errors.New("found")
}
// ignore non-manifest files
if e.ContentType != api.ManifestType {
return nil
}
// if the manifest's path is a prefix of the
// requested path, recurse into it by returning
// nil and continuing the walk
if strings.HasPrefix(uri.Path, e.Path) {
return nil
}
return api.ErrSkipManifest
})
if entry == nil {
getFail.Inc(1)
RespondError(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound)
return
}
addr = storage.Address(common.Hex2Bytes(entry.Hash))
}
etag := common.Bytes2Hex(addr) etag := common.Bytes2Hex(addr)
noneMatchEtag := r.Header.Get("If-None-Match") noneMatchEtag := r.Header.Get("If-None-Match")
w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key. w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key.
...@@ -781,6 +749,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) { ...@@ -781,6 +749,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) {
func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
ruid := GetRUID(r.Context()) ruid := GetRUID(r.Context())
uri := GetURI(r.Context()) uri := GetURI(r.Context())
_, credentials, _ := r.BasicAuth()
log.Debug("handle.get.list", "ruid", ruid, "uri", uri) log.Debug("handle.get.list", "ruid", ruid, "uri", uri)
getListCount.Inc(1) getListCount.Inc(1)
...@@ -790,7 +759,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { ...@@ -790,7 +759,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
return return
} }
addr, err := s.api.Resolve(r.Context(), uri) addr, err := s.api.Resolve(r.Context(), uri.Addr)
if err != nil { if err != nil {
getListFail.Inc(1) getListFail.Inc(1)
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
...@@ -798,9 +767,14 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { ...@@ -798,9 +767,14 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
} }
log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr) log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr)
list, err := s.api.GetManifestList(r.Context(), addr, uri.Path) list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), addr, uri.Path)
if err != nil { if err != nil {
getListFail.Inc(1) getListFail.Inc(1)
if isDecryptError(err) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", addr.String()))
RespondError(w, r, err.Error(), http.StatusUnauthorized)
return
}
RespondError(w, r, err.Error(), http.StatusInternalServerError) RespondError(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
...@@ -833,7 +807,8 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { ...@@ -833,7 +807,8 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
ruid := GetRUID(r.Context()) ruid := GetRUID(r.Context())
uri := GetURI(r.Context()) uri := GetURI(r.Context())
log.Debug("handle.get.file", "ruid", ruid) _, credentials, _ := r.BasicAuth()
log.Debug("handle.get.file", "ruid", ruid, "uri", r.RequestURI)
getFileCount.Inc(1) getFileCount.Inc(1)
// ensure the root path has a trailing slash so that relative URLs work // ensure the root path has a trailing slash so that relative URLs work
...@@ -845,7 +820,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { ...@@ -845,7 +820,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
manifestAddr := uri.Address() manifestAddr := uri.Address()
if manifestAddr == nil { if manifestAddr == nil {
manifestAddr, err = s.api.Resolve(r.Context(), uri) manifestAddr, err = s.api.ResolveURI(r.Context(), uri, credentials)
if err != nil { if err != nil {
getFileFail.Inc(1) getFileFail.Inc(1)
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
...@@ -856,7 +831,8 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { ...@@ -856,7 +831,8 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
} }
log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr) log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr)
reader, contentType, status, contentKey, err := s.api.Get(r.Context(), manifestAddr, uri.Path)
reader, contentType, status, contentKey, err := s.api.Get(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path)
etag := common.Bytes2Hex(contentKey) etag := common.Bytes2Hex(contentKey)
noneMatchEtag := r.Header.Get("If-None-Match") noneMatchEtag := r.Header.Get("If-None-Match")
...@@ -869,6 +845,12 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { ...@@ -869,6 +845,12 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
} }
if err != nil { if err != nil {
if isDecryptError(err) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr))
RespondError(w, r, err.Error(), http.StatusUnauthorized)
return
}
switch status { switch status {
case http.StatusNotFound: case http.StatusNotFound:
getFileNotFound.Inc(1) getFileNotFound.Inc(1)
...@@ -883,9 +865,14 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { ...@@ -883,9 +865,14 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
//the request results in ambiguous files //the request results in ambiguous files
//e.g. /read with readme.md and readinglist.txt available in manifest //e.g. /read with readme.md and readinglist.txt available in manifest
if status == http.StatusMultipleChoices { if status == http.StatusMultipleChoices {
list, err := s.api.GetManifestList(r.Context(), manifestAddr, uri.Path) list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path)
if err != nil { if err != nil {
getFileFail.Inc(1) getFileFail.Inc(1)
if isDecryptError(err) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr))
RespondError(w, r, err.Error(), http.StatusUnauthorized)
return
}
RespondError(w, r, err.Error(), http.StatusInternalServerError) RespondError(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
...@@ -951,3 +938,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) { ...@@ -951,3 +938,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code) lrw.ResponseWriter.WriteHeader(code)
} }
func isDecryptError(err error) bool {
return strings.Contains(err.Error(), api.ErrDecrypt.Error())
}
...@@ -53,6 +53,7 @@ type ManifestEntry struct { ...@@ -53,6 +53,7 @@ type ManifestEntry struct {
Size int64 `json:"size,omitempty"` Size int64 `json:"size,omitempty"`
ModTime time.Time `json:"mod_time,omitempty"` ModTime time.Time `json:"mod_time,omitempty"`
Status int `json:"status,omitempty"` Status int `json:"status,omitempty"`
Access *AccessEntry `json:"access,omitempty"`
} }
// ManifestList represents the result of listing files in a manifest // ManifestList represents the result of listing files in a manifest
...@@ -98,7 +99,7 @@ type ManifestWriter struct { ...@@ -98,7 +99,7 @@ type ManifestWriter struct {
} }
func (a *API) NewManifestWriter(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWriter, error) { func (a *API) NewManifestWriter(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWriter, error) {
trie, err := loadManifest(ctx, a.fileStore, addr, quitC) trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt)
if err != nil { if err != nil {
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err) return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
} }
...@@ -141,8 +142,8 @@ type ManifestWalker struct { ...@@ -141,8 +142,8 @@ type ManifestWalker struct {
quitC chan bool quitC chan bool
} }
func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWalker, error) { func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, decrypt DecryptFunc, quitC chan bool) (*ManifestWalker, error) {
trie, err := loadManifest(ctx, a.fileStore, addr, quitC) trie, err := loadManifest(ctx, a.fileStore, addr, quitC, decrypt)
if err != nil { if err != nil {
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err) return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
} }
...@@ -194,6 +195,7 @@ type manifestTrie struct { ...@@ -194,6 +195,7 @@ type manifestTrie struct {
entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry
ref storage.Address // if ref != nil, it is stored ref storage.Address // if ref != nil, it is stored
encrypted bool encrypted bool
decrypt DecryptFunc
} }
func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry { func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry {
...@@ -209,15 +211,15 @@ type manifestTrieEntry struct { ...@@ -209,15 +211,15 @@ type manifestTrieEntry struct {
subtrie *manifestTrie subtrie *manifestTrie
} }
func loadManifest(ctx context.Context, fileStore *storage.FileStore, hash storage.Address, quitC chan bool) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand func loadManifest(ctx context.Context, fileStore *storage.FileStore, hash storage.Address, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand
log.Trace("manifest lookup", "key", hash) log.Trace("manifest lookup", "key", hash)
// retrieve manifest via FileStore // retrieve manifest via FileStore
manifestReader, isEncrypted := fileStore.Retrieve(ctx, hash) manifestReader, isEncrypted := fileStore.Retrieve(ctx, hash)
log.Trace("reader retrieved", "key", hash) log.Trace("reader retrieved", "key", hash)
return readManifest(manifestReader, hash, fileStore, isEncrypted, quitC) return readManifest(manifestReader, hash, fileStore, isEncrypted, quitC, decrypt)
} }
func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore *storage.FileStore, isEncrypted bool, quitC chan bool) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore *storage.FileStore, isEncrypted bool, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand
// TODO check size for oversized manifests // TODO check size for oversized manifests
size, err := mr.Size(mr.Context(), quitC) size, err := mr.Size(mr.Context(), quitC)
...@@ -258,26 +260,41 @@ func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore ...@@ -258,26 +260,41 @@ func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore
trie = &manifestTrie{ trie = &manifestTrie{
fileStore: fileStore, fileStore: fileStore,
encrypted: isEncrypted, encrypted: isEncrypted,
decrypt: decrypt,
} }
for _, entry := range man.Entries { for _, entry := range man.Entries {
trie.addEntry(entry, quitC) err = trie.addEntry(entry, quitC)
if err != nil {
return
}
} }
return return
} }
func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) { func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) error {
mt.ref = nil // trie modified, hash needs to be re-calculated on demand mt.ref = nil // trie modified, hash needs to be re-calculated on demand
if entry.ManifestEntry.Access != nil {
if mt.decrypt == nil {
return errors.New("dont have decryptor")
}
err := mt.decrypt(&entry.ManifestEntry)
if err != nil {
return err
}
}
if len(entry.Path) == 0 { if len(entry.Path) == 0 {
mt.entries[256] = entry mt.entries[256] = entry
return return nil
} }
b := entry.Path[0] b := entry.Path[0]
oldentry := mt.entries[b] oldentry := mt.entries[b]
if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) { if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) {
mt.entries[b] = entry mt.entries[b] = entry
return return nil
} }
cpl := 0 cpl := 0
...@@ -287,12 +304,12 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) { ...@@ -287,12 +304,12 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) { if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) {
if mt.loadSubTrie(oldentry, quitC) != nil { if mt.loadSubTrie(oldentry, quitC) != nil {
return return nil
} }
entry.Path = entry.Path[cpl:] entry.Path = entry.Path[cpl:]
oldentry.subtrie.addEntry(entry, quitC) oldentry.subtrie.addEntry(entry, quitC)
oldentry.Hash = "" oldentry.Hash = ""
return return nil
} }
commonPrefix := entry.Path[:cpl] commonPrefix := entry.Path[:cpl]
...@@ -310,6 +327,7 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) { ...@@ -310,6 +327,7 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
Path: commonPrefix, Path: commonPrefix,
ContentType: ManifestType, ContentType: ManifestType,
}, subtrie) }, subtrie)
return nil
} }
func (mt *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) { func (mt *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) {
...@@ -398,9 +416,20 @@ func (mt *manifestTrie) recalcAndStore() error { ...@@ -398,9 +416,20 @@ func (mt *manifestTrie) recalcAndStore() error {
} }
func (mt *manifestTrie) loadSubTrie(entry *manifestTrieEntry, quitC chan bool) (err error) { func (mt *manifestTrie) loadSubTrie(entry *manifestTrieEntry, quitC chan bool) (err error) {
if entry.ManifestEntry.Access != nil {
if mt.decrypt == nil {
return errors.New("dont have decryptor")
}
err := mt.decrypt(&entry.ManifestEntry)
if err != nil {
return err
}
}
if entry.subtrie == nil { if entry.subtrie == nil {
hash := common.Hex2Bytes(entry.Hash) hash := common.Hex2Bytes(entry.Hash)
entry.subtrie, err = loadManifest(context.TODO(), mt.fileStore, hash, quitC) entry.subtrie, err = loadManifest(context.TODO(), mt.fileStore, hash, quitC, mt.decrypt)
entry.Hash = "" // might not match, should be recalculated entry.Hash = "" // might not match, should be recalculated
} }
return return
......
...@@ -44,7 +44,7 @@ func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...stri ...@@ -44,7 +44,7 @@ func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...stri
quitC := make(chan bool) quitC := make(chan bool)
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
ref := make([]byte, fileStore.HashSize()) ref := make([]byte, fileStore.HashSize())
trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC) trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC, NOOPDecrypt)
if err != nil { if err != nil {
t.Errorf("unexpected error making manifest: %v", err) t.Errorf("unexpected error making manifest: %v", err)
} }
...@@ -101,7 +101,7 @@ func TestExactMatch(t *testing.T) { ...@@ -101,7 +101,7 @@ func TestExactMatch(t *testing.T) {
mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map") mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map")
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
ref := make([]byte, fileStore.HashSize()) ref := make([]byte, fileStore.HashSize())
trie, err := readManifest(mf, ref, fileStore, false, quitC) trie, err := readManifest(mf, ref, fileStore, false, quitC, nil)
if err != nil { if err != nil {
t.Errorf("unexpected error making manifest: %v", err) t.Errorf("unexpected error making manifest: %v", err)
} }
...@@ -134,7 +134,7 @@ func TestAddFileWithManifestPath(t *testing.T) { ...@@ -134,7 +134,7 @@ func TestAddFileWithManifestPath(t *testing.T) {
} }
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
ref := make([]byte, fileStore.HashSize()) ref := make([]byte, fileStore.HashSize())
trie, err := readManifest(reader, ref, fileStore, false, nil) trie, err := readManifest(reader, ref, fileStore, false, nil, NOOPDecrypt)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -161,7 +161,7 @@ func TestReadManifestOverSizeLimit(t *testing.T) { ...@@ -161,7 +161,7 @@ func TestReadManifestOverSizeLimit(t *testing.T) {
reader := &storage.LazyTestSectionReader{ reader := &storage.LazyTestSectionReader{
SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))), SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))),
} }
_, err := readManifest(reader, storage.Address{}, nil, false, nil) _, err := readManifest(reader, storage.Address{}, nil, false, nil, NOOPDecrypt)
if err == nil { if err == nil {
t.Fatal("got no error from readManifest") t.Fatal("got no error from readManifest")
} }
......
...@@ -63,11 +63,11 @@ func (s *Storage) Get(ctx context.Context, bzzpath string) (*Response, error) { ...@@ -63,11 +63,11 @@ func (s *Storage) Get(ctx context.Context, bzzpath string) (*Response, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
addr, err := s.api.Resolve(ctx, uri) addr, err := s.api.Resolve(ctx, uri.Addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader, mimeType, status, _, err := s.api.Get(ctx, addr, uri.Path) reader, mimeType, status, _, err := s.api.Get(ctx, nil, addr, uri.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -93,7 +93,7 @@ func (s *Storage) Modify(ctx context.Context, rootHash, path, contentHash, conte ...@@ -93,7 +93,7 @@ func (s *Storage) Modify(ctx context.Context, rootHash, path, contentHash, conte
if err != nil { if err != nil {
return "", err return "", err
} }
addr, err := s.api.Resolve(ctx, uri) addr, err := s.api.Resolve(ctx, uri.Addr)
if err != nil { if err != nil {
return "", err return "", err
} }
......
...@@ -53,6 +53,19 @@ type URI struct { ...@@ -53,6 +53,19 @@ type URI struct {
Path string Path string
} }
func (u *URI) MarshalJSON() (out []byte, err error) {
return []byte(`"` + u.String() + `"`), nil
}
func (u *URI) UnmarshalJSON(value []byte) error {
uri, err := Parse(string(value))
if err != nil {
return err
}
*u = *uri
return nil
}
// Parse parses rawuri into a URI struct, where rawuri is expected to have one // Parse parses rawuri into a URI struct, where rawuri is expected to have one
// of the following formats: // of the following formats:
// //
......
...@@ -1650,7 +1650,7 @@ func TestFUSE(t *testing.T) { ...@@ -1650,7 +1650,7 @@ func TestFUSE(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ta := &testAPI{api: api.NewAPI(fileStore, nil, nil)} ta := &testAPI{api: api.NewAPI(fileStore, nil, nil, nil)}
//run a short suite of tests //run a short suite of tests
//approx time: 28s //approx time: 28s
......
...@@ -445,7 +445,7 @@ func retrieve( ...@@ -445,7 +445,7 @@ func retrieve(
log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount)) log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount))
r, _, _, _, err := swarm.api.Get(context.TODO(), f.addr, "/") r, _, _, _, err := swarm.api.Get(context.TODO(), api.NOOPDecrypt, f.addr, "/")
if err != nil { if err != nil {
errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err)
return return
......
package sctx package sctx
import "context"
type ContextKey int type ContextKey int
const ( const (
HTTPRequestIDKey ContextKey = iota HTTPRequestIDKey ContextKey = iota
requestHostKey
) )
func SetHost(ctx context.Context, domain string) context.Context {
return context.WithValue(ctx, requestHostKey, domain)
}
func GetHost(ctx context.Context) string {
v, ok := ctx.Value(requestHostKey).(string)
if ok {
return v
}
return ""
}
...@@ -85,14 +85,12 @@ type Swarm struct { ...@@ -85,14 +85,12 @@ type Swarm struct {
type SwarmAPI struct { type SwarmAPI struct {
Api *api.API Api *api.API
Backend chequebook.Backend Backend chequebook.Backend
PrvKey *ecdsa.PrivateKey
} }
func (self *Swarm) API() *SwarmAPI { func (self *Swarm) API() *SwarmAPI {
return &SwarmAPI{ return &SwarmAPI{
Api: self.api, Api: self.api,
Backend: self.backend, Backend: self.backend,
PrvKey: self.privateKey,
} }
} }
...@@ -217,7 +215,7 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e ...@@ -217,7 +215,7 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
pss.SetHandshakeController(self.ps, pss.NewHandshakeParams()) pss.SetHandshakeController(self.ps, pss.NewHandshakeParams())
} }
self.api = api.NewAPI(self.fileStore, self.dns, resourceHandler) self.api = api.NewAPI(self.fileStore, self.dns, resourceHandler, self.privateKey)
// Manifests for Smart Hosting // Manifests for Smart Hosting
log.Debug(fmt.Sprintf("-> Web3 virtual server API")) log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
......
...@@ -77,7 +77,7 @@ func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer) *Tes ...@@ -77,7 +77,7 @@ func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer) *Tes
t.Fatal(err) t.Fatal(err)
} }
a := api.NewAPI(fileStore, nil, rh.Handler) a := api.NewAPI(fileStore, nil, rh.Handler, nil)
srv := httptest.NewServer(serverFunc(a)) srv := httptest.NewServer(serverFunc(a))
return &TestSwarmServer{ return &TestSwarmServer{
Server: srv, Server: srv,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment