2024-09-09 14:34:00 +00:00
|
|
|
package network
|
2024-06-17 20:15:28 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2024-09-09 14:34:00 +00:00
|
|
|
"errors"
|
2024-06-17 20:15:28 +00:00
|
|
|
"fmt"
|
|
|
|
"isle/bootstrap"
|
2024-09-07 13:11:04 +00:00
|
|
|
"isle/daemon/daecommon"
|
2024-06-17 20:15:28 +00:00
|
|
|
"isle/garage"
|
|
|
|
"isle/nebula"
|
2024-09-09 14:34:00 +00:00
|
|
|
"isle/secrets"
|
|
|
|
"net"
|
2024-06-17 20:15:28 +00:00
|
|
|
"path/filepath"
|
2024-09-09 14:34:00 +00:00
|
|
|
"strconv"
|
2024-06-17 20:15:28 +00:00
|
|
|
|
2024-06-22 15:49:56 +00:00
|
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
2024-07-07 10:44:49 +00:00
|
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
2024-06-17 20:15:28 +00:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Paths within garage's global bucket.
|
|
|
|
const (
|
|
|
|
garageGlobalBucketBootstrapHostsDirPath = "bootstrap/hosts"
|
|
|
|
)
|
|
|
|
|
2024-10-24 20:14:13 +00:00
|
|
|
func getGarageClientParams(
|
|
|
|
ctx context.Context,
|
|
|
|
secretsStore secrets.Store,
|
|
|
|
currBootstrap bootstrap.Bootstrap,
|
2024-09-09 14:34:00 +00:00
|
|
|
) (
|
|
|
|
GarageClientParams, error,
|
|
|
|
) {
|
|
|
|
creds, err := daecommon.GetGarageS3APIGlobalBucketCredentials(
|
2024-10-24 20:14:13 +00:00
|
|
|
ctx, secretsStore,
|
2024-09-09 14:34:00 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return GarageClientParams{}, fmt.Errorf("getting garage global bucket creds: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-10-24 20:14:13 +00:00
|
|
|
rpcSecret, err := daecommon.GetGarageRPCSecret(ctx, secretsStore)
|
2024-09-09 14:34:00 +00:00
|
|
|
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
|
|
|
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return GarageClientParams{
|
|
|
|
Peer: currBootstrap.ChooseGaragePeer(),
|
|
|
|
GlobalBucketS3APICredentials: creds,
|
|
|
|
RPCSecret: rpcSecret,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func garageAdminClientLogger(logger *mlog.Logger) *mlog.Logger {
|
|
|
|
return logger.WithNamespace("garageAdminClient")
|
|
|
|
}
|
|
|
|
|
|
|
|
// newGarageAdminClient will return an AdminClient for a local garage instance,
|
|
|
|
// or it will _panic_ if there is no local instance configured.
|
|
|
|
func newGarageAdminClient(
|
|
|
|
logger *mlog.Logger,
|
2024-09-10 20:51:33 +00:00
|
|
|
networkConfig daecommon.NetworkConfig,
|
2024-09-09 14:34:00 +00:00
|
|
|
adminToken string,
|
2024-10-31 12:04:19 +00:00
|
|
|
host bootstrap.Host,
|
2024-09-09 14:34:00 +00:00
|
|
|
) *garage.AdminClient {
|
|
|
|
return garage.NewAdminClient(
|
|
|
|
garageAdminClientLogger(logger),
|
|
|
|
net.JoinHostPort(
|
2024-10-31 12:04:19 +00:00
|
|
|
host.IP().String(),
|
2024-09-10 20:51:33 +00:00
|
|
|
strconv.Itoa(networkConfig.Storage.Allocations[0].AdminPort),
|
2024-09-09 14:34:00 +00:00
|
|
|
),
|
|
|
|
adminToken,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func garageApplyLayout(
|
|
|
|
ctx context.Context,
|
|
|
|
logger *mlog.Logger,
|
2024-09-10 20:51:33 +00:00
|
|
|
networkConfig daecommon.NetworkConfig,
|
2024-09-09 14:34:00 +00:00
|
|
|
adminToken string,
|
2024-10-31 12:04:19 +00:00
|
|
|
prevHost, currHost bootstrap.Host,
|
2024-09-09 14:34:00 +00:00
|
|
|
) error {
|
|
|
|
|
|
|
|
var (
|
|
|
|
adminClient = newGarageAdminClient(
|
2024-10-31 12:04:19 +00:00
|
|
|
logger, networkConfig, adminToken, currHost,
|
2024-09-09 14:34:00 +00:00
|
|
|
)
|
2024-10-31 12:04:19 +00:00
|
|
|
hostName = currHost.Name
|
2024-09-10 20:51:33 +00:00
|
|
|
allocs = networkConfig.Storage.Allocations
|
2024-09-09 14:34:00 +00:00
|
|
|
peers = make([]garage.PeerLayout, len(allocs))
|
2024-10-31 12:04:19 +00:00
|
|
|
peerIDs = map[string]struct{}{}
|
|
|
|
|
|
|
|
idsToRemove = make([]string, 0, len(prevHost.Garage.Instances))
|
2024-09-09 14:34:00 +00:00
|
|
|
)
|
|
|
|
|
2024-10-07 19:12:47 +00:00
|
|
|
defer adminClient.Close()
|
|
|
|
|
2024-09-09 14:34:00 +00:00
|
|
|
for i, alloc := range allocs {
|
2024-10-31 12:04:19 +00:00
|
|
|
id := daecommon.BootstrapGarageHostForAlloc(currHost, alloc).ID
|
|
|
|
peerIDs[id] = struct{}{}
|
2024-09-09 14:34:00 +00:00
|
|
|
|
|
|
|
zone := string(hostName)
|
|
|
|
if alloc.Zone != "" {
|
|
|
|
zone = alloc.Zone
|
|
|
|
}
|
|
|
|
|
|
|
|
peers[i] = garage.PeerLayout{
|
|
|
|
ID: id,
|
|
|
|
Capacity: alloc.Capacity * 1_000_000_000,
|
|
|
|
Zone: zone,
|
|
|
|
Tags: []string{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-31 12:04:19 +00:00
|
|
|
for _, prevInst := range prevHost.Garage.Instances {
|
|
|
|
if _, ok := peerIDs[prevInst.ID]; !ok {
|
|
|
|
idsToRemove = append(idsToRemove, prevInst.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return adminClient.ApplyLayout(ctx, peers, idsToRemove)
|
2024-09-09 14:34:00 +00:00
|
|
|
}
|
|
|
|
|
2024-07-07 18:01:10 +00:00
|
|
|
func garageInitializeGlobalBucket(
|
|
|
|
ctx context.Context,
|
|
|
|
logger *mlog.Logger,
|
2024-09-10 20:51:33 +00:00
|
|
|
networkConfig daecommon.NetworkConfig,
|
2024-07-14 10:19:39 +00:00
|
|
|
adminToken string,
|
2024-10-31 12:04:19 +00:00
|
|
|
host bootstrap.Host,
|
2024-07-07 18:01:10 +00:00
|
|
|
) (
|
|
|
|
garage.S3APICredentials, error,
|
|
|
|
) {
|
2024-10-31 12:04:19 +00:00
|
|
|
adminClient := newGarageAdminClient(logger, networkConfig, adminToken, host)
|
2024-10-07 19:12:47 +00:00
|
|
|
defer adminClient.Close()
|
2024-07-07 18:01:10 +00:00
|
|
|
|
|
|
|
creds, err := adminClient.CreateS3APICredentials(
|
|
|
|
ctx, garage.GlobalBucketS3APICredentialsName,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return creds, fmt.Errorf("creating global bucket credentials: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
bucketID, err := adminClient.CreateBucket(ctx, garage.GlobalBucket)
|
|
|
|
if err != nil {
|
|
|
|
return creds, fmt.Errorf("creating global bucket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := adminClient.GrantBucketPermissions(
|
|
|
|
ctx,
|
|
|
|
bucketID,
|
|
|
|
creds.ID,
|
|
|
|
garage.BucketPermissionRead,
|
|
|
|
garage.BucketPermissionWrite,
|
|
|
|
); err != nil {
|
|
|
|
return creds, fmt.Errorf(
|
|
|
|
"granting permissions to shared global bucket key: %w", err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return creds, nil
|
|
|
|
}
|
|
|
|
|
2024-10-24 20:14:13 +00:00
|
|
|
func getGarageBootstrapHosts(
|
|
|
|
ctx context.Context,
|
|
|
|
logger *mlog.Logger,
|
|
|
|
secretsStore secrets.Store,
|
|
|
|
currBootstrap bootstrap.Bootstrap,
|
2024-06-17 20:15:28 +00:00
|
|
|
) (
|
2024-07-12 13:30:21 +00:00
|
|
|
map[nebula.HostName]bootstrap.Host, error,
|
2024-06-17 20:15:28 +00:00
|
|
|
) {
|
2024-10-24 20:14:13 +00:00
|
|
|
garageClientParams, err := getGarageClientParams(
|
|
|
|
ctx, secretsStore, currBootstrap,
|
|
|
|
)
|
2024-07-13 12:34:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("getting garage client params: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-06-17 20:15:28 +00:00
|
|
|
var (
|
2024-07-13 12:34:06 +00:00
|
|
|
client = garageClientParams.GlobalBucketS3APIClient()
|
2024-07-12 13:30:21 +00:00
|
|
|
hosts = map[nebula.HostName]bootstrap.Host{}
|
2024-06-17 20:15:28 +00:00
|
|
|
|
|
|
|
objInfoCh = client.ListObjects(
|
|
|
|
ctx, garage.GlobalBucket,
|
|
|
|
minio.ListObjectsOptions{
|
|
|
|
Prefix: garageGlobalBucketBootstrapHostsDirPath,
|
|
|
|
Recursive: true,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2024-10-07 19:12:47 +00:00
|
|
|
defer client.Close()
|
|
|
|
|
2024-06-17 20:15:28 +00:00
|
|
|
for objInfo := range objInfoCh {
|
|
|
|
|
|
|
|
ctx := mctx.Annotate(ctx, "objectKey", 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
var authedHost bootstrap.AuthenticatedHost
|
|
|
|
|
|
|
|
err = json.NewDecoder(obj).Decode(&authedHost)
|
|
|
|
obj.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
2024-10-24 20:14:13 +00:00
|
|
|
logger.Warn(ctx, "Object contains invalid json", err)
|
2024-06-17 20:15:28 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-07-13 12:34:06 +00:00
|
|
|
host, err := authedHost.Unwrap(currBootstrap.CAPublicCredentials)
|
2024-06-17 20:15:28 +00:00
|
|
|
if err != nil {
|
2024-10-24 20:14:13 +00:00
|
|
|
logger.Warn(ctx, "Host could not be authenticated", err)
|
2024-06-17 20:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hosts[host.Name] = host
|
|
|
|
}
|
|
|
|
|
|
|
|
return hosts, nil
|
|
|
|
}
|
2024-07-12 15:05:39 +00:00
|
|
|
|
2024-09-09 14:34:00 +00:00
|
|
|
// putGarageBoostrapHost places the <hostname>.json.signed file for this host
|
|
|
|
// into garage so that other hosts are able to see relevant configuration for
|
|
|
|
// it.
|
2024-10-24 20:14:13 +00:00
|
|
|
func putGarageBoostrapHost(
|
|
|
|
ctx context.Context,
|
|
|
|
secretsStore secrets.Store,
|
|
|
|
currBootstrap bootstrap.Bootstrap,
|
2024-09-09 14:34:00 +00:00
|
|
|
) error {
|
2024-10-24 20:14:13 +00:00
|
|
|
garageClientParams, err := getGarageClientParams(
|
|
|
|
ctx, secretsStore, currBootstrap,
|
|
|
|
)
|
2024-09-09 14:34:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("getting garage client params: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
host = currBootstrap.ThisHost()
|
|
|
|
client = garageClientParams.GlobalBucketS3APIClient()
|
|
|
|
)
|
|
|
|
|
2024-10-07 19:12:47 +00:00
|
|
|
defer client.Close()
|
|
|
|
|
2024-09-09 14:34:00 +00:00
|
|
|
configured, err := nebula.Sign(
|
|
|
|
host.HostConfigured, currBootstrap.PrivateCredentials.SigningPrivateKey,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("signing host configured data: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
hostB, err := json.Marshal(bootstrap.AuthenticatedHost{
|
|
|
|
Assigned: currBootstrap.SignedHostAssigned,
|
|
|
|
Configured: configured,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encoding host data: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
filePath := filepath.Join(
|
|
|
|
garageGlobalBucketBootstrapHostsDirPath,
|
|
|
|
string(host.Name)+".json.signed",
|
|
|
|
)
|
|
|
|
|
|
|
|
_, err = client.PutObject(
|
|
|
|
ctx,
|
|
|
|
garage.GlobalBucket,
|
|
|
|
filePath,
|
|
|
|
bytes.NewReader(hostB),
|
|
|
|
int64(len(hostB)),
|
|
|
|
minio.PutObjectOptions{},
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("writing to %q in global bucket: %w", filePath, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-12 15:05:39 +00:00
|
|
|
func removeGarageBootstrapHost(
|
2024-10-07 19:12:47 +00:00
|
|
|
ctx context.Context, client *garage.S3APIClient, hostName nebula.HostName,
|
2024-07-12 15:05:39 +00:00
|
|
|
) error {
|
|
|
|
|
|
|
|
filePath := filepath.Join(
|
|
|
|
garageGlobalBucketBootstrapHostsDirPath,
|
|
|
|
string(hostName)+".json.signed",
|
|
|
|
)
|
|
|
|
|
|
|
|
return client.RemoveObject(
|
|
|
|
ctx, garage.GlobalBucket, filePath, minio.RemoveObjectOptions{},
|
|
|
|
)
|
|
|
|
}
|