629a8ec9b2
I switched to using mlog for logging, as opposed to writing directly to Stderr. This gives us control over log levels, as well as coordination so that we don't have multiple go-routines writing to stderr at the same time.
172 lines
4.1 KiB
Go
172 lines
4.1 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"cryptic-net/garage"
|
|
"cryptic-net/nebula"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
|
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
|
"github.com/minio/minio-go/v7"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Paths within garage's global bucket
|
|
const (
|
|
garageGlobalBucketBootstrapHostsDirPath = "bootstrap/hosts"
|
|
)
|
|
|
|
// PutGarageBoostrapHost places the <hostname>.yml.signed file for this host
|
|
// into garage so that other hosts are able to see relevant configuration for
|
|
// it.
|
|
func (b Bootstrap) PutGarageBoostrapHost(ctx context.Context) error {
|
|
|
|
host := b.ThisHost()
|
|
client := b.GlobalBucketS3APIClient()
|
|
|
|
// the base Bootstrap has the public credentials signed by the CA, but we
|
|
// need this to be presented in the data stored into garage, so other hosts
|
|
// can verify that the stored host object is signed by the host public key,
|
|
// and that the host public key is signed by the CA.
|
|
host.Nebula.SignedPublicCredentials = b.Nebula.SignedPublicCredentials
|
|
|
|
hostB, err := yaml.Marshal(host)
|
|
if err != nil {
|
|
return fmt.Errorf("yaml encoding host data: %w", err)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
err = nebula.SignAndWrap(buf, b.Nebula.HostCredentials.SigningPrivateKeyPEM, hostB)
|
|
if err != nil {
|
|
return fmt.Errorf("signing encoded host data: %w", err)
|
|
}
|
|
|
|
filePath := filepath.Join(
|
|
garageGlobalBucketBootstrapHostsDirPath,
|
|
host.Name+".yml.signed",
|
|
)
|
|
|
|
_, err = client.PutObject(
|
|
ctx, garage.GlobalBucket, filePath, buf, int64(buf.Len()),
|
|
minio.PutObjectOptions{},
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("writing to %q in global bucket: %w", filePath, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveGarageBootstrapHost removes the <hostname>.yml.signed for the given
|
|
// host from garage.
|
|
//
|
|
// The given client should be for the global bucket.
|
|
func RemoveGarageBootstrapHost(
|
|
ctx context.Context, client garage.S3APIClient, hostName string,
|
|
) error {
|
|
|
|
filePath := filepath.Join(
|
|
garageGlobalBucketBootstrapHostsDirPath,
|
|
hostName+".yml.signed",
|
|
)
|
|
|
|
return client.RemoveObject(
|
|
ctx, garage.GlobalBucket, filePath,
|
|
minio.RemoveObjectOptions{},
|
|
)
|
|
}
|
|
|
|
// GetGarageBootstrapHosts loads the <hostname>.yml.signed file for all hosts
|
|
// stored in garage.
|
|
func (b Bootstrap) GetGarageBootstrapHosts(
|
|
ctx context.Context,
|
|
logger *mlog.Logger,
|
|
) (
|
|
map[string]Host, error,
|
|
) {
|
|
|
|
client := b.GlobalBucketS3APIClient()
|
|
|
|
hosts := map[string]Host{}
|
|
|
|
objInfoCh := client.ListObjects(
|
|
ctx, garage.GlobalBucket,
|
|
minio.ListObjectsOptions{
|
|
Prefix: garageGlobalBucketBootstrapHostsDirPath,
|
|
Recursive: true,
|
|
},
|
|
)
|
|
|
|
for objInfo := range objInfoCh {
|
|
|
|
ctx := mctx.Annotate(ctx, "object-key", objInfo.Key)
|
|
|
|
if objInfo.Err != nil {
|
|
return nil, fmt.Errorf("listing objects: %w", objInfo.Err)
|
|
}
|
|
|
|
obj, err := client.GetObject(
|
|
ctx, garage.GlobalBucket, objInfo.Key, minio.GetObjectOptions{},
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("retrieving object %q: %w", objInfo.Key, err)
|
|
}
|
|
|
|
hostB, hostSig, err := nebula.Unwrap(obj)
|
|
obj.Close()
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unwrapping signature from %q: %w", objInfo.Key, err)
|
|
}
|
|
|
|
var host Host
|
|
if err = yaml.Unmarshal(hostB, &host); err != nil {
|
|
return nil, fmt.Errorf("yaml decoding object %q: %w", objInfo.Key, err)
|
|
}
|
|
|
|
hostPublicCredsB, hostPublicCredsSig, err := nebula.Unwrap(
|
|
strings.NewReader(host.Nebula.SignedPublicCredentials),
|
|
)
|
|
|
|
if err != nil {
|
|
logger.Warn(ctx, "unwrapping signed public creds", err)
|
|
continue
|
|
}
|
|
|
|
err = nebula.ValidateSignature(
|
|
b.Nebula.CAPublicCredentials.SigningKeyPEM,
|
|
hostPublicCredsB,
|
|
hostPublicCredsSig,
|
|
)
|
|
|
|
if err != nil {
|
|
logger.Warn(ctx, "invalid signed public creds", err)
|
|
continue
|
|
}
|
|
|
|
var hostPublicCreds nebula.HostPublicCredentials
|
|
if err := yaml.Unmarshal(hostPublicCredsB, &hostPublicCreds); err != nil {
|
|
logger.Warn(ctx, "yaml unmarshaling signed public creds", err)
|
|
continue
|
|
}
|
|
|
|
err = nebula.ValidateSignature(hostPublicCreds.SigningKeyPEM, hostB, hostSig)
|
|
|
|
if err != nil {
|
|
logger.Warn(ctx, "invalid host data", err)
|
|
continue
|
|
}
|
|
|
|
hosts[host.Name] = host
|
|
}
|
|
|
|
return hosts, nil
|
|
}
|