e2c14edd7b
A profile file can contain multiple profile entries. Each entry has a name, a set of default options to use and an optional list of macaroons in a jar. The profile file can be serialized/deserialized to and from JSON.
178 lines
5.2 KiB
Go
178 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/walletunlocker"
|
|
"github.com/urfave/cli"
|
|
"gopkg.in/macaroon.v2"
|
|
)
|
|
|
|
var (
|
|
errNoProfileFile = errors.New("no profile file found")
|
|
)
|
|
|
|
// profileEntry is a struct that represents all settings for one specific
|
|
// profile.
|
|
type profileEntry struct {
|
|
Name string `json:"name"`
|
|
RPCServer string `json:"rpcserver"`
|
|
LndDir string `json:"lnddir"`
|
|
Chain string `json:"chain"`
|
|
Network string `json:"network"`
|
|
NoMacaroons bool `json:"no-macaroons,omitempty"`
|
|
TLSCert string `json:"tlscert"`
|
|
Macaroons *macaroonJar `json:"macaroons"`
|
|
}
|
|
|
|
// cert returns the profile's TLS certificate as a x509 certificate pool.
|
|
func (e *profileEntry) cert() (*x509.CertPool, error) {
|
|
cp := x509.NewCertPool()
|
|
if !cp.AppendCertsFromPEM([]byte(e.TLSCert)) {
|
|
return nil, fmt.Errorf("credentials: failed to append " +
|
|
"certificate")
|
|
}
|
|
return cp, nil
|
|
}
|
|
|
|
// profileFromContext creates an ephemeral profile entry from the global options
|
|
// set in the CLI context.
|
|
func profileFromContext(ctx *cli.Context, store bool) (*profileEntry, error) {
|
|
// Parse the paths of the cert and macaroon. This will validate the
|
|
// chain and network value as well.
|
|
tlsCertPath, macPath, err := extractPathArgs(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load the certificate file now. We store it as plain PEM directly.
|
|
tlsCert, err := ioutil.ReadFile(tlsCertPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not load TLS cert file %s: %v",
|
|
tlsCertPath, err)
|
|
}
|
|
|
|
// Now load and possibly encrypt the macaroon file.
|
|
macBytes, err := ioutil.ReadFile(macPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read macaroon path (check "+
|
|
"the network setting!): %v", err)
|
|
}
|
|
mac := &macaroon.Macaroon{}
|
|
if err = mac.UnmarshalBinary(macBytes); err != nil {
|
|
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
|
|
}
|
|
|
|
var pw []byte
|
|
if store {
|
|
// Read a password from the terminal. If it's empty, we won't
|
|
// encrypt the macaroon and store it plaintext.
|
|
pw, err = capturePassword(
|
|
"Enter password to encrypt macaroon with or leave "+
|
|
"blank to store in plaintext: ", true,
|
|
walletunlocker.ValidatePassword,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get encryption "+
|
|
"password: %v", err)
|
|
}
|
|
}
|
|
macEntry := &macaroonEntry{}
|
|
if err = macEntry.storeMacaroon(mac, pw); err != nil {
|
|
return nil, fmt.Errorf("unable to store macaroon: %v", err)
|
|
}
|
|
|
|
// We determine the name of the macaroon from the file itself but cut
|
|
// off the ".macaroon" at the end.
|
|
macEntry.Name = path.Base(macPath)
|
|
if path.Ext(macEntry.Name) == "macaroon" {
|
|
macEntry.Name = strings.TrimSuffix(macEntry.Name, ".macaroon")
|
|
}
|
|
|
|
// Now that we have the complicated arguments behind us, let's return
|
|
// the new entry with all the values populated.
|
|
return &profileEntry{
|
|
RPCServer: ctx.GlobalString("rpcserver"),
|
|
LndDir: lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir")),
|
|
Chain: ctx.GlobalString("chain"),
|
|
Network: ctx.GlobalString("network"),
|
|
NoMacaroons: ctx.GlobalBool("no-macaroons"),
|
|
TLSCert: string(tlsCert),
|
|
Macaroons: &macaroonJar{
|
|
Default: macEntry.Name,
|
|
Timeout: ctx.GlobalInt64("macaroontimeout"),
|
|
IP: ctx.GlobalString("macaroonip"),
|
|
Jar: []*macaroonEntry{macEntry},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// loadProfileFile tries to load the file specified and JSON deserialize it into
|
|
// the profile file struct.
|
|
func loadProfileFile(file string) (*profileFile, error) {
|
|
if !lnrpc.FileExists(file) {
|
|
return nil, errNoProfileFile
|
|
}
|
|
|
|
content, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not load profile file %s: %v",
|
|
file, err)
|
|
}
|
|
f := &profileFile{}
|
|
err = f.unmarshalJSON(content)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not unmarshal profile file %s: "+
|
|
"%v", file, err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// saveProfileFile stores the given profile file struct in the specified file,
|
|
// overwriting it if it already existed.
|
|
func saveProfileFile(file string, f *profileFile) error {
|
|
content, err := f.marshalJSON()
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal profile: %v", err)
|
|
}
|
|
return ioutil.WriteFile(file, content, 0644)
|
|
}
|
|
|
|
// profileFile is a struct that represents the whole content of a profile file.
|
|
type profileFile struct {
|
|
Default string `json:"default,omitempty"`
|
|
Profiles []*profileEntry `json:"profiles"`
|
|
}
|
|
|
|
// unmarshalJSON tries to parse the given JSON and unmarshal it into the
|
|
// receiving instance.
|
|
func (f *profileFile) unmarshalJSON(content []byte) error {
|
|
return json.Unmarshal(content, f)
|
|
}
|
|
|
|
// marshalJSON serializes the receiving instance to formatted/indented JSON.
|
|
func (f *profileFile) marshalJSON() ([]byte, error) {
|
|
b, err := json.Marshal(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error JSON marshalling profile: %v",
|
|
err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
err = json.Indent(&out, b, "", " ")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error indenting profile JSON: %v", err)
|
|
}
|
|
out.WriteString("\n")
|
|
return out.Bytes(), nil
|
|
}
|