Use yaml to encode admin file, not tgz

This commit is contained in:
Brian Picciano 2022-11-02 14:02:21 +01:00
parent 745fe31324
commit 7d95825f97
4 changed files with 45 additions and 138 deletions

View File

@ -37,7 +37,7 @@ which was configured when creating the network.
## Step 3: Create a `bootstrap.tgz` File ## Step 3: Create a `bootstrap.tgz` File
Access to an `admin.tgz` file is required for this step. Access to an `admin.yml` file is required for this step.
To create a `bootstrap.tgz` file for the new host, the admin should perform the To create a `bootstrap.tgz` file for the new host, the admin should perform the
following command from their own host: following command from their own host:
@ -46,7 +46,7 @@ following command from their own host:
cryptic-net hosts make-bootstrap \ cryptic-net hosts make-bootstrap \
--name <name> \ --name <name> \
--ip <ip> \ --ip <ip> \
--admin-path <path to admin.tgz> \ --admin-path <path to admin.yml> \
> bootstrap.tgz > bootstrap.tgz
``` ```
@ -58,22 +58,22 @@ If the user already has access to a `cryptic-net` binary then the new
`bootstrap.tgz` file can be given to them as-is, and they can proceed with `bootstrap.tgz` file can be given to them as-is, and they can proceed with
running their host's `cryptic-net daemon`. running their host's `cryptic-net daemon`.
### Encrypted `admin.tgz` ### Encrypted `admin.yml`
If `admin.tgz` is kept in an encrypted format on disk (it should be!) then the If `admin.yml` is kept in an encrypted format on disk (it should be!) then the
decrypted form can be piped into `make-bootstrap` over stdin. For example, if decrypted form can be piped into `make-bootstrap` over stdin. For example, if
GPG is being used to secure `admin.tgz` then the following could be used to GPG is being used to secure `admin.yml` then the following could be used to
generate a `bootstrap.tgz`: generate a `bootstrap.tgz`:
``` ```
gpg -d <path to admin.tgz.gpg> | cryptic-net hosts make-boostrap \ gpg -d <path to admin.yml.gpg> | cryptic-net hosts make-boostrap \
--name <name> \ --name <name> \
--ip <ip> \ --ip <ip> \
--admin-path - \ --admin-path - \
> bootstrap.tgz > bootstrap.tgz
``` ```
Note that the value of `--admin-path` is `-`, indicating that `admin.tgz` should Note that the value of `--admin-path` is `-`, indicating that `admin.yml` should
be read from stdin. be read from stdin.
## Step 4: Optionally, Build Binary ## Step 4: Optionally, Build Binary

View File

