Compare commits

..

No commits in common. "736b23429c02d5fd660994883d13b68658d9915a" and "279c79a9f18cb4affe783185a76e1e21fbef5919" have entirely different histories.

20 changed files with 153 additions and 218 deletions

View File

@ -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 <subnet> \ --ip-net <ip/subnet-prefix> \
--domain <domain> \ --domain <domain> \
--hostname <hostname> \ --hostname <hostname> \
| gpg -e -r <my gpg email> \ | gpg -e -r <my gpg email> \
@ -112,6 +112,11 @@ 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

View File

@ -10,7 +10,7 @@ import (
"isle/admin" "isle/admin"
"isle/garage" "isle/garage"
"isle/nebula" "isle/nebula"
"net/netip" "net"
"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[nebula.HostName]Host Hosts map[string]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 nebula.HostName, name string,
ip netip.Addr, ip net.IP,
) ( ) (
Bootstrap, error, Bootstrap, error,
) { ) {
@ -89,7 +89,7 @@ func New(
PrivateCredentials: hostPrivCreds, PrivateCredentials: hostPrivCreds,
HostAssigned: assigned, HostAssigned: assigned,
SignedHostAssigned: signedAssigned, SignedHostAssigned: signedAssigned,
Hosts: map[nebula.HostName]Host{}, Hosts: map[string]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[nebula.HostName]Host) ([]byte, error) { func HostsHash(hostsMap map[string]Host) ([]byte, error) {
hosts := make([]Host, 0, len(hostsMap)) hosts := make([]Host, 0, len(hostsMap))
for _, host := range hostsMap { for _, host := range hostsMap {

View File

@ -5,7 +5,6 @@ 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"
@ -47,12 +46,12 @@ func (b Bootstrap) GetGarageBootstrapHosts(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
) ( ) (
map[nebula.HostName]Host, error, map[string]Host, error,
) { ) {
client := b.GlobalBucketS3APIClient() client := b.GlobalBucketS3APIClient()
hosts := map[nebula.HostName]Host{} hosts := map[string]Host{}
objInfoCh := client.ListObjects( objInfoCh := client.ListObjects(
ctx, garage.GlobalBucket, ctx, garage.GlobalBucket,

View File

@ -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 nebula.HostName Name string
PublicCredentials nebula.HostPublicCredentials PublicCredentials nebula.HostPublicCredentials
} }

View File

@ -8,7 +8,7 @@ import (
"isle/admin" "isle/admin"
"isle/bootstrap" "isle/bootstrap"
"isle/nebula" "isle/nebula"
"net/netip" "net"
"os" "os"
) )
@ -46,20 +46,17 @@ 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
)
hostNameF := flags.VarPF( flags := subCmdCtx.flagSet(false)
textUnmarshalerFlag{&hostName},
"hostname", "h", hostName := flags.StringP(
"hostname", "h", "",
"Name of the host to generate bootstrap.json for", "Name of the host to generate bootstrap.json for",
) )
ipF := flags.VarPF( ipStr := flags.StringP(
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", "ip", "i", "",
"IP of the new host",
) )
adminPath := flags.StringP( adminPath := flags.StringP(
@ -71,12 +68,20 @@ var subCmdAdminCreateBootstrap = subCmd{
return fmt.Errorf("parsing flags: %w", err) return fmt.Errorf("parsing flags: %w", err)
} }
if !hostNameF.Changed || if *hostName == "" || *ipStr == "" || *adminPath == "" {
!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)
@ -92,21 +97,19 @@ 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)
} }
hostsRes, err := subCmdCtx.getHosts() hostBootstrap, err := loadHostBootstrap()
if err != nil { if err != nil {
return fmt.Errorf("getting hosts: %w", err) return fmt.Errorf("loading host bootstrap: %w", err)
} }
for _, host := range hostsRes.Hosts { newHostBootstrap.Hosts = hostBootstrap.Hosts
newHostBootstrap.Hosts[host.Name] = host
}
return newHostBootstrap.WriteTo(os.Stdout) return newHostBootstrap.WriteTo(os.Stdout)
}, },
@ -117,20 +120,17 @@ 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
ip netip.Addr hostName := flags.StringP(
"hostname", "h", "",
"Name of the host to generate bootstrap.json for",
) )
hostNameF := flags.VarPF( ipStr := flags.StringP(
textUnmarshalerFlag{&hostName}, "ip", "i", "",
"hostname", "h", "IP of the new host",
"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,13 +147,20 @@ var subCmdAdminCreateNebulaCert = subCmd{
return fmt.Errorf("parsing flags: %w", err) return fmt.Errorf("parsing flags: %w", err)
} }
if !hostNameF.Changed || if *hostName == "" || *ipStr == "" || *adminPath == "" || *pubKeyPath == "" {
!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)
@ -170,7 +177,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)

