Compare commits

..

6 Commits

Author SHA1 Message Date
Brian Picciano
c19b2f53dd WIP 2022-10-16 22:17:26 +02:00
Brian Picciano
7a25e1b6e6 Initial implementation of garage.AdminClient 2022-10-16 22:17:24 +02:00
Brian Picciano
eba9b23e61 Introduce admin.CreationParams 2022-10-16 22:07:03 +02:00
Brian Picciano
f720d7accd Enable the garage admin interface 2022-10-16 21:22:58 +02:00
Brian Picciano
51e21c3e46 Get rid of garage web port
It's not clear how we would be using it at this point, and garage 0.8.0
allows us to leave it off, so might as well do so.
2022-10-16 21:12:33 +02:00
Brian Picciano
5e08061cd6 Factor out garage-entrypoint
The daemon entrypoint now starts the garage child processes directly,
without the extra step of indirection
2022-10-16 20:48:33 +02:00
11 changed files with 198 additions and 27 deletions

View File

@ -64,7 +64,7 @@ storage:
# Capacity declares how many gigabytes can be stored in each allocation, and # Capacity declares how many gigabytes can be stored in each allocation, and
# is required. It must be a multiple of 100. # is required. It must be a multiple of 100.
# #
# The various ports are all required and must all be unique within and across # The ports are all required and must all be unique within and across
# allocations. # allocations.
allocations: allocations:
@ -73,4 +73,4 @@ storage:
# capacity: 1200 # capacity: 1200
# api_port: 3900 # api_port: 3900
# rpc_port: 3901 # rpc_port: 3901
# web_port: 3902 # admin_port: 3902

View File

@ -36,7 +36,6 @@ storage:
capacity: 1200 capacity: 1200
api_port: 3900 api_port: 3900
rpc_port: 3901 rpc_port: 3901
web_port: 3902
# 100 GB (the minimum) are being shared from drive2 # 100 GB (the minimum) are being shared from drive2
- data_path: /mnt/drive2/cryptic-net/data - data_path: /mnt/drive2/cryptic-net/data
@ -44,7 +43,6 @@ storage:
capacity: 100 capacity: 100
api_port: 3910 api_port: 3910
rpc_port: 3911 rpc_port: 3911
web_port: 3912
``` ```
## Setup Firewall ## Setup Firewall

View File

