Compare commits
2 Commits
279c79a9f1
...
736b23429c
Author | SHA1 | Date | |
---|---|---|---|
736b23429c | |||
1ee396c976 |
@ -103,7 +103,7 @@ To create the network, and the `admin.json` file in the process, run:
|
|||||||
```
|
```
|
||||||
sudo isle network create \
|
sudo isle network create \
|
||||||
--name <name> \
|
--name <name> \
|
||||||
--ip-net <ip/subnet-prefix> \
|
--ip-net <subnet> \
|
||||||
--domain <domain> \
|
--domain <domain> \
|
||||||
--hostname <hostname> \
|
--hostname <hostname> \
|
||||||
| gpg -e -r <my gpg email> \
|
| gpg -e -r <my gpg email> \
|
||||||
@ -112,11 +112,6 @@ sudo isle network create \
|
|||||||
|
|
||||||
A couple of notes here:
|
A couple of notes here:
|
||||||
|
|
||||||
* The `--ip-net` parameter is formed from both the subnet and the IP you chose
|
|
||||||
within it. So if your subnet is `10.10.0.0/16`, and your chosen IP in that
|
|
||||||
subnet is `10.10.4.20`, then your `--ip-net` parameter will be
|
|
||||||
`10.10.4.20/16`.
|
|
||||||
|
|
||||||
* Only one gpg recipient is specified. If you intend on including other users as
|
* Only one gpg recipient is specified. If you intend on including other users as
|
||||||
network administrators you can add them to the recipients list at this step,
|
network administrators you can add them to the recipients list at this step,
|
||||||
so they will be able to use the `admin.json` file as well. You can also
|
so they will be able to use the `admin.json` file as well. You can also
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/garage"
|
"isle/garage"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@ -51,7 +51,7 @@ type Bootstrap struct {
|
|||||||
HostAssigned `json:"-"`
|
HostAssigned `json:"-"`
|
||||||
SignedHostAssigned nebula.Signed[HostAssigned] // signed by CA
|
SignedHostAssigned nebula.Signed[HostAssigned] // signed by CA
|
||||||
|
|
||||||
Hosts map[string]Host
|
Hosts map[nebula.HostName]Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes and returns a new Bootstrap file for a new host. This
|
// New initializes and returns a new Bootstrap file for a new host. This
|
||||||
@ -60,8 +60,8 @@ func New(
|
|||||||
caCreds nebula.CACredentials,
|
caCreds nebula.CACredentials,
|
||||||
adminCreationParams admin.CreationParams,
|
adminCreationParams admin.CreationParams,
|
||||||
garage Garage,
|
garage Garage,
|
||||||
name string,
|
name nebula.HostName,
|
||||||
ip net.IP,
|
ip netip.Addr,
|
||||||
) (
|
) (
|
||||||
Bootstrap, error,
|
Bootstrap, error,
|
||||||
) {
|
) {
|
||||||
@ -89,7 +89,7 @@ func New(
|
|||||||
PrivateCredentials: hostPrivCreds,
|
PrivateCredentials: hostPrivCreds,
|
||||||
HostAssigned: assigned,
|
HostAssigned: assigned,
|
||||||
SignedHostAssigned: signedAssigned,
|
SignedHostAssigned: signedAssigned,
|
||||||
Hosts: map[string]Host{},
|
Hosts: map[nebula.HostName]Host{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ func (b Bootstrap) ThisHost() Host {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns a deterministic hash of the given hosts map.
|
// Hash returns a deterministic hash of the given hosts map.
|
||||||
func HostsHash(hostsMap map[string]Host) ([]byte, error) {
|
func HostsHash(hostsMap map[nebula.HostName]Host) ([]byte, error) {
|
||||||
|
|
||||||
hosts := make([]Host, 0, len(hostsMap))
|
hosts := make([]Host, 0, len(hostsMap))
|
||||||
for _, host := range hostsMap {
|
for _, host := range hostsMap {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/garage"
|
"isle/garage"
|
||||||
|
"isle/nebula"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
||||||
@ -46,12 +47,12 @@ func (b Bootstrap) GetGarageBootstrapHosts(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
) (
|
) (
|
||||||
map[string]Host, error,
|
map[nebula.HostName]Host, error,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
client := b.GlobalBucketS3APIClient()
|
client := b.GlobalBucketS3APIClient()
|
||||||
|
|
||||||
hosts := map[string]Host{}
|
hosts := map[nebula.HostName]Host{}
|
||||||
|
|
||||||
objInfoCh := client.ListObjects(
|
objInfoCh := client.ListObjects(
|
||||||
ctx, garage.GlobalBucket,
|
ctx, garage.GlobalBucket,
|
||||||
|
@ -28,7 +28,7 @@ type GarageHost struct {
|
|||||||
// HostAssigned are all fields related to a host which were assigned to it by an
|
// HostAssigned are all fields related to a host which were assigned to it by an
|
||||||
// admin.
|
// admin.
|
||||||
type HostAssigned struct {
|
type HostAssigned struct {
|
||||||
Name string
|
Name nebula.HostName
|
||||||
PublicCredentials nebula.HostPublicCredentials
|
PublicCredentials nebula.HostPublicCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,17 +46,20 @@ var subCmdAdminCreateBootstrap = subCmd{
|
|||||||
descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout",
|
descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout",
|
||||||
checkLock: false,
|
checkLock: false,
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
var (
|
||||||
|
flags = subCmdCtx.flagSet(false)
|
||||||
|
hostName nebula.HostName
|
||||||
|
ip netip.Addr
|
||||||
|
)
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
hostNameF := flags.VarPF(
|
||||||
|
textUnmarshalerFlag{&hostName},
|
||||||
hostName := flags.StringP(
|
"hostname", "h",
|
||||||
"hostname", "h", "",
|
|
||||||
"Name of the host to generate bootstrap.json for",
|
"Name of the host to generate bootstrap.json for",
|
||||||
)
|
)
|
||||||
|
|
||||||
ipStr := flags.StringP(
|
ipF := flags.VarPF(
|
||||||
"ip", "i", "",
|
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host",
|
||||||
"IP of the new host",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
adminPath := flags.StringP(
|
adminPath := flags.StringP(
|
||||||
@ -68,20 +71,12 @@ var subCmdAdminCreateBootstrap = subCmd{
|
|||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *hostName == "" || *ipStr == "" || *adminPath == "" {
|
if !hostNameF.Changed ||
|
||||||
|
!ipF.Changed ||
|
||||||
|
*adminPath == "" {
|
||||||
return errors.New("--hostname, --ip, and --admin-path are required")
|
return errors.New("--hostname, --ip, and --admin-path are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateHostName(*hostName); err != nil {
|
|
||||||
return fmt.Errorf("invalid hostname %q: %w", *hostName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(*ipStr)
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
return fmt.Errorf("invalid ip %q", *ipStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
adm, err := readAdmin(*adminPath)
|
adm, err := readAdmin(*adminPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
|
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
|
||||||
@ -97,19 +92,21 @@ var subCmdAdminCreateBootstrap = subCmd{
|
|||||||
adm.Nebula.CACredentials,
|
adm.Nebula.CACredentials,
|
||||||
adm.CreationParams,
|
adm.CreationParams,
|
||||||
garageBootstrap,
|
garageBootstrap,
|
||||||
*hostName,
|
hostName,
|
||||||
ip,
|
ip,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initializing bootstrap data: %w", err)
|
return fmt.Errorf("initializing bootstrap data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostBootstrap, err := loadHostBootstrap()
|
hostsRes, err := subCmdCtx.getHosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
return fmt.Errorf("getting hosts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newHostBootstrap.Hosts = hostBootstrap.Hosts
|
for _, host := range hostsRes.Hosts {
|
||||||
|
newHostBootstrap.Hosts[host.Name] = host
|
||||||
|
}
|
||||||
|
|
||||||
return newHostBootstrap.WriteTo(os.Stdout)
|
return newHostBootstrap.WriteTo(os.Stdout)
|
||||||
},
|
},
|
||||||
@ -120,17 +117,20 @@ var subCmdAdminCreateNebulaCert = subCmd{
|
|||||||
descr: "Creates a signed nebula certificate file and writes it to stdout",
|
descr: "Creates a signed nebula certificate file and writes it to stdout",
|
||||||
checkLock: false,
|
checkLock: false,
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
var (
|
||||||
flags := subCmdCtx.flagSet(false)
|
flags = subCmdCtx.flagSet(false)
|
||||||
|
hostName nebula.HostName
|
||||||
hostName := flags.StringP(
|
ip netip.Addr
|
||||||
"hostname", "h", "",
|
|
||||||
"Name of the host to generate bootstrap.json for",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ipStr := flags.StringP(
|
hostNameF := flags.VarPF(
|
||||||
"ip", "i", "",
|
textUnmarshalerFlag{&hostName},
|
||||||
"IP of the new host",
|
"hostname", "h",
|
||||||
|
"Name of the host to generate a certificate for",
|
||||||
|
)
|
||||||
|
|
||||||
|
ipF := flags.VarPF(
|
||||||
|
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host",
|
||||||
)
|
)
|
||||||
|
|
||||||
adminPath := flags.StringP(
|
adminPath := flags.StringP(
|
||||||
@ -147,20 +147,13 @@ var subCmdAdminCreateNebulaCert = subCmd{
|
|||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *hostName == "" || *ipStr == "" || *adminPath == "" || *pubKeyPath == "" {
|
if !hostNameF.Changed ||
|
||||||
|
!ipF.Changed ||
|
||||||
|
*adminPath == "" ||
|
||||||
|
*pubKeyPath == "" {
|
||||||
return errors.New("--hostname, --ip, --admin-path, and --pub-key-path are required")
|
return errors.New("--hostname, --ip, --admin-path, and --pub-key-path are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateHostName(*hostName); err != nil {
|
|
||||||
return fmt.Errorf("invalid hostname %q: %w", *hostName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(*ipStr)
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
return fmt.Errorf("invalid ip %q", *ipStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
adm, err := readAdmin(*adminPath)
|
adm, err := readAdmin(*adminPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
|
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
|
||||||
@ -177,7 +170,7 @@ var subCmdAdminCreateNebulaCert = subCmd{
|
|||||||
}
|
}
|
||||||
|
|
||||||
nebulaHostCert, err := nebula.NewHostCert(
|
nebulaHostCert, err := nebula.NewHostCert(
|
||||||
adm.Nebula.CACredentials, hostPub, *hostName, ip,
|
adm.Nebula.CACredentials, hostPub, hostName, ip,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating cert: %w", err)
|
return fmt.Errorf("creating cert: %w", err)
|
||||||
|
15
go/cmd/entrypoint/client.go
Normal file
15
go/cmd/entrypoint/client.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"isle/daemon"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) {
|
||||||
|
var res daemon.GetHostsResult
|
||||||
|
err := ctx.daemonRCPClient.Call(ctx.ctx, &res, "GetHosts", nil)
|
||||||
|
if err != nil {
|
||||||
|
return daemon.GetHostsResult{}, fmt.Errorf("calling GetHosts: %w", err)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
22
go/cmd/entrypoint/flags.go
Normal file
22
go/cmd/entrypoint/flags.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type textUnmarshalerFlag struct {
|
||||||
|
inner interface {
|
||||||
|
encoding.TextUnmarshaler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f textUnmarshalerFlag) Set(v string) error {
|
||||||
|
return f.inner.UnmarshalText([]byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f textUnmarshalerFlag) String() string {
|
||||||
|
return fmt.Sprint(f.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f textUnmarshalerFlag) Type() string { return "string" }
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -26,11 +25,7 @@ var subCmdHostsList = subCmd{
|
|||||||
name: "list",
|
name: "list",
|
||||||
descr: "Lists all hosts in the network, and their IPs",
|
descr: "Lists all hosts in the network, and their IPs",
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
hostsRes, err := subCmdCtx.getHosts()
|
||||||
ctx := subCmdCtx.ctx
|
|
||||||
|
|
||||||
var res daemon.GetHostsResult
|
|
||||||
err := subCmdCtx.daemonRCPClient.Call(ctx, &res, "GetHosts", nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetHosts: %w", err)
|
return fmt.Errorf("calling GetHosts: %w", err)
|
||||||
}
|
}
|
||||||
@ -43,11 +38,11 @@ var subCmdHostsList = subCmd{
|
|||||||
Storage bootstrap.GarageHost `json:",omitempty"`
|
Storage bootstrap.GarageHost `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := make([]host, 0, len(res.Hosts))
|
hosts := make([]host, 0, len(hostsRes.Hosts))
|
||||||
for _, h := range res.Hosts {
|
for _, h := range hostsRes.Hosts {
|
||||||
|
|
||||||
host := host{
|
host := host{
|
||||||
Name: h.Name,
|
Name: string(h.Name),
|
||||||
Storage: h.Garage,
|
Storage: h.Garage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,16 +29,16 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
"Domain name that should be used as the root domain in the network.",
|
"Domain name that should be used as the root domain in the network.",
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.StringVarP(
|
ipNetF := flags.VarPF(
|
||||||
&req.IPNet, "ip-net", "i", "",
|
textUnmarshalerFlag{&req.IPNet}, "ip-net", "i",
|
||||||
`IP+prefix (e.g. "10.10.0.1/16") which denotes the IP of this`+
|
`An IP subnet, in CIDR form, which will be the overall range of`+
|
||||||
` host, which will be the first host in the network, and the`+
|
` possible IPs in the network. The first IP in this network`+
|
||||||
` range of IPs which other hosts in the network can be`+
|
` range will become this first host's IP.`,
|
||||||
` assigned`,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.StringVarP(
|
hostNameF := flags.VarPF(
|
||||||
&req.HostName, "hostname", "h", "",
|
textUnmarshalerFlag{&req.HostName},
|
||||||
|
"hostname", "h",
|
||||||
"Name of this host, which will be the first host in the network",
|
"Name of this host, which will be the first host in the network",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,8 +48,8 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
|
|
||||||
if req.Name == "" ||
|
if req.Name == "" ||
|
||||||
req.Domain == "" ||
|
req.Domain == "" ||
|
||||||
req.IPNet == "" ||
|
!ipNetF.Changed ||
|
||||||
req.HostName == "" {
|
!hostNameF.Changed {
|
||||||
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func dnsmasqPmuxProcConfig(
|
|||||||
hostsSlice := make([]dnsmasq.ConfDataHost, 0, len(hostBootstrap.Hosts))
|
hostsSlice := make([]dnsmasq.ConfDataHost, 0, len(hostBootstrap.Hosts))
|
||||||
for _, host := range hostBootstrap.Hosts {
|
for _, host := range hostBootstrap.Hosts {
|
||||||
hostsSlice = append(hostsSlice, dnsmasq.ConfDataHost{
|
hostsSlice = append(hostsSlice, dnsmasq.ConfDataHost{
|
||||||
Name: host.Name,
|
Name: string(host.Name),
|
||||||
IP: host.IP().String(),
|
IP: host.IP().String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ func GarageApplyLayout(
|
|||||||
|
|
||||||
id := bootstrapGarageHostForAlloc(thisHost, alloc).ID
|
id := bootstrapGarageHostForAlloc(thisHost, alloc).ID
|
||||||
|
|
||||||
zone := hostName
|
zone := string(hostName)
|
||||||
if alloc.Zone != "" {
|
if alloc.Zone != "" {
|
||||||
zone = alloc.Zone
|
zone = alloc.Zone
|
||||||
}
|
}
|
||||||
|
@ -11,29 +11,15 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon/jsonrpc2"
|
|
||||||
"isle/garage"
|
"isle/garage"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`)
|
|
||||||
|
|
||||||
func validateHostName(name string) error {
|
|
||||||
|
|
||||||
if !hostNameRegexp.MatchString(name) {
|
|
||||||
return errors.New("a host's name must start with a letter and only contain letters, numbers, and dashes")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Daemon presents all functionality required for client frontends to interact
|
// Daemon presents all functionality required for client frontends to interact
|
||||||
// with isle, typically via the unix socket.
|
// with isle, typically via the unix socket.
|
||||||
type Daemon interface {
|
type Daemon interface {
|
||||||
@ -41,15 +27,17 @@ type Daemon interface {
|
|||||||
// CreateNetwork will initialize a new network using the given parameters.
|
// CreateNetwork will initialize a new network using the given parameters.
|
||||||
// - name: Human-readable name of the network.
|
// - name: Human-readable name of the network.
|
||||||
// - domain: Primary domain name that network services are served under.
|
// - domain: Primary domain name that network services are served under.
|
||||||
// - ipNetStr: An IP + subnet mask which represents both the IP of this
|
// - ipNet: An IP subnet, in CIDR form, which will be the overall range of
|
||||||
// first host in the network, as well as the overall range of possible IPs
|
// possible IPs in the network. The first IP in this network range will
|
||||||
// in the network.
|
// become this first host's IP.
|
||||||
// - hostName: The name of this first host in the network.
|
// - hostName: The name of this first host in the network.
|
||||||
//
|
//
|
||||||
// An Admin instance is returned, which is necessary to perform admin
|
// An Admin instance is returned, which is necessary to perform admin
|
||||||
// actions in the future.
|
// actions in the future.
|
||||||
CreateNetwork(
|
CreateNetwork(
|
||||||
ctx context.Context, name, domain, ipNetStr, hostName string,
|
ctx context.Context, name, domain string,
|
||||||
|
ipNet nebula.IPNet,
|
||||||
|
hostName nebula.HostName,
|
||||||
) (
|
) (
|
||||||
admin.Admin, error,
|
admin.Admin, error,
|
||||||
)
|
)
|
||||||
@ -61,12 +49,11 @@ type Daemon interface {
|
|||||||
// - ErrAlreadyJoined
|
// - ErrAlreadyJoined
|
||||||
JoinNetwork(context.Context, bootstrap.Bootstrap) error
|
JoinNetwork(context.Context, bootstrap.Bootstrap) error
|
||||||
|
|
||||||
// GetGarageBootstrapHosts loads (and verifies) the <hostname>.json.signed
|
// GetBootstrapHosts returns the hosts stored in the bootstrap.
|
||||||
// file for all hosts stored in garage.
|
GetBootstrapHosts(
|
||||||
GetGarageBootstrapHosts(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (
|
) (
|
||||||
map[string]bootstrap.Host, error,
|
map[nebula.HostName]bootstrap.Host, error,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Shutdown blocks until all resources held or created by the daemon,
|
// Shutdown blocks until all resources held or created by the daemon,
|
||||||
@ -149,10 +136,7 @@ type daemon struct {
|
|||||||
// it should restart itself only when there's something actually requiring a
|
// it should restart itself only when there's something actually requiring a
|
||||||
// restart.
|
// restart.
|
||||||
func NewDaemon(
|
func NewDaemon(
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger, daemonConfig Config, envBinDirPath string, opts *Opts,
|
||||||
daemonConfig Config,
|
|
||||||
envBinDirPath string,
|
|
||||||
opts *Opts,
|
|
||||||
) (
|
) (
|
||||||
Daemon, error,
|
Daemon, error,
|
||||||
) {
|
) {
|
||||||
@ -479,24 +463,11 @@ func (d *daemon) restartLoop(ctx context.Context, readyCh chan<- struct{}) {
|
|||||||
|
|
||||||
func (d *daemon) CreateNetwork(
|
func (d *daemon) CreateNetwork(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
name, domain, ipNetStr, hostName string,
|
name, domain string, ipNet nebula.IPNet, hostName nebula.HostName,
|
||||||
) (
|
) (
|
||||||
admin.Admin, error,
|
admin.Admin, error,
|
||||||
) {
|
) {
|
||||||
ip, subnet, err := net.ParseCIDR(ipNetStr)
|
nebulaCACreds, err := nebula.NewCACredentials(domain, ipNet)
|
||||||
if err != nil {
|
|
||||||
return admin.Admin{}, jsonrpc2.NewInvalidParamsError(
|
|
||||||
"parsing %q as a CIDR: %v", ipNetStr, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateHostName(hostName); err != nil {
|
|
||||||
return admin.Admin{}, jsonrpc2.NewInvalidParamsError(
|
|
||||||
"invalid hostname: %v", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
nebulaCACreds, err := nebula.NewCACredentials(domain, subnet)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return admin.Admin{}, fmt.Errorf("creating nebula CA cert: %w", err)
|
return admin.Admin{}, fmt.Errorf("creating nebula CA cert: %w", err)
|
||||||
}
|
}
|
||||||
@ -519,7 +490,7 @@ func (d *daemon) CreateNetwork(
|
|||||||
adm.CreationParams,
|
adm.CreationParams,
|
||||||
garageBootstrap,
|
garageBootstrap,
|
||||||
hostName,
|
hostName,
|
||||||
ip,
|
ipNet.FirstAddr(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return adm, fmt.Errorf("initializing bootstrap data: %w", err)
|
return adm, fmt.Errorf("initializing bootstrap data: %w", err)
|
||||||
@ -594,17 +565,17 @@ func (d *daemon) JoinNetwork(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *daemon) GetGarageBootstrapHosts(
|
func (d *daemon) GetBootstrapHosts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (
|
) (
|
||||||
map[string]bootstrap.Host, error,
|
map[nebula.HostName]bootstrap.Host, error,
|
||||||
) {
|
) {
|
||||||
return withCurrBootstrap(d, func(
|
return withCurrBootstrap(d, func(
|
||||||
currBootstrap bootstrap.Bootstrap,
|
currBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
map[string]bootstrap.Host, error,
|
map[nebula.HostName]bootstrap.Host, error,
|
||||||
) {
|
) {
|
||||||
return getGarageBootstrapHosts(ctx, d.logger, currBootstrap)
|
return currBootstrap.Hosts, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ func (d *daemon) putGarageBoostrapHost(ctx context.Context) error {
|
|||||||
|
|
||||||
filePath := filepath.Join(
|
filePath := filepath.Join(
|
||||||
garageGlobalBucketBootstrapHostsDirPath,
|
garageGlobalBucketBootstrapHostsDirPath,
|
||||||
host.Name+".json.signed",
|
string(host.Name)+".json.signed",
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err = client.PutObject(
|
_, err = client.PutObject(
|
||||||
@ -108,12 +108,12 @@ func (d *daemon) putGarageBoostrapHost(ctx context.Context) error {
|
|||||||
func getGarageBootstrapHosts(
|
func getGarageBootstrapHosts(
|
||||||
ctx context.Context, logger *mlog.Logger, currBootstrap bootstrap.Bootstrap,
|
ctx context.Context, logger *mlog.Logger, currBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
map[string]bootstrap.Host, error,
|
map[nebula.HostName]bootstrap.Host, error,
|
||||||
) {
|
) {
|
||||||
var (
|
var (
|
||||||
b = currBootstrap
|
b = currBootstrap
|
||||||
client = b.GlobalBucketS3APIClient()
|
client = b.GlobalBucketS3APIClient()
|
||||||
hosts = map[string]bootstrap.Host{}
|
hosts = map[nebula.HostName]bootstrap.Host{}
|
||||||
|
|
||||||
objInfoCh = client.ListObjects(
|
objInfoCh = client.ListObjects(
|
||||||
ctx, garage.GlobalBucket,
|
ctx, garage.GlobalBucket,
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
|
"isle/nebula"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
@ -32,12 +33,13 @@ type CreateNetworkRequest struct {
|
|||||||
// Primary domain name that network services are served under.
|
// Primary domain name that network services are served under.
|
||||||
Domain string
|
Domain string
|
||||||
|
|
||||||
// An IP + subnet mask which represents both the IP of this first host in
|
// An IP subnet, in CIDR form, which will be the overall range of possible
|
||||||
// the network, as well as the overall range of possible IPs in the network.
|
// IPs in the network. The first IP in this network range will become this
|
||||||
IPNet string
|
// first host's IP.
|
||||||
|
IPNet nebula.IPNet
|
||||||
|
|
||||||
// The name of this first host in the network.
|
// The name of this first host in the network.
|
||||||
HostName string
|
HostName nebula.HostName
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNetwork passes through to the Daemon method of the same name.
|
// CreateNetwork passes through to the Daemon method of the same name.
|
||||||
@ -71,7 +73,7 @@ func (r *RPC) GetHosts(
|
|||||||
) (
|
) (
|
||||||
GetHostsResult, error,
|
GetHostsResult, error,
|
||||||
) {
|
) {
|
||||||
hostsMap, err := r.daemon.GetGarageBootstrapHosts(ctx)
|
hostsMap, err := r.daemon.GetBootstrapHosts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GetHostsResult{}, fmt.Errorf("retrieving hosts: %w", err)
|
return GetHostsResult{}, fmt.Errorf("retrieving hosts: %w", err)
|
||||||
}
|
}
|
||||||
|
22
go/nebula/hostname.go
Normal file
22
go/nebula/hostname.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`)
|
||||||
|
|
||||||
|
// HostName is the name of a host in a network. HostNames may only have
|
||||||
|
// lowercase letters, numbers, and hyphens, and must start with a letter.
|
||||||
|
type HostName string
|
||||||
|
|
||||||
|
// UnmarshalText parses and validates a HostName from a text string.
|
||||||
|
func (h *HostName) UnmarshalText(b []byte) error {
|
||||||
|
if !hostNameRegexp.Match(b) {
|
||||||
|
return errors.New("a host's name must start with a letter and only contain letters, numbers, and dashes")
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = HostName(b)
|
||||||
|
return nil
|
||||||
|
}
|
43
go/nebula/ip_net.go
Normal file
43
go/nebula/ip_net.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPNet is the CIDR of a nebula network.
|
||||||
|
type IPNet net.IPNet
|
||||||
|
|
||||||
|
// UnmarshalText parses and validates an IPNet from a text string.
|
||||||
|
func (n *IPNet) UnmarshalText(b []byte) error {
|
||||||
|
str := string(b)
|
||||||
|
_, subnet, err := net.ParseCIDR(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cstr := subnet.String(); cstr != str {
|
||||||
|
return fmt.Errorf("IPNet is not given in its canonical form of %q", cstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !subnet.IP.IsPrivate() {
|
||||||
|
return fmt.Errorf("IPNet is not in a private IP range")
|
||||||
|
}
|
||||||
|
|
||||||
|
*n = IPNet(*subnet)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n IPNet) String() string {
|
||||||
|
return (*net.IPNet)(&n).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n IPNet) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(n.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstAddr returns the first IP address in the subnet.
|
||||||
|
func (n IPNet) FirstAddr() netip.Addr {
|
||||||
|
return netip.MustParseAddr(n.IP.String()).Next()
|
||||||
|
}
|
@ -5,6 +5,7 @@ package nebula
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
@ -44,12 +45,15 @@ type CACredentials struct {
|
|||||||
func NewHostCert(
|
func NewHostCert(
|
||||||
caCreds CACredentials,
|
caCreds CACredentials,
|
||||||
hostPub EncryptingPublicKey,
|
hostPub EncryptingPublicKey,
|
||||||
hostName string,
|
hostName HostName,
|
||||||
ip net.IP,
|
ip netip.Addr,
|
||||||
) (
|
) (
|
||||||
Certificate, error,
|
Certificate, error,
|
||||||
) {
|
) {
|
||||||
caCert := caCreds.Public.Cert
|
var (
|
||||||
|
caCert = caCreds.Public.Cert
|
||||||
|
ipSlice = net.IP(ip.AsSlice())
|
||||||
|
)
|
||||||
|
|
||||||
issuer, err := caCert.inner.Sha256Sum()
|
issuer, err := caCert.inner.Sha256Sum()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,15 +63,15 @@ func NewHostCert(
|
|||||||
expireAt := caCert.inner.Details.NotAfter.Add(-1 * time.Second)
|
expireAt := caCert.inner.Details.NotAfter.Add(-1 * time.Second)
|
||||||
|
|
||||||
subnet := caCert.inner.Details.Subnets[0]
|
subnet := caCert.inner.Details.Subnets[0]
|
||||||
if !subnet.Contains(ip) {
|
if !subnet.Contains(ipSlice) {
|
||||||
return Certificate{}, fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet)
|
return Certificate{}, fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostCert := cert.NebulaCertificate{
|
hostCert := cert.NebulaCertificate{
|
||||||
Details: cert.NebulaCertificateDetails{
|
Details: cert.NebulaCertificateDetails{
|
||||||
Name: hostName,
|
Name: string(hostName),
|
||||||
Ips: []*net.IPNet{{
|
Ips: []*net.IPNet{{
|
||||||
IP: ip,
|
IP: ipSlice,
|
||||||
Mask: subnet.Mask,
|
Mask: subnet.Mask,
|
||||||
}},
|
}},
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
@ -92,7 +96,7 @@ func NewHostCert(
|
|||||||
// 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
|
||||||
// key which will be found in the adminFS.
|
// key which will be found in the adminFS.
|
||||||
func NewHostCredentials(
|
func NewHostCredentials(
|
||||||
caCreds CACredentials, hostName string, ip net.IP,
|
caCreds CACredentials, hostName HostName, ip netip.Addr,
|
||||||
) (
|
) (
|
||||||
pub HostPublicCredentials, priv HostPrivateCredentials, err error,
|
pub HostPublicCredentials, priv HostPrivateCredentials, err error,
|
||||||
) {
|
) {
|
||||||
@ -125,7 +129,7 @@ func NewHostCredentials(
|
|||||||
|
|
||||||
// NewCACredentials generates a CACredentials. The domain should be the network's root domain,
|
// NewCACredentials generates a CACredentials. The domain should be the network's root domain,
|
||||||
// and is included in the signing certificate's Name field.
|
// and is included in the signing certificate's Name field.
|
||||||
func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
func NewCACredentials(domain string, subnet IPNet) (CACredentials, error) {
|
||||||
var (
|
var (
|
||||||
signingPubKey, signingPrivKey = GenerateSigningPair()
|
signingPubKey, signingPrivKey = GenerateSigningPair()
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
@ -135,7 +139,7 @@ func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
|||||||
caCert := cert.NebulaCertificate{
|
caCert := cert.NebulaCertificate{
|
||||||
Details: cert.NebulaCertificateDetails{
|
Details: cert.NebulaCertificateDetails{
|
||||||
Name: fmt.Sprintf("%s isle root cert", domain),
|
Name: fmt.Sprintf("%s isle root cert", domain),
|
||||||
Subnets: []*net.IPNet{subnet},
|
Subnets: []*net.IPNet{(*net.IPNet)(&subnet)},
|
||||||
NotBefore: now,
|
NotBefore: now,
|
||||||
NotAfter: expireAt,
|
NotAfter: expireAt,
|
||||||
PublicKey: signingPubKey,
|
PublicKey: signingPubKey,
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import "net/netip"
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ip net.IP
|
ip netip.Addr
|
||||||
ipNet *net.IPNet
|
ipNet IPNet
|
||||||
caCredsA, caCredsB CACredentials
|
caCredsA, caCredsB CACredentials
|
||||||
hostPubCredsA, hostPubCredsB HostPublicCredentials
|
hostPubCredsA, hostPubCredsB HostPublicCredentials
|
||||||
hostPrivCredsA, hostPrivCredsB HostPrivateCredentials
|
hostPrivCredsA, hostPrivCredsB HostPrivateCredentials
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
ip = netip.MustParseAddr("192.168.0.1")
|
||||||
|
|
||||||
ip, ipNet, err = net.ParseCIDR("192.168.0.1/24")
|
var err error
|
||||||
if err != nil {
|
if err := ipNet.UnmarshalText([]byte("192.168.0.0/24")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# shellcheck source=../../utils/with-1-data-1-empty-node-network.sh
|
# shellcheck source=../../utils/with-1-data-1-empty-node-network.sh
|
||||||
source "$UTILS"/with-1-data-1-empty-node-network.sh
|
source "$UTILS"/with-1-data-1-empty-node-network.sh
|
||||||
|
|
||||||
as_primus
|
# TODO when primus creates secondus' bootstrap it should write the new host to
|
||||||
|
# its own bootstrap, as well as to garage.
|
||||||
|
as_secondus
|
||||||
hosts="$(isle hosts list)"
|
hosts="$(isle hosts list)"
|
||||||
|
|
||||||
[ "$(echo "$hosts" | jq -r '.[0].Name')" = "primus" ]
|
[ "$(echo "$hosts" | jq -r '.[0].Name')" = "primus" ]
|
||||||
|
@ -2,6 +2,8 @@ set -e
|
|||||||
|
|
||||||
base="shared/1-data-1-empty"
|
base="shared/1-data-1-empty"
|
||||||
|
|
||||||
|
ipNet="10.6.9.0/24"
|
||||||
|
|
||||||
primus_base="$base/primus"
|
primus_base="$base/primus"
|
||||||
primus_ip="10.6.9.1"
|
primus_ip="10.6.9.1"
|
||||||
|
|
||||||
@ -60,7 +62,7 @@ EOF
|
|||||||
isle network create \
|
isle network create \
|
||||||
--domain shared.test \
|
--domain shared.test \
|
||||||
--hostname primus \
|
--hostname primus \
|
||||||
--ip-net "$current_ip/24" \
|
--ip-net "$ipNet" \
|
||||||
--name "testing" \
|
--name "testing" \
|
||||||
> admin.json
|
> admin.json
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user