Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: REST Client Unsafe/Safe Endpoints #3640

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ FEATURES
IMPROVEMENTS

* Gaia REST API
* [\#3560] Add a `--allow-unsafe` flag to the REST client command to mount and
expose all unsafe routes. Unsafe routes will not be exposed by default, instead
returning a standard error response.

* Gaia CLI

Expand Down
20 changes: 15 additions & 5 deletions client/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"path/filepath"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
clientkeyscmn "github.com/cosmos/cosmos-sdk/client/keys/common"
"github.com/cosmos/cosmos-sdk/codec"
cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/x/auth"
Expand Down Expand Up @@ -53,6 +53,7 @@ type CLIContext struct {
FromAddress sdk.AccAddress
FromName string
Indent bool
AllowUnsafe bool
}

// NewCLIContext returns a new initialized CLIContext with parameters from the
Expand Down Expand Up @@ -96,6 +97,7 @@ func NewCLIContext() CLIContext {
FromAddress: fromAddress,
FromName: fromName,
Indent: viper.GetBool(client.FlagIndentResponse),
AllowUnsafe: viper.GetBool(client.FlagUnsafeRoutes),
}
}

Expand Down Expand Up @@ -245,9 +247,17 @@ func (ctx CLIContext) WithFromAddress(addr sdk.AccAddress) CLIContext {
return ctx
}

// PrintOutput prints output while respecting output and indent flags
// NOTE: pass in marshalled structs that have been unmarshaled
// because this function will panic on marshaling errors
// WithAllowUnsafe returns a copy of the context with an updated AllowUnsafe
// value.
func (ctx CLIContext) WithAllowUnsafe(allowUnsafe bool) CLIContext {
ctx.AllowUnsafe = allowUnsafe
return ctx
}

// PrintOutput prints output while respecting output and indent flags.
//
// NOTE: Pass in marshalled structs that have been unmarshaled
// because this function will panic on marshaling errors.
func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) {
var out []byte

Expand Down Expand Up @@ -277,7 +287,7 @@ func GetFromFields(from string) (sdk.AccAddress, string, error) {
return nil, "", nil
}

keybase, err := keys.NewKeyBaseFromHomeFlag()
keybase, err := clientkeyscmn.NewKeyBaseFromHomeFlag()
if err != nil {
return nil, "", err
}
Expand Down
5 changes: 4 additions & 1 deletion client/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
FlagListenAddr = "laddr"
FlagCORS = "cors"
FlagMaxOpenConnections = "max-open"
FlagUnsafeRoutes = "allow-unsafe"
FlagTLS = "tls"
FlagSSLHosts = "ssl-hosts"
FlagSSLCertFile = "ssl-certfile"
Expand Down Expand Up @@ -101,16 +102,18 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
return cmds
}

// RegisterRestServerFlags registers the flags required for rest server
// RegisterRestServerFlags registers the flags required for rest server.
func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command {
cmd = GetCommands(cmd)[0]

cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on")
cmd.Flags().Bool(FlagTLS, false, "Enable SSL/TLS layer")
cmd.Flags().String(FlagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for")
cmd.Flags().String(FlagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.")
cmd.Flags().String(FlagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.")
cmd.Flags().String(FlagCORS, "", "Set the domains that can make CORS requests (* for all)")
cmd.Flags().Int(FlagMaxOpenConnections, 1000, "The number of maximum open connections")
cmd.Flags().Bool(FlagUnsafeRoutes, false, "Expose unsafe routes (should only be exposed on localhost)")

return cmd
}
Expand Down
78 changes: 49 additions & 29 deletions client/keys/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (
"sort"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/keys/common"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
Expand Down Expand Up @@ -108,7 +111,7 @@ func runAddCmd(_ *cobra.Command, args []string) error {
kb = keys.NewInMemory()
encryptPassword = app.DefaultKeyPass
} else {
kb, err = NewKeyBaseFromHomeFlag()
kb, err = common.NewKeyBaseFromHomeFlag()
if err != nil {
return err
}
Expand Down Expand Up @@ -266,9 +269,9 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
output := viper.Get(cli.OutputFlag)

switch output {
case OutputFormatText:
case common.OutputFormatText:
fmt.Fprintln(os.Stderr)
printKeyInfo(info, Bech32KeyOutput)
common.PrintKeyInfo(info, common.Bech32KeyOutput)

// print mnemonic unless requested not to.
if showMnemonic {
Expand All @@ -277,8 +280,8 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, mnemonic)
}
case OutputFormatJSON:
out, err := Bech32KeyOutput(info)
case common.OutputFormatJSON:
out, err := common.Bech32KeyOutput(info)
if err != nil {
return err
}
Expand All @@ -289,9 +292,9 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {

var jsonString []byte
if viper.GetBool(client.FlagIndentResponse) {
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
jsonString, err = codec.Cdc.MarshalJSONIndent(out, "", " ")
} else {
jsonString, err = cdc.MarshalJSON(out)
jsonString, err = codec.Cdc.MarshalJSON(out)
}

if err != nil {
Expand Down Expand Up @@ -330,12 +333,17 @@ func CheckAndWriteErrorResponse(w http.ResponseWriter, httpErr int, err error) b
}

// add new key REST handler
func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
func AddNewKeyRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !cliCtx.AllowUnsafe {
rest.UnsafeRouteHandler(w)
return
}

var kb keys.Keybase
var m AddNewKey
var m common.AddNewKey

kb, err := NewKeyBaseFromHomeFlag()
kb, err := common.NewKeyBaseFromHomeFlag()
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}
Expand Down Expand Up @@ -393,52 +401,64 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
return
}

keyOutput, err := Bech32KeyOutput(info)
keyOutput, err := common.Bech32KeyOutput(info)
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}

keyOutput.Mnemonic = mnemonic

rest.PostProcessResponse(w, cdc, keyOutput, indent)
rest.PostProcessResponse(w, codec.Cdc, keyOutput, cliCtx.Indent)
}
}

// Seed REST request handler
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
algoType := vars["type"]
func SeedRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !cliCtx.AllowUnsafe {
rest.UnsafeRouteHandler(w)
return
}

// algo type defaults to secp256k1
if algoType == "" {
algoType = "secp256k1"
}
vars := mux.Vars(r)
algoType := vars["type"]

// algo type defaults to secp256k1
if algoType == "" {
algoType = "secp256k1"
}

algo := keys.SigningAlgo(algoType)
seed := generateMnemonic(algo)
algo := keys.SigningAlgo(algoType)
seed := generateMnemonic(algo)

w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(seed))
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(seed))
}
}