View File

@ -1,15 +0,0 @@
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
}

View File

@ -1,22 +0,0 @@
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" }

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon"
"isle/jsonutil" "isle/jsonutil"
"os" "os"
"regexp" "regexp"
@ -25,7 +26,11 @@ 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)
} }
@ -38,11 +43,11 @@ var subCmdHostsList = subCmd{
Storage bootstrap.GarageHost `json:",omitempty"` Storage bootstrap.GarageHost `json:",omitempty"`
} }
hosts := make([]host, 0, len(hostsRes.Hosts)) hosts := make([]host, 0, len(res.Hosts))
for _, h := range hostsRes.Hosts { for _, h := range res.Hosts {
host := host{ host := host{
Name: string(h.Name), Name: h.Name,
Storage: h.Garage, Storage: h.Garage,
} }

View File

@ -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.",
) )
ipNetF := flags.VarPF( flags.StringVarP(
textUnmarshalerFlag{&req.IPNet}, "ip-net", "i", &req.IPNet, "ip-net", "i", "",
`An IP subnet, in CIDR form, which will be the overall range of`+ `IP+prefix (e.g. "10.10.0.1/16") which denotes the IP of this`+
` possible IPs in the network. The first IP in this network`+ ` host, which will be the first host in the network, and the`+
` range will become this first host's IP.`, ` range of IPs which other hosts in the network can be`+
` assigned`,
) )
hostNameF := flags.VarPF( flags.StringVarP(
textUnmarshalerFlag{&req.HostName}, &req.HostName, "hostname", "h", "",
"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 == "" ||
!ipNetF.Changed || req.IPNet == "" ||
!hostNameF.Changed { req.HostName == "" {
return errors.New("--name, --domain, --ip-net, and --hostname are required") return errors.New("--name, --domain, --ip-net, and --hostname are required")
} }

View File

@ -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: string(host.Name), Name: host.Name,
IP: host.IP().String(), IP: host.IP().String(),
}) })
} }

View File

