mirror of
https://github.com/aljazceru/ark.git
synced 2026-01-28 07:44:20 +01:00
Support macaroons and TLS && Add arkd wallet cmds (#232)
* Update protos * Update handlers * Support macaroons and TLS * Add arkd cli * Minor fixes * Update deps * Fixes * Update makefile * Fixes * Fix * Fix * Fix * Remove trusted onboarding from client * Completely remove trusted onboarding * Fix compose files and add --no-macaroon flag to arkd cli * Lint * Remove e2e for trusted onboarding * Add sleep time
This commit is contained in:
committed by
GitHub
parent
059e837794
commit
57ce08f239
447
server/cmd/arkd/commands.go
Normal file
447
server/cmd/arkd/commands.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
// flags
|
||||
var (
|
||||
passwordFlag = &cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "wallet password",
|
||||
Required: true,
|
||||
}
|
||||
mnemonicFlag = &cli.StringFlag{
|
||||
Name: "mnemonic",
|
||||
Usage: "mnemonic from which restore the wallet",
|
||||
}
|
||||
gapLimitFlag = &cli.Uint64Flag{
|
||||
Name: "addr-gap-limit",
|
||||
Usage: "address gap limit for wallet restoration",
|
||||
Value: 100,
|
||||
}
|
||||
)
|
||||
|
||||
// commands
|
||||
var (
|
||||
walletCmd = &cli.Command{
|
||||
Name: "wallet",
|
||||
Usage: "Manage the Ark Server wallet",
|
||||
Subcommands: append(
|
||||
cli.Commands{},
|
||||
walletStatusCmd,
|
||||
walletCreateOrRestoreCmd,
|
||||
walletUnlockCmd,
|
||||
walletAddressCmd,
|
||||
walletBalanceCmd,
|
||||
),
|
||||
}
|
||||
walletStatusCmd = &cli.Command{
|
||||
Name: "status",
|
||||
Usage: "Get info about the status of the wallet",
|
||||
Action: walletStatusAction,
|
||||
}
|
||||
walletCreateOrRestoreCmd = &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create or restore the wallet",
|
||||
Action: walletCreateOrRestoreAction,
|
||||
Flags: []cli.Flag{passwordFlag, mnemonicFlag, gapLimitFlag},
|
||||
}
|
||||
walletUnlockCmd = &cli.Command{
|
||||
Name: "unlock",
|
||||
Usage: "Unlock the wallet",
|
||||
Action: walletUnlockAction,
|
||||
Flags: []cli.Flag{passwordFlag},
|
||||
}
|
||||
walletAddressCmd = &cli.Command{
|
||||
Name: "address",
|
||||
Usage: "Generate a receiving address",
|
||||
Action: walletAddressAction,
|
||||
}
|
||||
walletBalanceCmd = &cli.Command{
|
||||
Name: "balance",
|
||||
Usage: "Get the wallet balance",
|
||||
Action: walletBalanceAction,
|
||||
}
|
||||
)
|
||||
|
||||
func walletStatusAction(ctx *cli.Context) error {
|
||||
baseURL := ctx.String("url")
|
||||
tlsCertPath := ctx.String("tls-cert-path")
|
||||
if strings.Contains(baseURL, "http://") {
|
||||
tlsCertPath = ""
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/v1/admin/wallet/status", baseURL)
|
||||
status, err := getStatus(url, tlsCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(status)
|
||||
return nil
|
||||
}
|
||||
|
||||
func walletCreateOrRestoreAction(ctx *cli.Context) error {
|
||||
baseURL := ctx.String("url")
|
||||
password := ctx.String("password")
|
||||
mnemonic := ctx.String("mnemonic")
|
||||
gapLimit := ctx.Uint64("addr-gap-limit")
|
||||
tlsCertPath := ctx.String("tls-cert-path")
|
||||
if strings.Contains(baseURL, "http://") {
|
||||
tlsCertPath = ""
|
||||
}
|
||||
|
||||
if len(mnemonic) > 0 {
|
||||
url := fmt.Sprintf("%s/v1/admin/wallet/restore", baseURL)
|
||||
body := fmt.Sprintf(
|
||||
`{"seed": "%s", "password": "%s", "gap_limit": %d}`,
|
||||
mnemonic, password, gapLimit,
|
||||
)
|
||||
if _, err := post[struct{}](url, body, "", "", tlsCertPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("wallet restored")
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/v1/admin/wallet/seed", baseURL)
|
||||
seed, err := get[string](url, "seed", "", tlsCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url = fmt.Sprintf("%s/v1/admin/wallet/create", baseURL)
|
||||
body := fmt.Sprintf(
|
||||
`{"seed": "%s", "password": "%s"}`, seed, password,
|
||||
)
|
||||
if _, err := post[struct{}](url, body, "", "", tlsCertPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(seed)
|
||||
return nil
|
||||
}
|
||||
|
||||
func walletUnlockAction(ctx *cli.Context) error {
|
||||
baseURL := ctx.String("url")
|
||||
password := ctx.String("password")
|
||||
url := fmt.Sprintf("%s/v1/admin/wallet/unlock", baseURL)
|
||||
body := fmt.Sprintf(`{"password": "%s"}`, password)
|
||||
tlsCertPath := ctx.String("tls-cert-path")
|
||||
if strings.Contains(baseURL, "http://") {
|
||||
tlsCertPath = ""
|
||||
}
|
||||
|
||||
if _, err := post[struct{}](url, body, "", "", tlsCertPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("wallet unlocked")
|
||||
return nil
|
||||
}
|
||||
|
||||
func walletAddressAction(ctx *cli.Context) error {
|
||||
baseURL := ctx.String("url")
|
||||
var macaroon string
|
||||
if !ctx.Bool("no-macaroon") {
|
||||
macaroonPath := ctx.String("macaroon-path")
|
||||
mac, err := getMacaroon(macaroonPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
macaroon = mac
|
||||
}
|
||||
tlsCertPath := ctx.String("tls-cert-path")
|
||||
if strings.Contains(baseURL, "http://") {
|
||||
tlsCertPath = ""
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/v1/admin/wallet/address", baseURL)
|
||||
addr, err := get[string](url, "address", macaroon, tlsCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func walletBalanceAction(ctx *cli.Context) error {
|
||||
baseURL := ctx.String("url")
|
||||
var macaroon string
|
||||
if !ctx.Bool("no-macaroon") {
|
||||
macaroonPath := ctx.String("macaroon-path")
|
||||
mac, err := getMacaroon(macaroonPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
macaroon = mac
|
||||
}
|
||||
tlsCertPath := ctx.String("tls-cert-path")
|
||||
if strings.Contains(baseURL, "http://") {
|
||||
tlsCertPath = ""
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/v1/admin/wallet/balance", baseURL)
|
||||
balance, err := getBalance(url, macaroon, tlsCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(balance)
|
||||
return nil
|
||||
}
|
||||
|
||||
func post[T any](url, body, key, macaroon, tlsCert string) (result T, err error) {
|
||||
tlsConfig, err := getTLSConfig(tlsCert)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(body))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
if len(macaroon) > 0 {
|
||||
req.Header.Add("X-Macaroon", macaroon)
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf(string(buf))
|
||||
return
|
||||
}
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
res := make(map[string]T)
|
||||
if err = json.Unmarshal(buf, &res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result = res[key]
|
||||
return
|
||||
}
|
||||
|
||||
func get[T any](url, key, macaroon, tlsCert string) (result T, err error) {
|
||||
tlsConfig, err := getTLSConfig(tlsCert)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
if len(macaroon) > 0 {
|
||||
req.Header.Add("X-Macaroon", macaroon)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf(string(buf))
|
||||
return
|
||||
}
|
||||
|
||||
res := make(map[string]T)
|
||||
if err = json.Unmarshal(buf, &res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result = res[key]
|
||||
return
|
||||
}
|
||||
|
||||
type accountBalance struct {
|
||||
Available string `json:"available"`
|
||||
Locked string `json:"locked"`
|
||||
}
|
||||
|
||||
func (b accountBalance) String() string {
|
||||
return fmt.Sprintf(" available: %s\n locked: %s", b.Available, b.Locked)
|
||||
}
|
||||
|
||||
type balance struct {
|
||||
MainAccount accountBalance `json:"mainAccount"`
|
||||
ConnectorsAccount accountBalance `json:"connectorsAccount"`
|
||||
}
|
||||
|
||||
func (b balance) String() string {
|
||||
return fmt.Sprintf(
|
||||
"main account\n%s\nconnectors account\n%s",
|
||||
b.MainAccount, b.ConnectorsAccount,
|
||||
)
|
||||
}
|
||||
|
||||
func getBalance(url, macaroon, tlsCert string) (*balance, error) {
|
||||
tlsConfig, err := getTLSConfig(tlsCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
if len(macaroon) > 0 {
|
||||
req.Header.Add("X-Macaroon", macaroon)
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf(string(buf))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &balance{}
|
||||
if err := json.Unmarshal(buf, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type status struct {
|
||||
Initialized bool `json:"initialized"`
|
||||
Unlocked bool `json:"unlocked"`
|
||||
Synced bool `json:"synced"`
|
||||
}
|
||||
|
||||
func (s status) String() string {
|
||||
return fmt.Sprintf(
|
||||
"initialized: %t\nunlocked: %t\nsynced: %t",
|
||||
s.Initialized, s.Unlocked, s.Synced,
|
||||
)
|
||||
}
|
||||
|
||||
func getStatus(url, tlsCert string) (*status, error) {
|
||||
tlsConfig, err := getTLSConfig(tlsCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf(string(buf))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &status{}
|
||||
if err := json.Unmarshal(buf, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getMacaroon(path string) (string, error) {
|
||||
macBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read macaroon %s: %s", path, err)
|
||||
}
|
||||
mac := &macaroon.Macaroon{}
|
||||
if err := mac.UnmarshalBinary(macBytes); err != nil {
|
||||
return "", fmt.Errorf("failed to parse macaroon %s: %s", path, err)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(macBytes), nil
|
||||
}
|
||||
|
||||
func getTLSConfig(path string) (*tls.Config, error) {
|
||||
if len(path) <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var buf []byte
|
||||
buf, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
if ok := caCertPool.AppendCertsFromPEM(buf); !ok {
|
||||
return nil, fmt.Errorf("failed to parse tls cert")
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: caCertPool,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
appconfig "github.com/ark-network/ark/internal/app-config"
|
||||
"github.com/ark-network/ark/internal/config"
|
||||
grpcservice "github.com/ark-network/ark/internal/interface/grpc"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
//nolint:all
|
||||
@@ -18,19 +22,45 @@ var (
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// flags
|
||||
var (
|
||||
urlFlag = &cli.StringFlag{
|
||||
Name: "url",
|
||||
Usage: "the url where to reach ark server",
|
||||
Value: fmt.Sprintf("http://localhost:%d", config.DefaultPort),
|
||||
}
|
||||
noMacaroonFlag = &cli.BoolFlag{
|
||||
Name: "no-macaroon",
|
||||
Usage: "don't use macaroon auth",
|
||||
Value: false,
|
||||
}
|
||||
macaroonFlag = &cli.StringFlag{
|
||||
Name: "macaroon-path",
|
||||
Usage: "the path where to find the admin macaroon file",
|
||||
Value: filepath.Join(common.AppDataDir("arkd", false), "macaroons", "admin.macaroon"),
|
||||
}
|
||||
tlsCertFlag = &cli.StringFlag{
|
||||
Name: "tls-cert-path",
|
||||
Usage: "the path where to find the TLS certificate",
|
||||
Value: filepath.Join(common.AppDataDir("arkd", false), "tls", "cert.pem"),
|
||||
}
|
||||
)
|
||||
|
||||
func mainAction(_ *cli.Context) error {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("invalid config")
|
||||
return fmt.Errorf("invalid config: %s", err)
|
||||
}
|
||||
|
||||
log.SetLevel(log.Level(cfg.LogLevel))
|
||||
|
||||
svcConfig := grpcservice.Config{
|
||||
Port: cfg.Port,
|
||||
NoTLS: cfg.NoTLS,
|
||||
AuthUser: cfg.AuthUser,
|
||||
AuthPass: cfg.AuthPass,
|
||||
Datadir: cfg.Datadir,
|
||||
Port: cfg.Port,
|
||||
NoTLS: cfg.NoTLS,
|
||||
NoMacaroons: cfg.NoMacaroons,
|
||||
TLSExtraIPs: cfg.TLSExtraIPs,
|
||||
TLSExtraDomains: cfg.TLSExtraDomains,
|
||||
}
|
||||
|
||||
appConfig := &appconfig.Config{
|
||||
@@ -53,14 +83,14 @@ func main() {
|
||||
}
|
||||
svc, err := grpcservice.NewService(svcConfig, appConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.RegisterExitHandler(svc.Stop)
|
||||
|
||||
log.Info("starting service...")
|
||||
if err := svc.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
@@ -69,4 +99,20 @@ func main() {
|
||||
|
||||
log.Info("shutting down service...")
|
||||
log.Exit(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Version = version
|
||||
app.Name = "Arkd CLI"
|
||||
app.Usage = "arkd command line interface"
|
||||
app.Commands = append(app.Commands, walletCmd)
|
||||
app.Action = mainAction
|
||||
app.Flags = append(app.Flags, urlFlag, noMacaroonFlag, macaroonFlag, tlsCertFlag)
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user