Compare commits

...

2 Commits

20 changed files with 218 additions and 153 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 <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

View File

@ -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 {

View File

@ -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,

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 string Name nebula.HostName
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" "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)

View 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
}

View 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" }

View File

@ -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,
} }

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.",
) )
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")
} }

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: host.Name, Name: string(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 := hostName zone := string(hostName)
if alloc.Zone != "" { if alloc.Zone != "" {
zone = alloc.Zone zone = alloc.Zone
} }

View File

@ -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
}) })
} }

View File

@ -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,

View File

@ -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
View 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
View 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()
}

View File

@ -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,

View File

@ -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)
} }

View File

@ -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" ]

View File

@ -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