// RecoverRequestHandler performs key recover request
func RecoverRequestHandler(indent bool) http.HandlerFunc {
func RecoverRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !cliCtx.AllowUnsafe {
rest.UnsafeRouteHandler(w)
return
}

vars := mux.Vars(r)
name := vars["name"]
var m RecoverKey
var m common.RecoverKey

body, err := ioutil.ReadAll(r.Body)
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}

err = cdc.UnmarshalJSON(body, &m)
err = codec.Cdc.UnmarshalJSON(body, &m)
if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) {
return
}

kb, err := NewKeyBaseFromHomeFlag()
kb, err := common.NewKeyBaseFromHomeFlag()
CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err)

if name == "" {
Expand Down Expand Up @@ -484,11 +504,11 @@ func RecoverRequestHandler(indent bool) http.HandlerFunc {
return
}

keyOutput, err := Bech32KeyOutput(info)
keyOutput, err := common.Bech32KeyOutput(info)
if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) {
return
}

rest.PostProcessResponse(w, cdc, keyOutput, indent)
rest.PostProcessResponse(w, codec.Cdc, keyOutput, cliCtx.Indent)
}
}
13 changes: 6 additions & 7 deletions client/keys/add_ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ import (
"strings"
"testing"

"github.com/cosmos/cosmos-sdk/client/keys/common"
"github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/libs/cli"

"github.com/cosmos/cosmos-sdk/tests"

"github.com/cosmos/cosmos-sdk/client"

"github.com/stretchr/testify/assert"
"github.com/cosmos/cosmos-sdk/tests"
)

func Test_runAddCmdLedger(t *testing.T) {
func TestRunAddCmdLedger(t *testing.T) {
cmd := addKeyCommand()
assert.NotNil(t, cmd)

Expand All @@ -32,15 +31,15 @@ func Test_runAddCmdLedger(t *testing.T) {
viper.Set(client.FlagUseLedger, true)

/// Test Text
viper.Set(cli.OutputFlag, OutputFormatText)
viper.Set(cli.OutputFlag, common.OutputFormatText)
// Now enter password
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp1()
err := runAddCmd(cmd, []string{"keyname1"})
assert.NoError(t, err)

// Now check that it has been stored properly
kb, err := NewKeyBaseFromHomeFlag()
kb, err := common.NewKeyBaseFromHomeFlag()
assert.NoError(t, err)
assert.NotNil(t, kb)
key1, err := kb.Get("keyname1")
Expand Down
20 changes: 9 additions & 11 deletions client/keys/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@ import (
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/cli"

"github.com/cosmos/cosmos-sdk/tests"

"github.com/cosmos/cosmos-sdk/client"

"github.com/stretchr/testify/assert"
"github.com/cosmos/cosmos-sdk/client/keys/common"
"github.com/cosmos/cosmos-sdk/tests"
)

func Test_runAddCmdBasic(t *testing.T) {
func TestRunAddCmdBasic(t *testing.T) {
cmd := addKeyCommand()
assert.NotNil(t, cmd)

Expand All @@ -30,31 +28,31 @@ func Test_runAddCmdBasic(t *testing.T) {
viper.Set(cli.HomeFlag, kbHome)

/// Test Text
viper.Set(cli.OutputFlag, OutputFormatText)
viper.Set(cli.OutputFlag, common.OutputFormatText)
// Now enter password
cleanUp1 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp1()
err := runAddCmd(cmd, []string{"keyname1"})
assert.NoError(t, err)

/// Test Text - Replace? >> FAIL
viper.Set(cli.OutputFlag, OutputFormatText)
viper.Set(cli.OutputFlag, common.OutputFormatText)
// Now enter password
cleanUp2 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp2()
err = runAddCmd(cmd, []string{"keyname1"})
assert.Error(t, err)

/// Test Text - Replace? Answer >> PASS
viper.Set(cli.OutputFlag, OutputFormatText)
viper.Set(cli.OutputFlag, common.OutputFormatText)
// Now enter password
cleanUp3 := client.OverrideStdin(bufio.NewReader(strings.NewReader("y\ntest1234\ntest1234\n")))
defer cleanUp3()
err = runAddCmd(cmd, []string{"keyname1"})
assert.NoError(t, err)

// Check JSON
viper.Set(cli.OutputFlag, OutputFormatJSON)
viper.Set(cli.OutputFlag, common.OutputFormatJSON)
// Now enter password
cleanUp4 := client.OverrideStdin(bufio.NewReader(strings.NewReader("test1234\ntest1234\n")))
defer cleanUp4()
Expand Down
22 changes: 0 additions & 22 deletions client/keys/codec.go

This file was deleted.

Loading