@ -197,7 +197,7 @@ func GarageApplyLayout(
id := bootstrapGarageHostForAlloc(thisHost, alloc).ID id := bootstrapGarageHostForAlloc(thisHost, alloc).ID
zone := string(hostName) zone := hostName
if alloc.Zone != "" { if alloc.Zone != "" {
zone = alloc.Zone zone = alloc.Zone
} }

View File

@ -11,15 +11,29 @@ 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 {
@ -27,17 +41,15 @@ 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.
// - ipNet: An IP subnet, in CIDR form, which will be the overall range of // - ipNetStr: An IP + subnet mask which represents both the IP of this
// possible IPs in the network. The first IP in this network range will // first host in the network, as well as the overall range of possible IPs
// become this first host's IP. // in the network.
// - 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 string, ctx context.Context, name, domain, ipNetStr, hostName string,
ipNet nebula.IPNet,
hostName nebula.HostName,
) ( ) (
admin.Admin, error, admin.Admin, error,
) )
@ -49,11 +61,12 @@ type Daemon interface {
// - ErrAlreadyJoined // - ErrAlreadyJoined
JoinNetwork(context.Context, bootstrap.Bootstrap) error JoinNetwork(context.Context, bootstrap.Bootstrap) error
// GetBootstrapHosts returns the hosts stored in the bootstrap. // GetGarageBootstrapHosts loads (and verifies) the <hostname>.json.signed
GetBootstrapHosts( // file for all hosts stored in garage.
GetGarageBootstrapHosts(
ctx context.Context, ctx context.Context,
) ( ) (
map[nebula.HostName]bootstrap.Host, error, map[string]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,
@ -136,7 +149,10 @@ 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, daemonConfig Config, envBinDirPath string, opts *Opts, logger *mlog.Logger,
daemonConfig Config,
envBinDirPath string,
opts *Opts,
) ( ) (
Daemon, error, Daemon, error,
) { ) {
@ -463,11 +479,24 @@ 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 string, ipNet nebula.IPNet, hostName nebula.HostName, name, domain, ipNetStr, hostName string,
) ( ) (
admin.Admin, error, admin.Admin, error,
) { ) {
nebulaCACreds, err := nebula.NewCACredentials(domain, ipNet) ip, subnet, err := net.ParseCIDR(ipNetStr)
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)
} }
@ -490,7 +519,7 @@ func (d *daemon) CreateNetwork(
adm.CreationParams, adm.CreationParams,
garageBootstrap, garageBootstrap,
hostName, hostName,
ipNet.FirstAddr(), ip,
) )
if err != nil { if err != nil {
return adm, fmt.Errorf("initializing bootstrap data: %w", err) return adm, fmt.Errorf("initializing bootstrap data: %w", err)
@ -565,17 +594,17 @@ func (d *daemon) JoinNetwork(
} }
} }
func (d *daemon) GetBootstrapHosts( func (d *daemon) GetGarageBootstrapHosts(
ctx context.Context, ctx context.Context,
) ( ) (
map[nebula.HostName]bootstrap.Host, error, map[string]bootstrap.Host, error,
) { ) {
return withCurrBootstrap(d, func( return withCurrBootstrap(d, func(
currBootstrap bootstrap.Bootstrap, currBootstrap bootstrap.Bootstrap,
) ( ) (
map[nebula.HostName]bootstrap.Host, error, map[string]bootstrap.Host, error,
) { ) {
return currBootstrap.Hosts, nil return getGarageBootstrapHosts(ctx, d.logger, currBootstrap)
}) })
} }

View File