@ -1,29 +1,14 @@
// Package admin deals with the parsing and creation of admin.tgz files. // Package admin deals with the parsing and creation of admin.yml files.
package admin package admin
import ( import (
"cryptic-net/garage" "cryptic-net/garage"
"cryptic-net/nebula" "cryptic-net/nebula"
"cryptic-net/tarutil"
"cryptic-net/yamlutil"
"fmt"
"io" "io"
"io/fs"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const (
creationParamsPath = "admin/creation-params.yml"
nebulaCertsCACertPath = "nebula/certs/ca.crt"
nebulaCertsCAKeyPath = "nebula/certs/ca.key"
garageGlobalBucketKeyYmlPath = "garage/cryptic-net-global-bucket-key.yml"
garageAdminBucketKeyYmlPath = "garage/cryptic-net-admin-bucket-key.yml"
garageRPCSecretPath = "garage/rpc-secret.txt"
)
// CreationParams are general parameters used when creating a new network. These // CreationParams are general parameters used when creating a new network. These
// are available to all hosts within the network via their bootstrap files. // are available to all hosts within the network via their bootstrap files.
type CreationParams struct { type CreationParams struct {
@ -31,105 +16,28 @@ type CreationParams struct {
Domain string `yaml:"domain"` Domain string `yaml:"domain"`
} }
// Admin is used for accessing all information contained within an admin.tgz. // Admin is used for accessing all information contained within an admin.yml.
type Admin struct { type Admin struct {
CreationParams CreationParams CreationParams CreationParams `yaml:"creation_params"`
NebulaCACredentials nebula.CACredentials Nebula struct {
CACredentials nebula.CACredentials `yaml:"ca_credentials"`
} `yaml:"nebula"`
GarageRPCSecret string Garage struct {
GarageGlobalBucketS3APICredentials garage.S3APICredentials RPCSecret string `yaml:"rpc_secret"`
GarageAdminBucketS3APICredentials garage.S3APICredentials GlobalBucketS3APICredentials garage.S3APICredentials `yaml:"global_bucket_s3_api_credentials"`
} `yaml:"garage"`
} }
// FromFS loads an Admin instance from the given fs.FS, which presumably // FromReader reads an admin.yml from the given io.Reader.
// represents the file structure of an admin.tgz file.
func FromFS(adminFS fs.FS) (Admin, error) {
var a Admin
filesToLoadAsYAML := []struct {
into interface{}
path string
}{
{&a.CreationParams, creationParamsPath},
{&a.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
{&a.GarageAdminBucketS3APICredentials, garageAdminBucketKeyYmlPath},
}
for _, f := range filesToLoadAsYAML {
if err := yamlutil.LoadYamlFSFile(f.into, adminFS, f.path); err != nil {
return Admin{}, fmt.Errorf("loading %q from fs: %w", f.path, err)
}
}
filesToLoadAsString := []struct {
into *string
path string
}{
{&a.NebulaCACredentials.CACertPEM, nebulaCertsCACertPath},
{&a.NebulaCACredentials.CAKeyPEM, nebulaCertsCAKeyPath},
{&a.GarageRPCSecret, garageRPCSecretPath},
}
for _, f := range filesToLoadAsString {
body, err := fs.ReadFile(adminFS, f.path)
if err != nil {
return Admin{}, fmt.Errorf("loading %q from fs: %w", f.path, err)
}
*f.into = string(body)
}
return a, nil
}
// FromReader reads an admin.tgz file from the given io.Reader.
func FromReader(r io.Reader) (Admin, error) { func FromReader(r io.Reader) (Admin, error) {
var a Admin
fs, err := tarutil.FSFromReader(r) err := yaml.NewDecoder(r).Decode(&a)
if err != nil { return a, err
return Admin{}, fmt.Errorf("reading admin.tgz: %w", err)
} }
return FromFS(fs) // WriteTo writes the Admin as an admin.yml to the given io.Writer.
}
// WriteTo writes the Admin as a new admin.tgz to the given io.Writer.
func (a Admin) WriteTo(into io.Writer) error { func (a Admin) WriteTo(into io.Writer) error {
return yaml.NewEncoder(into).Encode(a)
w := tarutil.NewTGZWriter(into)
filesToWriteAsYAML := []struct {
value interface{}
path string
}{
{a.CreationParams, creationParamsPath},
{a.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
{a.GarageAdminBucketS3APICredentials, garageAdminBucketKeyYmlPath},
}
for _, f := range filesToWriteAsYAML {
b, err := yaml.Marshal(f.value)
if err != nil {
return fmt.Errorf("yaml encoding data for %q: %w", f.path, err)
}
w.WriteFileBytes(f.path, b)
}
filesToWriteAsString := []struct {
value string
path string
}{
{a.NebulaCACredentials.CACertPEM, nebulaCertsCACertPath},
{a.NebulaCACredentials.CAKeyPEM, nebulaCertsCAKeyPath},
{a.GarageRPCSecret, garageRPCSecretPath},
}
for _, f := range filesToWriteAsString {
w.WriteFileBytes(f.path, []byte(f.value))
}
return w.Close()
} }

View File

@ -32,7 +32,7 @@ func readAdmin(path string) (admin.Admin, error) {
adm, err := admin.FromReader(os.Stdin) adm, err := admin.FromReader(os.Stdin)
if err != nil { if err != nil {
return admin.Admin{}, fmt.Errorf("parsing admin.tgz from stdin: %w", err) return admin.Admin{}, fmt.Errorf("parsing admin.yml from stdin: %w", err)
} }
return adm, nil return adm, nil
@ -49,7 +49,7 @@ func readAdmin(path string) (admin.Admin, error) {
var subCmdAdminCreateNetwork = subCmd{ var subCmdAdminCreateNetwork = subCmd{
name: "create-network", name: "create-network",
descr: "Creates a new cryptic-net network, outputting the resulting admin.tgz to stdout", descr: "Creates a new cryptic-net network, outputting the resulting admin.yml to stdout",
do: func(subCmdCtx subCmdCtx) error { do: func(subCmdCtx subCmdCtx) error {
flags := subCmdCtx.flagSet(false) flags := subCmdCtx.flagSet(false)
@ -178,7 +178,7 @@ var subCmdAdminCreateNetwork = subCmd{
fmt.Fprintln(os.Stderr, "starting child processes") fmt.Fprintln(os.Stderr, "starting child processes")
go func() { go func() {
// NOTE both stdout and stderr are sent to stderr, so that the user // NOTE both stdout and stderr are sent to stderr, so that the user
// can pipe the resulting admin.tgz to stdout. // can pipe the resulting admin.yml to stdout.
pmuxlib.Run(ctx, os.Stderr, os.Stderr, pmuxConfig) pmuxlib.Run(ctx, os.Stderr, os.Stderr, pmuxConfig)
close(pmuxDoneCh) close(pmuxDoneCh)
}() }()
@ -209,18 +209,17 @@ var subCmdAdminCreateNetwork = subCmd{
return fmt.Errorf("initializing garage shared global bucket: %w", err) return fmt.Errorf("initializing garage shared global bucket: %w", err)
} }
fmt.Fprintln(os.Stderr, "cluster initialized successfully, writing admin.tgz to stdout") fmt.Fprintln(os.Stderr, "cluster initialized successfully, writing admin.yml to stdout")
err = admin.Admin{ adm := admin.Admin{
CreationParams: adminCreationParams, CreationParams: adminCreationParams,
NebulaCACredentials: nebulaCACreds, }
GarageRPCSecret: hostBootstrap.GarageRPCSecret, adm.Nebula.CACredentials = nebulaCACreds
GarageGlobalBucketS3APICredentials: hostBootstrap.GarageGlobalBucketS3APICredentials, adm.Garage.RPCSecret = hostBootstrap.GarageRPCSecret
GarageAdminBucketS3APICredentials: garage.NewS3APICredentials(), adm.Garage.GlobalBucketS3APICredentials = hostBootstrap.GarageGlobalBucketS3APICredentials
}.WriteTo(os.Stdout)
if err != nil { if err := adm.WriteTo(os.Stdout); err != nil {
return fmt.Errorf("writing admin.tgz to stdout") return fmt.Errorf("writing admin.yml to stdout")
} }
return nil return nil
@ -247,7 +246,7 @@ var subCmdAdminMakeBootstrap = subCmd{
adminPath := flags.StringP( adminPath := flags.StringP(
"admin-path", "a", "", "admin-path", "a", "",
`Path to admin.tgz file. If the given path is "-" then stdin is used.`, `Path to admin.yml file. If the given path is "-" then stdin is used.`,
) )
if err := flags.Parse(subCmdCtx.args); err != nil { if err := flags.Parse(subCmdCtx.args); err != nil {
@ -270,7 +269,7 @@ var subCmdAdminMakeBootstrap = subCmd{
adm, err := readAdmin(*adminPath) adm, err := readAdmin(*adminPath)
if err != nil { if err != nil {
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err) return fmt.Errorf("reading admin.yml with --admin-path of %q: %w", *adminPath, err)
} }
hostBootstrap, err := loadHostBootstrap() hostBootstrap, err := loadHostBootstrap()
@ -278,7 +277,7 @@ var subCmdAdminMakeBootstrap = subCmd{
return fmt.Errorf("loading host bootstrap: %w", err) return fmt.Errorf("loading host bootstrap: %w", err)
} }
nebulaHostCreds, err := nebula.NewHostCredentials(adm.NebulaCACredentials, *name, ip) nebulaHostCreds, err := nebula.NewHostCredentials(adm.Nebula.CACredentials, *name, ip)
if err != nil { if err != nil {
return fmt.Errorf("creating new nebula host key/cert: %w", err) return fmt.Errorf("creating new nebula host key/cert: %w", err)
} }
@ -291,9 +290,9 @@ var subCmdAdminMakeBootstrap = subCmd{
NebulaHostCredentials: nebulaHostCreds, NebulaHostCredentials: nebulaHostCreds,
GarageRPCSecret: adm.GarageRPCSecret, GarageRPCSecret: adm.Garage.RPCSecret,
GarageAdminToken: randStr(32), GarageAdminToken: randStr(32),
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials, GarageGlobalBucketS3APICredentials: adm.Garage.GlobalBucketS3APICredentials,
} }
return newHostBootstrap.WriteTo(os.Stdout) return newHostBootstrap.WriteTo(os.Stdout)

View File

@ -24,16 +24,16 @@ var ErrInvalidSignature = errors.New("invalid signature")
// HostCredentials contains the certificate and private key files which will // HostCredentials contains the certificate and private key files which will
// need to be present on a particular host. Each file is PEM encoded. // need to be present on a particular host. Each file is PEM encoded.
type HostCredentials struct { type HostCredentials struct {
CACertPEM string CACertPEM string `yaml:"ca_cert_pem"`
HostKeyPEM string HostKeyPEM string `yaml:"host_key_pem"`
HostCertPEM string HostCertPEM string `yaml:"host_cert_pem"`
} }
// CACredentials contains the certificate and private files which can be used to // CACredentials contains the certificate and private files which can be used to
// create and validate HostCredentials. Each file is PEM encoded. // create and validate HostCredentials. Each file is PEM encoded.
type CACredentials struct { type CACredentials struct {
CACertPEM string CACertPEM string `yaml:"ca_cert_pem"`
CAKeyPEM string CAKeyPEM string `yaml:"ca_key_pem"`
} }
// NewHostCredentials generates a new key/cert for a nebula host using the CA // NewHostCredentials generates a new key/cert for a nebula host using the CA