@ -36,6 +36,7 @@ type Bootstrap struct {
NebulaHostCert nebula.HostCert NebulaHostCert nebula.HostCert
GarageRPCSecret string GarageRPCSecret string
GarageAdminToken string
GarageGlobalBucketS3APICredentials garage.S3APICredentials GarageGlobalBucketS3APICredentials garage.S3APICredentials
} }
@ -75,6 +76,7 @@ func FromFS(bootstrapFS fs.FS) (Bootstrap, error) {
{&b.NebulaHostCert.HostCert, nebulaCertsHostCertPath}, {&b.NebulaHostCert.HostCert, nebulaCertsHostCertPath},
{&b.NebulaHostCert.HostKey, nebulaCertsHostKeyPath}, {&b.NebulaHostCert.HostKey, nebulaCertsHostKeyPath},
{&b.GarageRPCSecret, garageRPCSecretPath}, {&b.GarageRPCSecret, garageRPCSecretPath},
{&b.GarageAdminToken, garageAdminTokenPath},
} }
for _, f := range filesToLoadAsString { for _, f := range filesToLoadAsString {
@ -155,6 +157,7 @@ func (b Bootstrap) WriteTo(into io.Writer) error {
{b.NebulaHostCert.HostCert, nebulaCertsHostCertPath}, {b.NebulaHostCert.HostCert, nebulaCertsHostCertPath},
{b.NebulaHostCert.HostKey, nebulaCertsHostKeyPath}, {b.NebulaHostCert.HostKey, nebulaCertsHostKeyPath},
{b.GarageRPCSecret, garageRPCSecretPath}, {b.GarageRPCSecret, garageRPCSecretPath},
{b.GarageAdminToken, garageAdminTokenPath},
} }
for _, f := range filesToWriteAsString { for _, f := range filesToWriteAsString {

View File

@ -7,8 +7,9 @@ import (
// Paths within the bootstrap FS related to garage. // Paths within the bootstrap FS related to garage.
const ( const (
garageGlobalBucketKeyYmlPath = "garage/cryptic-net-global-bucket-key.yml"
garageRPCSecretPath = "garage/rpc-secret.txt" garageRPCSecretPath = "garage/rpc-secret.txt"
garageAdminTokenPath = "garage/admin-token.txt"
garageGlobalBucketKeyYmlPath = "garage/cryptic-net-global-bucket-key.yml"
) )
// GaragePeers returns a Peer for each known garage instance in the network. // GaragePeers returns a Peer for each known garage instance in the network.

View File

@ -24,7 +24,6 @@ type NebulaHost struct {
type GarageHostInstance struct { type GarageHostInstance struct {
RPCPort int `yaml:"rpc_port"` RPCPort int `yaml:"rpc_port"`
S3APIPort int `yaml:"s3_api_port"` S3APIPort int `yaml:"s3_api_port"`
WebPort int `yaml:"web_port"`
} }
// GarageHost describes the garage configuration of a Host which is relevant for // GarageHost describes the garage configuration of a Host which is relevant for

View File

@ -2,8 +2,10 @@ package entrypoint
import ( import (
"context" "context"
crypticnet "cryptic-net"
"cryptic-net/admin" "cryptic-net/admin"
"cryptic-net/bootstrap" "cryptic-net/bootstrap"
"cryptic-net/garage"
"cryptic-net/nebula" "cryptic-net/nebula"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
@ -11,6 +13,7 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"strconv"
"strings" "strings"
"github.com/cryptic-io/pmux/pmuxlib" "github.com/cryptic-io/pmux/pmuxlib"
@ -45,6 +48,73 @@ func readAdmin(path string) (admin.Admin, error) {
return admin.FromReader(f) return admin.FromReader(f)
} }
func garageInitializeGlobalBucket(
env *crypticnet.Env, globalBucketCreds garage.S3APICredentials,
) error {
var (
ctx = env.Context
thisHost = env.Bootstrap.ThisHost()
thisDaemon = env.ThisDaemon()
allocs = thisDaemon.Storage.Allocations
)
adminClient := garage.NewAdminClient(
net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(allocs[0].AdminPort)),
env.Bootstrap.GarageAdminToken,
)
// first attempt to import the key
err := adminClient.Do(ctx, nil, "POST", "/v0/key/import", map[string]string{
"accessKeyId": globalBucketCreds.ID,
"secretAccessKey": globalBucketCreds.Secret,
"name": "shared-global-bucket-key",
})
if err != nil {
return fmt.Errorf("importing global bucket key into garage: %w", err)
}
// create global bucket
err = adminClient.Do(ctx, nil, "POST", "/v0/bucket", map[string]string{
"globalAlias": garage.GlobalBucket,
})
if err != nil {
return fmt.Errorf("creating global bucket: %w", err)
}
// retrieve newly created bucket's id
var getBucketRes struct {
ID string `json:"id"`
}
err = adminClient.Do(
ctx, &getBucketRes,
"GET", "/v0/bucket?globalAlias="+garage.GlobalBucket, nil,
)
if err != nil {
return fmt.Errorf("fetching global bucket id: %w", err)
}
// allow shared global bucket key to perform all operations
err = adminClient.Do(ctx, nil, "POST", "/v0/bucket/allow", map[string]interface{}{
"bucketId": getBucketRes.ID,
"accessKeyId": globalBucketCreds.ID,
"permissions": map[string]bool{
"read": true,
"write": true,
},
})
if err != nil {
return fmt.Errorf("granting permissions to shared global bucket key: %w", err)
}
return nil
}
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.tgz to stdout",
@ -183,7 +253,6 @@ var subCmdAdminCreateNetwork = subCmd{
} }
ctx, cancel := context.WithCancel(env.Context) ctx, cancel := context.WithCancel(env.Context)
defer cancel()
pmuxDoneCh := make(chan struct{}) pmuxDoneCh := make(chan struct{})
go func() { go func() {
@ -191,6 +260,21 @@ var subCmdAdminCreateNetwork = subCmd{
close(pmuxDoneCh) close(pmuxDoneCh)
}() }()
defer func() {
cancel()
<-pmuxDoneCh
}()
globalBucketCreds := garage.S3APICredentials{} // TODO
// TODO wait for garage to be confirmed as booted up
// TODO apply layout
if err := garageInitializeGlobalBucket(env, globalBucketCreds); err != nil {
return fmt.Errorf("initializing shared global bucket: %w", err)
}
panic("TODO: create and output admin.tgz")
}, },
} }
@ -246,7 +330,12 @@ var subCmdAdminMakeBootstrap = subCmd{
return fmt.Errorf("couldn't find host into for %q in garage, has `cryptic-net hosts add` been run yet?", *name) return fmt.Errorf("couldn't find host into for %q in garage, has `cryptic-net hosts add` been run yet?", *name)
} }
nebulaHostCert, err := nebula.NewHostCert(adm.NebulaCACert, host.Name, host.Nebula.IP) ip := net.ParseIP(host.Nebula.IP)
if ip == nil {
return fmt.Errorf("invalid IP stored with host %q: %q", *name, host.Nebula.IP)
}
nebulaHostCert, err := nebula.NewHostCert(adm.NebulaCACert, host.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)
} }
@ -260,6 +349,7 @@ var subCmdAdminMakeBootstrap = subCmd{
NebulaHostCert: nebulaHostCert, NebulaHostCert: nebulaHostCert,
GarageRPCSecret: adm.GarageRPCSecret, GarageRPCSecret: adm.GarageRPCSecret,
GarageAdminToken: randStr(32),
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials, GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
} }

