diff --git a/go/bootstrap/bootstrap.go b/go/bootstrap/bootstrap.go index c4eeed8..a41c188 100644 --- a/go/bootstrap/bootstrap.go +++ b/go/bootstrap/bootstrap.go @@ -47,6 +47,10 @@ type Bootstrap struct { } // New initializes and returns a new Bootstrap file for a new host. +// +// TODO in the resulting bootstrap only include this host and hosts which are +// necessary for connecting to nebula/garage. Remember to immediately re-poll +// garage for the full hosts list during network joining. func New( caCreds nebula.CACredentials, adminCreationParams CreationParams, diff --git a/go/cmd/entrypoint/flags.go b/go/cmd/entrypoint/flags.go index 51d337a..fcd8732 100644 --- a/go/cmd/entrypoint/flags.go +++ b/go/cmd/entrypoint/flags.go @@ -3,20 +3,37 @@ package main import ( "encoding" "fmt" + "isle/nebula" + "net/netip" ) -type textUnmarshalerFlag struct { - inner interface { - encoding.TextUnmarshaler +type textUnmarshaler[T any] interface { + encoding.TextUnmarshaler + *T +} + +type textUnmarshalerFlag[T encoding.TextMarshaler, P textUnmarshaler[T]] struct { + V T +} + +func (f *textUnmarshalerFlag[T, P]) Set(v string) error { + return P(&(f.V)).UnmarshalText([]byte(v)) +} + +func (f *textUnmarshalerFlag[T, P]) String() string { + b, err := f.V.MarshalText() + if err != nil { + panic(fmt.Sprintf("calling MarshalText on %#v: %v", f.V, err)) } + return string(b) } -func (f textUnmarshalerFlag) Set(v string) error { - return f.inner.UnmarshalText([]byte(v)) -} +func (f *textUnmarshalerFlag[T, P]) Type() string { return "string" } -func (f textUnmarshalerFlag) String() string { - return fmt.Sprint(f.inner) -} +//////////////////////////////////////////////////////////////////////////////// -func (f textUnmarshalerFlag) Type() string { return "string" } +type ( + hostNameFlag = textUnmarshalerFlag[nebula.HostName, *nebula.HostName] + ipNetFlag = textUnmarshalerFlag[nebula.IPNet, *nebula.IPNet] + ipFlag = textUnmarshalerFlag[netip.Addr, *netip.Addr] +) diff --git a/go/cmd/entrypoint/hosts.go b/go/cmd/entrypoint/hosts.go index 7f9ae1b..340b1ba 100644 --- a/go/cmd/entrypoint/hosts.go +++ b/go/cmd/entrypoint/hosts.go @@ -7,8 +7,6 @@ import ( "isle/bootstrap" "isle/daemon" "isle/jsonutil" - "isle/nebula" - "net/netip" "os" "sort" ) @@ -19,19 +17,17 @@ var subCmdHostsCreate = subCmd{ do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) - hostName nebula.HostName - ip netip.Addr + hostName hostNameFlag + ip ipFlag ) hostNameF := flags.VarPF( - textUnmarshalerFlag{&hostName}, + &hostName, "hostname", "h", "Name of the host to generate bootstrap.json for", ) - flags.VarP( - textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", - ) + flags.VarP(&ip, "ip", "i", "IP of the new host. An available IP will be chosen if none is given.") canCreateHosts := flags.Bool( "can-create-hosts", @@ -53,9 +49,9 @@ var subCmdHostsCreate = subCmd{ &res, "CreateHost", daemon.CreateHostRequest{ - HostName: hostName, + HostName: hostName.V, Opts: daemon.CreateHostOpts{ - IP: ip, + IP: ip.V, CanCreateHosts: *canCreateHosts, }, }, @@ -110,11 +106,11 @@ var subCmdHostsRemove = subCmd{ do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) - hostName nebula.HostName + hostName hostNameFlag ) hostNameF := flags.VarPF( - textUnmarshalerFlag{&hostName}, + &hostName, "hostname", "h", "Name of the host to remove", ) @@ -129,7 +125,7 @@ var subCmdHostsRemove = subCmd{ err := subCmdCtx.daemonRCPClient.Call( subCmdCtx.ctx, nil, "RemoveHost", daemon.RemoveHostRequest{ - HostName: hostName, + HostName: hostName.V, }, ) if err != nil { diff --git a/go/cmd/entrypoint/nebula.go b/go/cmd/entrypoint/nebula.go index c43fb97..f7261b6 100644 --- a/go/cmd/entrypoint/nebula.go +++ b/go/cmd/entrypoint/nebula.go @@ -15,11 +15,11 @@ var subCmdNebulaCreateCert = subCmd{ do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) - hostName nebula.HostName + hostName hostNameFlag ) hostNameF := flags.VarPF( - textUnmarshalerFlag{&hostName}, + &hostName, "hostname", "h", "Name of the host to generate a certificate for", ) @@ -53,7 +53,7 @@ var subCmdNebulaCreateCert = subCmd{ &res, "CreateNebulaCertificate", daemon.CreateNebulaCertificateRequest{ - HostName: hostName, + HostName: hostName.V, HostEncryptingPublicKey: hostPub, }, ) diff --git a/go/cmd/entrypoint/network.go b/go/cmd/entrypoint/network.go index e1f8214..4405f04 100644 --- a/go/cmd/entrypoint/network.go +++ b/go/cmd/entrypoint/network.go @@ -5,6 +5,7 @@ import ( "fmt" "isle/daemon" "isle/jsonutil" + "log" ) var subCmdNetworkCreate = subCmd{ @@ -14,28 +15,30 @@ var subCmdNetworkCreate = subCmd{ var ( ctx = subCmdCtx.ctx flags = subCmdCtx.flagSet(false) - req daemon.CreateNetworkRequest + + ipNet ipNetFlag + hostName hostNameFlag ) - flags.StringVarP( - &req.Name, "name", "n", "", + name := flags.StringP( + "name", "n", "", "Human-readable name to identify the network as.", ) - flags.StringVarP( - &req.Domain, "domain", "d", "", + domain := flags.StringP( + "domain", "d", "", "Domain name that should be used as the root domain in the network.", ) ipNetF := flags.VarPF( - textUnmarshalerFlag{&req.IPNet}, "ip-net", "i", + &ipNet, "ip-net", "i", `An IP subnet, in CIDR form, which will be the overall range of`+ ` possible IPs in the network. The first IP in this network`+ ` range will become this first host's IP.`, ) hostNameF := flags.VarPF( - textUnmarshalerFlag{&req.HostName}, + &hostName, "hostname", "h", "Name of this host, which will be the first host in the network", ) @@ -44,13 +47,23 @@ var subCmdNetworkCreate = subCmd{ return fmt.Errorf("parsing flags: %w", err) } - if req.Name == "" || - req.Domain == "" || + if *name == "" || + *domain == "" || !ipNetF.Changed || !hostNameF.Changed { return errors.New("--name, --domain, --ip-net, and --hostname are required") } + req := daemon.CreateNetworkRequest{ + Name: *name, + Domain: *domain, + IPNet: ipNet.V, + HostName: hostName.V, + } + + log.Printf("req:%+v", req) + return nil + err := subCmdCtx.daemonRCPClient.Call(ctx, nil, "CreateNetwork", req) if err != nil { return fmt.Errorf("creating network: %w", err) diff --git a/go/cmd/entrypoint/sub_cmd.go b/go/cmd/entrypoint/sub_cmd.go index 886dd0c..693bda3 100644 --- a/go/cmd/entrypoint/sub_cmd.go +++ b/go/cmd/entrypoint/sub_cmd.go @@ -48,6 +48,8 @@ func (ctx subCmdCtx) flagSet(withPassthrough bool) *pflag.FlagSet { passthroughStr = " [--] [args...]" } + // TODO don't allow -h/--help flag to be set by sub-commands (or better, + // somehow check that a flag hasn't been set twice). fmt.Fprintf( os.Stderr, "%s[-h|--help] [%s flags...]%s\n\n", ctx.usagePrefix(), ctx.subCmd.name, passthroughStr, diff --git a/go/daemon/daemon.go b/go/daemon/daemon.go index 45a7165..e44f736 100644 --- a/go/daemon/daemon.go +++ b/go/daemon/daemon.go @@ -32,6 +32,8 @@ type CreateHostOpts struct { // CanCreateHosts indicates that the bootstrap produced by CreateHost should // give the new host the ability to create new hosts as well. CanCreateHosts bool + + // TODO add nebula cert tags } // Daemon presents all functionality required for client frontends to interact @@ -85,6 +87,9 @@ type Daemon interface { // existing host, given the public key for that host. This is currently // mostly useful for creating certs for mobile devices. // + // TODO replace this with CreateHostBootstrap, and the + // CreateNebulaCertificate RPC method can just pull cert out of that. + // // Errors: // - ErrHostNotFound CreateNebulaCertificate( @@ -685,6 +690,7 @@ func (d *daemon) CreateHost( ) } } + // TODO if the ip is given, check that it's not already in use. caSigningPrivateKey, err := getNebulaCASigningPrivateKey( ctx, d.secretsStore, diff --git a/go/nebula/hostname.go b/go/nebula/hostname.go index 8b87dc9..cc7ba8f 100644 --- a/go/nebula/hostname.go +++ b/go/nebula/hostname.go @@ -11,6 +11,11 @@ var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`) // lowercase letters, numbers, and hyphens, and must start with a letter. type HostName string +// MarshalText casts the HostName to a byte string and returns it. +func (h HostName) MarshalText() ([]byte, error) { + return []byte(h), nil +} + // UnmarshalText parses and validates a HostName from a text string. func (h *HostName) UnmarshalText(b []byte) error { if !hostNameRegexp.Match(b) {