2022-10-15 16:41:07 +00:00
|
|
|
package bootstrap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2023-08-05 21:53:17 +00:00
|
|
|
"isle/garage"
|
|
|
|
"isle/nebula"
|
2022-10-15 16:41:07 +00:00
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
2022-11-05 14:23:29 +00:00
|
|
|
"strings"
|
2022-10-15 16:41:07 +00:00
|
|
|
|
2022-11-13 15:45:42 +00:00
|
|
|
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
|
|
|
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
2022-10-15 16:41:07 +00:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Paths within garage's global bucket
|
|
|
|
const (
|
|
|
|
garageGlobalBucketBootstrapHostsDirPath = "bootstrap/hosts"
|
|
|
|
)
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
// 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()
|
|
|
|
|
2022-11-05 14:23:29 +00:00
|
|
|
// 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
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
hostB, err := yaml.Marshal(host)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("yaml encoding host data: %w", err)
|
|
|
|
}
|
2022-10-15 16:41:07 +00:00
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
2022-11-05 14:23:29 +00:00
|
|
|
err = nebula.SignAndWrap(buf, b.Nebula.HostCredentials.SigningPrivateKeyPEM, hostB)
|
2022-10-29 19:11:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("signing encoded host data: %w", err)
|
2022-10-15 16:41:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
filePath := filepath.Join(
|
|
|
|
garageGlobalBucketBootstrapHostsDirPath,
|
|
|
|
host.Name+".yml.signed",
|
|
|
|
)
|
2022-10-15 16:41:07 +00:00
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
_, err = client.PutObject(
|
2022-10-15 16:41:07 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
// RemoveGarageBootstrapHost removes the <hostname>.yml.signed for the given
|
|
|
|
// host from garage.
|
2022-10-15 16:41:07 +00:00
|
|
|
//
|
|
|
|
// The given client should be for the global bucket.
|
|
|
|
func RemoveGarageBootstrapHost(
|
|
|
|
ctx context.Context, client garage.S3APIClient, hostName string,
|
|
|
|
) error {
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
filePath := filepath.Join(
|
|
|
|
garageGlobalBucketBootstrapHostsDirPath,
|
|
|
|
hostName+".yml.signed",
|
|
|
|
)
|
2022-10-15 16:41:07 +00:00
|
|
|
|
|
|
|
return client.RemoveObject(
|
|
|
|
ctx, garage.GlobalBucket, filePath,
|
|
|
|
minio.RemoveObjectOptions{},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
// GetGarageBootstrapHosts loads the <hostname>.yml.signed file for all hosts
|
|
|
|
// stored in garage.
|
|
|
|
func (b Bootstrap) GetGarageBootstrapHosts(
|
|
|
|
ctx context.Context,
|
2022-11-13 15:45:42 +00:00
|
|
|
logger *mlog.Logger,
|
2022-10-15 16:41:07 +00:00
|
|
|
) (
|
|
|
|
map[string]Host, error,
|
|
|
|
) {
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
client := b.GlobalBucketS3APIClient()
|
|
|
|
|
2022-10-15 16:41:07 +00:00
|
|
|
hosts := map[string]Host{}
|
|
|
|
|
|
|
|
objInfoCh := client.ListObjects(
|
|
|
|
ctx, garage.GlobalBucket,
|
|
|
|
minio.ListObjectsOptions{
|
|
|
|
Prefix: garageGlobalBucketBootstrapHostsDirPath,
|
|
|
|
Recursive: true,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
for objInfo := range objInfoCh {
|
|
|
|
|
2022-11-16 16:27:42 +00:00
|
|
|
ctx := mctx.Annotate(ctx, "objectKey", objInfo.Key)
|
2022-11-13 15:45:42 +00:00
|
|
|
|
2022-10-15 16:41:07 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-11-05 14:23:29 +00:00
|
|
|
hostB, hostSig, err := nebula.Unwrap(obj)
|
2022-10-15 16:41:07 +00:00
|
|
|
obj.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
2022-10-29 19:11:40 +00:00
|
|
|
return nil, fmt.Errorf("unwrapping signature from %q: %w", objInfo.Key, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var host Host
|
|
|
|
if err = yaml.Unmarshal(hostB, &host); err != nil {
|
2022-10-15 16:41:07 +00:00
|
|
|
return nil, fmt.Errorf("yaml decoding object %q: %w", objInfo.Key, err)
|
|
|
|
}
|
|
|
|
|
2022-11-05 14:23:29 +00:00
|
|
|
hostPublicCredsB, hostPublicCredsSig, err := nebula.Unwrap(
|
|
|
|
strings.NewReader(host.Nebula.SignedPublicCredentials),
|
|
|
|
)
|
2022-10-29 19:11:40 +00:00
|
|
|
|
2022-11-05 14:23:29 +00:00
|
|
|
if err != nil {
|
2022-11-13 15:45:42 +00:00
|
|
|
logger.Warn(ctx, "unwrapping signed public creds", err)
|
2022-11-05 14:23:29 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
err = nebula.ValidateSignature(
|
|
|
|
b.Nebula.CAPublicCredentials.SigningKeyPEM,
|
|
|
|
hostPublicCredsB,
|
|
|
|
hostPublicCredsSig,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
2022-11-13 15:45:42 +00:00
|
|
|
logger.Warn(ctx, "invalid signed public creds", err)
|
2022-11-05 14:23:29 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var hostPublicCreds nebula.HostPublicCredentials
|
|
|
|
if err := yaml.Unmarshal(hostPublicCredsB, &hostPublicCreds); err != nil {
|
2022-11-13 15:45:42 +00:00
|
|
|
logger.Warn(ctx, "yaml unmarshaling signed public creds", err)
|
2022-10-29 19:11:40 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-11-05 14:23:29 +00:00
|
|
|
err = nebula.ValidateSignature(hostPublicCreds.SigningKeyPEM, hostB, hostSig)
|
|
|
|
|
|
|
|
if err != nil {
|
2022-11-13 15:45:42 +00:00
|
|
|
logger.Warn(ctx, "invalid host data", err)
|
2022-10-29 19:11:40 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-10-15 16:41:07 +00:00
|
|
|
hosts[host.Name] = host
|
|
|
|
}
|
|
|
|
|
|
|
|
return hosts, nil
|
|
|
|
}
|