Skip to content

Commit

Permalink
lndinit: add ability to write to k8s configmap
Browse files Browse the repository at this point in the history
We'll use this to write connection relateed information
for consumption by payment service proxy.
  • Loading branch information
calvinrzachman committed Jan 16, 2024
1 parent 97404fa commit 1c05001
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 82 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ initialization, including seed and password generation.
- [`gen-seed`](#gen-seed)
- [`load-secret`](#load-secret)
- [`store-secret`](#store-secret)
- [`store-configmap`](#store-configmap)
- [`init-wallet`](#init-wallet)
- [`wait-ready`](#wait-ready)
- [Example usage](#example-usage)
Expand Down Expand Up @@ -48,6 +49,9 @@ No `lnd` needed, but seed will be in `lnd`-specific [`aezeed` format](https://gi
### store-secret
`store-secret` interacts with kubernetes to write to secrets (no `lnd` needed)

### store-configmap
`store-configamp` interacts with kubernetes to write to configmaps (no `lnd` needed)

### init-wallet
`init-wallet` has two modes:
- `--init-type=file` creates an `lnd` specific `wallet.db` file
Expand Down
4 changes: 2 additions & 2 deletions cmd_gen_seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ type jsonSeed struct {
type genSeedCommand struct {
EntropySourceFile string `long:"entropy-source-file" description:"The file descriptor to read the seed entropy from; if set lndinit will read exactly 16 bytes from the file, otherwise the default crypto/rand source will be used"`
PassphraseFile string `long:"passphrase-file" description:"The file to read the seed passphrase from; if not set, no seed passphrase will be used, unless --passhprase-k8s is used"`
PassphraseK8s *k8sSecretOptions `group:"Flags for reading seed passphrase from Kubernetes" namespace:"passphrase-k8s"`
PassphraseK8s *k8sObjectOptions `group:"Flags for reading seed passphrase from Kubernetes" namespace:"passphrase-k8s"`
Output string `long:"output" short:"o" description:"Output format" choice:"raw" choice:"json"`
}

func newGenSeedCommand() *genSeedCommand {
return &genSeedCommand{
Output: outputFormatRaw,
PassphraseK8s: &k8sSecretOptions{
PassphraseK8s: &k8sObjectOptions{
Namespace: defaultK8sNamespace,
},
}
Expand Down
19 changes: 10 additions & 9 deletions cmd_init_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type secretSourceFile struct {

type secretSourceK8s struct {
Namespace string `long:"namespace" description:"The Kubernetes namespace the secret is located in"`
SecretName string `long:"secret-name" description:"The name of the Kubernetes secret"`
Name string `long:"name" description:"The name of the Kubernetes secret"`
SeedKeyName string `long:"seed-key-name" description:"The name of the key within the secret that contains the seed"`
SeedPassphraseKeyName string `long:"seed-passphrase-key-name" description:"The name of the key within the secret that contains the seed passphrase"`
WalletPasswordKeyName string `long:"wallet-password-key-name" description:"The name of the key within the secret that contains the wallet password"`
Expand Down Expand Up @@ -239,16 +239,17 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string,

// Read passphrase from Kubernetes secret.
case storageK8s:
k8sSecret := &k8sSecretOptions{
k8sSecret := &k8sObjectOptions{
Namespace: x.K8s.Namespace,
SecretName: x.K8s.SecretName,
Name: x.K8s.Name,
Base64: x.K8s.Base64,
ObjectType: ObjectTypeSecret,
}
k8sSecret.SecretKeyName = x.K8s.SeedKeyName
k8sSecret.KeyName = x.K8s.SeedKeyName

if requireSeed {
log("Reading seed from k8s secret %s (namespace %s)",
x.K8s.SecretName, x.K8s.Namespace)
x.K8s.Name, x.K8s.Namespace)
seed, _, err = readK8s(k8sSecret)
if err != nil {
return "", "", "", err
Expand All @@ -258,18 +259,18 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string,
// The seed passphrase is optional.
if x.K8s.SeedPassphraseKeyName != "" {
log("Reading seed passphrase from k8s secret %s "+
"(namespace %s)", x.K8s.SecretName,
"(namespace %s)", x.K8s.Name,
x.K8s.Namespace)
k8sSecret.SecretKeyName = x.K8s.SeedPassphraseKeyName
k8sSecret.KeyName = x.K8s.SeedPassphraseKeyName
seedPassPhrase, _, err = readK8s(k8sSecret)
if err != nil {
return "", "", "", err
}
}

log("Reading wallet password from k8s secret %s (namespace %s)",
x.K8s.SecretName, x.K8s.Namespace)
k8sSecret.SecretKeyName = x.K8s.WalletPasswordKeyName
x.K8s.Name, x.K8s.Namespace)
k8sSecret.KeyName = x.K8s.WalletPasswordKeyName
walletPassword, _, err = readK8s(k8sSecret)
if err != nil {
return "", "", "", err
Expand Down
9 changes: 5 additions & 4 deletions cmd_load_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import (

type loadSecretCommand struct {
Source string `long:"source" short:"s" description:"Secret storage source" choice:"k8s"`
K8s *k8sSecretOptions `group:"Flags for looking up the secret as a value inside a Kubernetes Secret (use when --source=k8s)" namespace:"k8s"`
K8s *k8sObjectOptions `group:"Flags for looking up the secret as a value inside a Kubernetes Secret (use when --source=k8s)" namespace:"k8s"`
Output string `long:"output" short:"o" description:"Output format" choice:"raw" choice:"json"`
}

func newLoadSecretCommand() *loadSecretCommand {
return &loadSecretCommand{
Source: storageK8s,
K8s: &k8sSecretOptions{
Namespace: defaultK8sNamespace,
K8s: &k8sObjectOptions{
Namespace: defaultK8sNamespace,
ObjectType: ObjectTypeSecret,
},
Output: outputFormatRaw,
}
Expand All @@ -40,7 +41,7 @@ func (x *loadSecretCommand) Execute(_ []string) error {
content, secret, err := readK8s(x.K8s)
if err != nil {
return fmt.Errorf("error reading secret %s in "+
"namespace %s: %v", x.K8s.SecretName,
"namespace %s: %v", x.K8s.Name,
x.K8s.Namespace, err)
}

Expand Down
127 changes: 127 additions & 0 deletions cmd_store_configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package main

import (
"fmt"
"io"
"os"
"path/filepath"

"github.com/jessevdk/go-flags"
)

type storeConfigmapCommand struct {
Batch bool `long:"batch" description:"Instead of reading one configmap from stdin, read all files of the argument list and store them as entries in the configmap"`
Overwrite bool `long:"overwrite" description:"Overwrite existing configmap entries instead of aborting"`
Target string `long:"target" short:"t" description:"Configmap storage target" choice:"k8s"`
K8s *targetK8s `group:"Flags for storing the key/value pair inside a Kubernetes Configmap (use when --target=k8s)" namespace:"k8s"`
}

func newStoreConfigmapCommand() *storeConfigmapCommand {
return &storeConfigmapCommand{
Target: storageK8s,
K8s: &targetK8s{
k8sObjectOptions: k8sObjectOptions{
Namespace: defaultK8sNamespace,
ObjectType: ObjectTypeConfigMap,
},
},
}
}

func (x *storeConfigmapCommand) Register(parser *flags.Parser) error {
_, err := parser.AddCommand(
"store-configmap",
"Write key/value pairs to a Kubernetes configmap",
"Read a configmap from stdin and store it to the "+
"external configmaps storage indicated by the --target "+
"flag; if the --batch flag is used, instead of "+
"reading a single configmap entry from stdin, each command "+
"line argument is treated as a file and each file's "+
"content is added to the configmap with the file's name "+
"as the key name for the configmap entry",
x,
)
return err
}

func (x *storeConfigmapCommand) Execute(args []string) error {
var entries []*entry

switch {
case x.Batch && len(args) == 0:
return fmt.Errorf("at least one command line argument is " +
"required when using --batch flag")

case x.Batch:
for _, file := range args {
log("Reading value/entry from file %s", file)
content, err := readFile(file)
if err != nil {
return fmt.Errorf("cannot read file %s: %v",
file, err)
}

entries = append(entries, &entry{
key: filepath.Base(file),
value: content,
})
}

default:
log("Reading value/entry from stdin")
value, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("error reading value/entry from stdin: %v", err)
}
entries = append(entries, &entry{value: string(value)})
}

switch x.Target {
case storageK8s:
// Take the actual entry key from the options if we aren't in
// batch mode.
if len(entries) == 1 && entries[0].key == "" {
entries[0].key = x.K8s.KeyName
}

return storeConfigmapsK8s(entries, x.K8s, x.Overwrite)

default:
return fmt.Errorf("invalid configmap storage target %s", x.Target)
}
}

func storeConfigmapsK8s(entries []*entry, opts *targetK8s,
overwrite bool) error {

if opts.Name == "" {
return fmt.Errorf("configmap name is required")
}

for _, entry := range entries {
if entry.key == "" {
return fmt.Errorf("configmap entry key name is required")
}

entryOpts := &k8sObjectOptions{
Namespace: opts.Namespace,
Name: opts.Name,
KeyName: entry.key,
ObjectType: ObjectTypeConfigMap,
}

log("Storing key with name %s to configmap %s in namespace %s",
entryOpts.KeyName, entryOpts.Name,
entryOpts.Namespace)

// TODO(calvin): We need to make the Kubernetes write method capable
// of handling multiple different types.
err := saveK8s(entry.value, entryOpts, overwrite, opts.Helm)
if err != nil {
return fmt.Errorf("error storing key %s in configmap %s: "+
"%v", entry.key, opts.Name, err)
}
}

return nil
}
36 changes: 19 additions & 17 deletions cmd_store_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import (
)

type targetK8s struct {
k8sSecretOptions
k8sObjectOptions

Helm *helmOptions `group:"Flags for configuring the Helm annotations (use when --target=k8s)" namespace:"helm"`
}

type secretEntry struct {
type entry struct {
key string
value string
}
Expand All @@ -32,8 +32,9 @@ func newStoreSecretCommand() *storeSecretCommand {
return &storeSecretCommand{
Target: storageK8s,
K8s: &targetK8s{
k8sSecretOptions: k8sSecretOptions{
Namespace: defaultK8sNamespace,
k8sObjectOptions: k8sObjectOptions{
Namespace: defaultK8sNamespace,
ObjectType: ObjectTypeSecret,
},
Helm: &helmOptions{
ResourcePolicy: defaultK8sResourcePolicy,
Expand All @@ -59,7 +60,7 @@ func (x *storeSecretCommand) Register(parser *flags.Parser) error {
}

func (x *storeSecretCommand) Execute(args []string) error {
var entries []*secretEntry
var entries []*entry

switch {
case x.Batch && len(args) == 0:
Expand All @@ -75,7 +76,7 @@ func (x *storeSecretCommand) Execute(args []string) error {
file, err)
}

entries = append(entries, &secretEntry{
entries = append(entries, &entry{
key: filepath.Base(file),
value: content,
})
Expand All @@ -88,15 +89,15 @@ func (x *storeSecretCommand) Execute(args []string) error {
return fmt.Errorf("error reading secret from stdin: %v",
err)
}
entries = append(entries, &secretEntry{value: secret})
entries = append(entries, &entry{value: secret})
}

switch x.Target {
case storageK8s:
// Take the actual entry key from the options if we aren't in
// batch mode.
if len(entries) == 1 && entries[0].key == "" {
entries[0].key = x.K8s.SecretKeyName
entries[0].key = x.K8s.KeyName
}

return storeSecretsK8s(entries, x.K8s, x.Overwrite)
Expand All @@ -106,10 +107,10 @@ func (x *storeSecretCommand) Execute(args []string) error {
}
}

func storeSecretsK8s(entries []*secretEntry, opts *targetK8s,
func storeSecretsK8s(entries []*entry, opts *targetK8s,
overwrite bool) error {

if opts.SecretName == "" {
if opts.Name == "" {
return fmt.Errorf("secret name is required")
}

Expand All @@ -118,20 +119,21 @@ func storeSecretsK8s(entries []*secretEntry, opts *targetK8s,
return fmt.Errorf("secret key name is required")
}

entryOpts := &k8sSecretOptions{
Namespace: opts.Namespace,
SecretName: opts.SecretName,
SecretKeyName: entry.key,
Base64: opts.Base64,
entryOpts := &k8sObjectOptions{
Namespace: opts.Namespace,
Name: opts.Name,
KeyName: entry.key,
Base64: opts.Base64,
ObjectType: ObjectTypeSecret,
}

log("Storing key with name %s to secret %s in namespace %s",
entryOpts.SecretKeyName, entryOpts.SecretName,
entryOpts.KeyName, entryOpts.Name,
entryOpts.Namespace)
err := saveK8s(entry.value, entryOpts, overwrite, opts.Helm)
if err != nil {
return fmt.Errorf("error storing secret %s key %s: "+
"%v", opts.SecretName, entry.key, err)
"%v", opts.Name, entry.key, err)
}
}

Expand Down
Loading

0 comments on commit 1c05001

Please sign in to comment.