View File

@ -60,7 +60,6 @@ func mergeDaemonIntoBootstrap(env *crypticnet.Env) error {
host.Garage.Instances = append(host.Garage.Instances, bootstrap.GarageHostInstance{ host.Garage.Instances = append(host.Garage.Instances, bootstrap.GarageHostInstance{
RPCPort: alloc.RPCPort, RPCPort: alloc.RPCPort,
S3APIPort: alloc.S3APIPort, S3APIPort: alloc.S3APIPort,
WebPort: alloc.WebPort,
}) })
} }
} }
@ -157,10 +156,11 @@ func garageWriteChildConf(
DataPath: alloc.DataPath, DataPath: alloc.DataPath,
RPCSecret: env.Bootstrap.GarageRPCSecret, RPCSecret: env.Bootstrap.GarageRPCSecret,
AdminToken: env.Bootstrap.GarageAdminToken,
RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)), RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)), APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)),
WebAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.WebPort)), AdminAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.AdminPort)),
BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(), BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(),
}) })

View File

@ -102,11 +102,6 @@ func Main() {
Proto: "tcp", Proto: "tcp",
Host: "any", Host: "any",
}, },
crypticnet.ConfigFirewallRule{
Port: strconv.Itoa(alloc.WebPort),
Proto: "tcp",
Host: "any",
},
) )
} }

View File

@ -31,9 +31,9 @@ type DaemonYmlStorageAllocation struct {
DataPath string `yaml:"data_path"` DataPath string `yaml:"data_path"`
MetaPath string `yaml:"meta_path"` MetaPath string `yaml:"meta_path"`
Capacity int `yaml:"capacity"` Capacity int `yaml:"capacity"`
S3APIPort int `yaml:"api_port"` // TODO fix field name here S3APIPort int `yaml:"s3_api_port"`
RPCPort int `yaml:"rpc_port"` RPCPort int `yaml:"rpc_port"`
WebPort int `yaml:"web_port"` AdminPort int `yaml:"admin_port"`
} }
// DaemonYml describes the structure of the daemon.yml file. // DaemonYml describes the structure of the daemon.yml file.

View File

@ -0,0 +1,84 @@
package garage
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)
// AdminClient is a helper type for performing actions against the garage admin
// interface.
type AdminClient struct {
c *http.Client
addr string
adminToken string
}
// NewAdminClient initializes and returns an AdminClient which will use the
// given address and adminToken for all requests made.
func NewAdminClient(addr, adminToken string) *AdminClient {
return &AdminClient{
c: &http.Client{
Transport: http.DefaultTransport.(*http.Transport).Clone(),
},
addr: addr,
adminToken: adminToken,
}
}
// Do performs an HTTP request with the given method (GET, POST) and path, and
// using the json marshaling of the given body as the request body (unless body
// is nil). It will JSON unmarshal the response into rcv, unless rcv is nil.
func (c *AdminClient) Do(
ctx context.Context, rcv interface{}, method, path string, body interface{},
) error {
var bodyR io.Reader
if body != nil {
bodyBuf := new(bytes.Buffer)
bodyR = bodyBuf
if err := json.NewEncoder(bodyBuf).Encode(body); err != nil {
return fmt.Errorf("json marshaling body: %w", err)
}
}
urlStr := fmt.Sprintf("http://%s%s", c.addr, path)
req, err := http.NewRequestWithContext(ctx, method, urlStr, bodyR)
if err != nil {
return fmt.Errorf("initializing request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.adminToken)
res, err := c.c.Do(req)
if err != nil {
return fmt.Errorf("performing http request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("unexpected %s response returned", res.Status)
}
if rcv == nil {
if _, err := io.Copy(io.Discard, res.Body); err != nil {
return fmt.Errorf("discarding response body: %w", err)
}
return nil
}
if err := json.NewDecoder(res.Body).Decode(rcv); err != nil {
return fmt.Errorf("decoding json response body: %w", err)
}
return nil
}

View File

@ -14,10 +14,11 @@ type GarageTomlData struct {
DataPath string DataPath string
RPCSecret string RPCSecret string
AdminToken string
RPCAddr string RPCAddr string
APIAddr string APIAddr string
WebAddr string AdminAddr string
BootstrapPeers []string BootstrapPeers []string
} }
@ -41,9 +42,9 @@ bootstrap_peers = [{{- range .BootstrapPeers }}
api_bind_addr = "{{ .APIAddr }}" api_bind_addr = "{{ .APIAddr }}"
s3_region = "garage" s3_region = "garage"
[s3_web] [admin]
bind_addr = "{{ .WebAddr }}" api_bind_addr = "{{ .AdminAddr }}"
root_domain = ".example.com" admin_token = "{{ .AdminToken }}"
`)) `))