mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 04:34:19 +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
|
||||
}
|
||||
Reference in New Issue
Block a user