@ -86,7 +86,7 @@ func (d *daemon) putGarageBoostrapHost(ctx context.Context) error {
filePath := filepath.Join( filePath := filepath.Join(
garageGlobalBucketBootstrapHostsDirPath, garageGlobalBucketBootstrapHostsDirPath,
string(host.Name)+".json.signed", 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[nebula.HostName]bootstrap.Host, error, map[string]bootstrap.Host, error,
) { ) {
var ( var (
b = currBootstrap b = currBootstrap
client = b.GlobalBucketS3APIClient() client = b.GlobalBucketS3APIClient()
hosts = map[nebula.HostName]bootstrap.Host{} hosts = map[string]bootstrap.Host{}
objInfoCh = client.ListObjects( objInfoCh = client.ListObjects(
ctx, garage.GlobalBucket, ctx, garage.GlobalBucket,

View File

@ -6,7 +6,6 @@ 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"
@ -33,13 +32,12 @@ 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, in CIDR form, which will be the overall range of possible // An IP + subnet mask which represents both the IP of this first host in
// IPs in the network. The first IP in this network range will become this // the network, as well as the overall range of possible IPs in the network.
// first host's IP. IPNet string
IPNet nebula.IPNet
// The name of this first host in the network. // The name of this first host in the network.
HostName nebula.HostName HostName string
} }
// CreateNetwork passes through to the Daemon method of the same name. // CreateNetwork passes through to the Daemon method of the same name.
@ -73,7 +71,7 @@ func (r *RPC) GetHosts(
) ( ) (
GetHostsResult, error, GetHostsResult, error,
) { ) {
hostsMap, err := r.daemon.GetBootstrapHosts(ctx) hostsMap, err := r.daemon.GetGarageBootstrapHosts(ctx)
if err != nil { if err != nil {
return GetHostsResult{}, fmt.Errorf("retrieving hosts: %w", err) return GetHostsResult{}, fmt.Errorf("retrieving hosts: %w", err)
} }

View File

@ -1,22 +0,0 @@
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
}

View File

@ -1,43 +0,0 @@
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()
}

View File

@ -5,7 +5,6 @@ package nebula
import ( import (
"fmt" "fmt"
"net" "net"
"net/netip"
"time" "time"
"github.com/slackhq/nebula/cert" "github.com/slackhq/nebula/cert"
@ -45,15 +44,12 @@ type CACredentials struct {
func NewHostCert( func NewHostCert(
caCreds CACredentials, caCreds CACredentials,
hostPub EncryptingPublicKey, hostPub EncryptingPublicKey,
hostName HostName, hostName string,
ip netip.Addr, ip net.IP,
) ( ) (
Certificate, error, Certificate, error,
) { ) {
var ( caCert := caCreds.Public.Cert
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 {
@ -63,15 +59,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(ipSlice) { if !subnet.Contains(ip) {
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: string(hostName), Name: hostName,
Ips: []*net.IPNet{{ Ips: []*net.IPNet{{
IP: ipSlice, IP: ip,
Mask: subnet.Mask, Mask: subnet.Mask,
}}, }},
NotBefore: time.Now(), NotBefore: time.Now(),
@ -96,7 +92,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 HostName, ip netip.Addr, caCreds CACredentials, hostName string, ip net.IP,
) ( ) (
pub HostPublicCredentials, priv HostPrivateCredentials, err error, pub HostPublicCredentials, priv HostPrivateCredentials, err error,
) { ) {
@ -129,7 +125,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 IPNet) (CACredentials, error) { func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
var ( var (
signingPubKey, signingPrivKey = GenerateSigningPair() signingPubKey, signingPrivKey = GenerateSigningPair()
now = time.Now() now = time.Now()
@ -139,7 +135,7 @@ func NewCACredentials(domain string, subnet 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{(*net.IPNet)(&subnet)}, Subnets: []*net.IPNet{subnet},
NotBefore: now, NotBefore: now,
NotAfter: expireAt, NotAfter: expireAt,
PublicKey: signingPubKey, PublicKey: signingPubKey,

View File

@ -1,20 +1,22 @@
package nebula package nebula
import "net/netip" import (
"net"
)
var ( var (
ip netip.Addr ip net.IP
ipNet IPNet ipNet *net.IPNet
caCredsA, caCredsB CACredentials caCredsA, caCredsB CACredentials
hostPubCredsA, hostPubCredsB HostPublicCredentials hostPubCredsA, hostPubCredsB HostPublicCredentials
hostPrivCredsA, hostPrivCredsB HostPrivateCredentials hostPrivCredsA, hostPrivCredsB HostPrivateCredentials
) )
func init() { func init() {
ip = netip.MustParseAddr("192.168.0.1")
var err error var err error
if err := ipNet.UnmarshalText([]byte("192.168.0.0/24")); err != nil {
ip, ipNet, err = net.ParseCIDR("192.168.0.1/24")
if err != nil {
panic(err) panic(err)
} }

View File

@ -1,9 +1,7 @@
# 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
# TODO when primus creates secondus' bootstrap it should write the new host to as_primus
# 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" ]

View File

@ -2,8 +2,6 @@ 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"
@ -62,7 +60,7 @@ EOF
isle network create \ isle network create \
--domain shared.test \ --domain shared.test \
--hostname primus \ --hostname primus \
--ip-net "$ipNet" \ --ip-net "$current_ip/24" \
--name "testing" \ --name "testing" \
> admin.json > admin.json