Implement RPC socket and use it to list hosts

This commit is contained in:
Brian Picciano 2024-06-23 14:37:10 +02:00
parent 47e53dffb7
commit c3609252a5
22 changed files with 264 additions and 73 deletions

View File

@ -69,7 +69,7 @@ in rec {
''; '';
}; };
vendorHash = "sha256-heXFvEea3u3zvdUvVxlI+FTxqEeImoXd1sqdJJTASoc="; vendorHash = "sha256-fBCwaZLusixNnD1QG0XtDe/NpNvonI7wBhqWbRrLFAg=";
subPackages = [ subPackages = [
"./cmd/entrypoint" "./cmd/entrypoint"

View File

@ -78,5 +78,5 @@ type Host struct {
// This assumes that the Host and its data has already been verified against the // This assumes that the Host and its data has already been verified against the
// CA signing key. // CA signing key.
func (h Host) IP() net.IP { func (h Host) IP() net.IP {
return h.PublicCredentials.Cert.Details.Ips[0].IP return h.PublicCredentials.Cert.Unwrap().Details.Ips[0].IP
} }

View File

@ -372,7 +372,7 @@ var subCmdAdminCreateNebulaCert = subCmd{
return fmt.Errorf("creating cert: %w", err) return fmt.Errorf("creating cert: %w", err)
} }
nebulaHostCertPEM, err := nebulaHostCert.MarshalToPEM() nebulaHostCertPEM, err := nebulaHostCert.Unwrap().MarshalToPEM()
if err != nil { if err != nil {
return fmt.Errorf("marshaling cert to PEM: %w", err) return fmt.Errorf("marshaling cert to PEM: %w", err)
} }

View File

@ -115,6 +115,23 @@ func runDaemonPmuxOnce(
} }
}() }()
{
logger := logger.WithNamespace("http")
httpSrv, err := newHTTPServer(
ctx, logger, daemon.NewRPC(daemonInst),
)
if err != nil {
return bootstrap.Bootstrap{}, fmt.Errorf("starting HTTP server: %w", err)
}
defer func() {
// see comment in daemonInst shutdown logic regarding background
// context.
if err := httpSrv.Shutdown(context.Background()); err != nil {
logger.Error(ctx, "Failed to cleanly shutdown http server", err)
}
}()
}
ticker := time.NewTicker(3 * time.Minute) ticker := time.NewTicker(3 * time.Minute)
defer ticker.Stop() defer ticker.Stop()

View File

@ -1,10 +1,18 @@
package main package main
import ( import (
"context"
"errors"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon" "isle/daemon"
"isle/daemon/jsonrpc2"
"isle/garage/garagesrv" "isle/garage/garagesrv"
"net"
"net/http"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
) )
func coalesceDaemonConfigAndBootstrap( func coalesceDaemonConfigAndBootstrap(
@ -50,3 +58,41 @@ func coalesceDaemonConfigAndBootstrap(
return hostBootstrap, nil return hostBootstrap, nil
} }
const daemonHTTPRPCPath = "/rpc/v0.json"
func newHTTPServer(
ctx context.Context, logger *mlog.Logger, rpc *daemon.RPC,
) (
*http.Server, error,
) {
l, err := net.Listen("unix", envSocketPath)
if err != nil {
return nil, fmt.Errorf(
"failed to listen on socket %q: %w", envSocketPath, err,
)
}
ctx = mctx.Annotate(ctx, "httpAddr", l.Addr().String())
logger.Info(ctx, "HTTP server socket created")
rpcHandler := jsonrpc2.Chain(
jsonrpc2.NewMLogMiddleware(logger.WithNamespace("rpc")),
jsonrpc2.ExposeServerSideErrorsMiddleware,
)(
jsonrpc2.NewDispatchHandler(&rpc),
)
httpMux := http.NewServeMux()
httpMux.Handle(daemonHTTPRPCPath, jsonrpc2.NewHTTPHandler(rpcHandler))
srv := &http.Server{Handler: httpMux}
go func() {
if err := srv.Serve(l); !errors.Is(err, http.ErrServerClosed) {
logger.Fatal(ctx, "HTTP server unexpectedly shut down", err)
}
}()
return srv, nil
}

View File

@ -1,15 +1,15 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon"
"isle/jsonutil" "isle/jsonutil"
"os" "os"
"regexp" "regexp"
"sort" "sort"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
) )
var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`) var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`)
@ -26,37 +26,19 @@ func validateHostName(name string) error {
var subCmdHostsList = subCmd{ 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",
checkLock: true,
do: func(subCmdCtx subCmdCtx) error { do: func(subCmdCtx subCmdCtx) error {
flags := subCmdCtx.flagSet(false)
logLevelStr := flags.StringP(
"log-level", "l", "info",
`Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes`,
)
if err := flags.Parse(subCmdCtx.args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
ctx := subCmdCtx.ctx ctx := subCmdCtx.ctx
logLevel := mlog.LevelFromString(*logLevelStr) var resRaw json.RawMessage
if logLevel == nil { err := subCmdCtx.daemonRCPClient.Call(ctx, &resRaw, "GetHosts", nil)
return fmt.Errorf("couldn't parse log level %q", *logLevelStr) if err != nil {
return fmt.Errorf("calling GetHosts: %w", err)
} }
logger := subCmdCtx.logger.WithMaxLevel(logLevel.Int()) var res daemon.GetHostsResult
if err := json.Unmarshal(resRaw, &res); err != nil {
hostBootstrap, err := loadHostBootstrap() return fmt.Errorf("unmarshaling %s into %T: %w", string(resRaw), res, err)
if err != nil {
return fmt.Errorf("loading host bootstrap: %w", err)
}
hostsMap, err := hostBootstrap.GetGarageBootstrapHosts(ctx, logger)
if err != nil {
return fmt.Errorf("retrieving hosts from garage: %w", err)
} }
type host struct { type host struct {
@ -67,8 +49,8 @@ var subCmdHostsList = subCmd{
Storage bootstrap.GarageHost `json:",omitempty"` Storage bootstrap.GarageHost `json:",omitempty"`
} }
hosts := make([]host, 0, len(hostsMap)) hosts := make([]host, 0, len(res.Hosts))
for _, h := range hostsMap { for _, h := range res.Hosts {
host := host{ host := host{
Name: h.Name, Name: h.Name,

View File

@ -8,9 +8,9 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"github.com/adrg/xdg"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx" "dev.mediocregopher.com/mediocre-go-lib.git/mctx"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog" "dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/adrg/xdg"
) )
// The purpose of this binary is to act as the entrypoint of the isle // The purpose of this binary is to act as the entrypoint of the isle

View File

@ -21,7 +21,7 @@ var subCmdNebulaShow = subCmd{
return fmt.Errorf("loading host bootstrap: %w", err) return fmt.Errorf("loading host bootstrap: %w", err)
} }
caCert := hostBootstrap.CAPublicCredentials.Cert caCert := hostBootstrap.CAPublicCredentials.Cert.Unwrap()
caCertPEM, err := caCert.MarshalToPEM() caCertPEM, err := caCert.MarshalToPEM()
if err != nil { if err != nil {
return fmt.Errorf("marshaling CA cert to PEM: %w", err) return fmt.Errorf("marshaling CA cert to PEM: %w", err)

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"isle/daemon/jsonrpc2"
"os" "os"
"strings" "strings"
@ -18,6 +19,7 @@ type subCmdCtx struct {
ctx context.Context ctx context.Context
logger *mlog.Logger logger *mlog.Logger
daemonRCPClient jsonrpc2.Client
} }
type subCmd struct { type subCmd struct {
@ -107,12 +109,17 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
} }
} }
daemonRCPClient := jsonrpc2.NewUnixHTTPClient(
envSocketPath, daemonHTTPRPCPath,
)
err := subCmd.do(subCmdCtx{ err := subCmd.do(subCmdCtx{
subCmd: subCmd, subCmd: subCmd,
args: args, args: args,
subCmdNames: append(ctx.subCmdNames, subCmdName), subCmdNames: append(ctx.subCmdNames, subCmdName),
ctx: ctx.ctx, ctx: ctx.ctx,
logger: ctx.logger, logger: ctx.logger,
daemonRCPClient: daemonRCPClient,
}) })
if err != nil { if err != nil {

View File

@ -59,14 +59,14 @@ func nebulaPmuxProcConfig(
staticHostMap[ip] = []string{host.Nebula.PublicAddr} staticHostMap[ip] = []string{host.Nebula.PublicAddr}
} }
caCertPEM, err := hostBootstrap.CAPublicCredentials.Cert.MarshalToPEM() caCertPEM, err := hostBootstrap.CAPublicCredentials.Cert.Unwrap().MarshalToPEM()
if err != nil { if err != nil {
return pmuxlib.ProcessConfig{}, fmt.Errorf( return pmuxlib.ProcessConfig{}, fmt.Errorf(
"marshaling CA cert to PEM: :%w", err, "marshaling CA cert to PEM: :%w", err,
) )
} }
hostCertPEM, err := hostBootstrap.PublicCredentials.Cert.MarshalToPEM() hostCertPEM, err := hostBootstrap.PublicCredentials.Cert.Unwrap().MarshalToPEM()
if err != nil { if err != nil {
return pmuxlib.ProcessConfig{}, fmt.Errorf( return pmuxlib.ProcessConfig{}, fmt.Errorf(
"marshaling host cert to PEM: :%w", err, "marshaling host cert to PEM: :%w", err,

View File

@ -6,6 +6,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"github.com/tv42/httpunix"
) )
type httpClient struct { type httpClient struct {
@ -15,12 +18,33 @@ type httpClient struct {
// NewHTTPClient returns a Client which will use HTTP POST requests against the // NewHTTPClient returns a Client which will use HTTP POST requests against the
// given URL as a transport for JSONRPC2 method calls. // given URL as a transport for JSONRPC2 method calls.
func NewHTTPClient(url string) Client { func NewHTTPClient(urlStr string) Client {
return &httpClient{ return &httpClient{
c: &http.Client{ c: &http.Client{
Transport: http.DefaultTransport.(*http.Transport).Clone(), Transport: http.DefaultTransport.(*http.Transport).Clone(),
}, },
url: url, url: urlStr,
}
}
// NewUnixHTTPClient returns a Client which will use HTTP POST requests against
// the given unix socket as a transport for JSONRPC2 method calls. The given
// path will be used as the path portion of the HTTP request.
func NewUnixHTTPClient(unixSocketPath, reqPath string) Client {
const host = "uds"
u := &url.URL{
Scheme: httpunix.Scheme,
Host: host,
Path: reqPath,
}
transport := new(httpunix.Transport)
transport.RegisterLocation(host, unixSocketPath)
return &httpClient{
c: &http.Client{Transport: transport},
url: u.String(),
} }
} }

View File

@ -4,7 +4,9 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"net"
"net/http/httptest" "net/http/httptest"
"path/filepath"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -156,3 +158,20 @@ func TestHTTP(t *testing.T) {
t.Cleanup(server.Close) t.Cleanup(server.Close)
testClient(t, NewHTTPClient(server.URL)) testClient(t, NewHTTPClient(server.URL))
} }
func TestUnixHTTP(t *testing.T) {
var (
unixSocketPath = filepath.Join(t.TempDir(), "test.sock")
server = httptest.NewUnstartedServer(NewHTTPHandler(testHandler))
)
var err error
if server.Listener, err = net.Listen("unix", unixSocketPath); err != nil {
t.Fatal(err)
}
server.Start()
t.Cleanup(server.Close)
testClient(t, NewUnixHTTPClient(unixSocketPath, "/"))
}

View File

@ -18,6 +18,11 @@ func NewHTTPHandler(h Handler) http.Handler {
} }
func (h httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { func (h httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
var ( var (
ctx = r.Context() ctx = r.Context()
enc = json.NewEncoder(rw) enc = json.NewEncoder(rw)

46
go/daemon/rpc.go Normal file
View File

@ -0,0 +1,46 @@
package daemon
import (
"cmp"
"context"
"fmt"
"isle/bootstrap"
"slices"
"golang.org/x/exp/maps"
)
// GetHostsResult wraps the results from the GetHosts RPC method.
type GetHostsResult struct {
Hosts []bootstrap.Host
}
// RPC exposes all RPC methods which are available to be called over the RPC
// interface.
type RPC struct {
daemon Daemon
}
// NewRPC initializes and returns an RPC instance.
func NewRPC(daemon Daemon) *RPC {
return &RPC{daemon}
}
// GetHosts returns all hosts known to the cluster, sorted by their name.
func (r *RPC) GetHosts(
ctx context.Context, req struct{},
) (
GetHostsResult, error,
) {
hostsMap, err := r.daemon.GetGarageBootstrapHosts(ctx)
if err != nil {
return GetHostsResult{}, fmt.Errorf("retrieving hosts: %w", err)
}
hosts := maps.Values(hostsMap)
slices.SortFunc(hosts, func(a, b bootstrap.Host) int {
return cmp.Compare(a.Name, b.Name)
})
return GetHostsResult{hosts}, nil
}

View File

@ -12,13 +12,14 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/slackhq/nebula v1.6.1 github.com/slackhq/nebula v1.6.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/uuid v1.1.1 // indirect github.com/google/uuid v1.1.1 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect

View File

@ -13,8 +13,8 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -77,10 +77,14 @@ github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03O
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0= golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -92,8 +96,6 @@ golang.org/x/sys v0.0.0-20220406155245-289d7a0edf71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

29
go/nebula/certificate.go Normal file
View File

@ -0,0 +1,29 @@
package nebula
import "github.com/slackhq/nebula/cert"
// Certificate wraps a NebulaCertificate to provide convenient (and consistent)
// text (un)marshaling methods.
type Certificate struct {
inner cert.NebulaCertificate
}
// Unwrap returns the wrapped NebulaCertificate type.
func (c Certificate) Unwrap() *cert.NebulaCertificate {
return &c.inner
}
// MarshalText implements the encoding.TextMarshaler interface.
func (c Certificate) MarshalText() ([]byte, error) {
return c.inner.MarshalToPEM()
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (c *Certificate) UnmarshalText(b []byte) error {
nebCrt, _, err := cert.UnmarshalNebulaCertificateFromPEM(b)
if err != nil {
return err
}
c.inner = *nebCrt
return nil
}

View File

@ -13,7 +13,7 @@ import (
// HostPublicCredentials contains certificate and signing public keys which are // HostPublicCredentials contains certificate and signing public keys which are
// able to be broadcast publicly. // able to be broadcast publicly.
type HostPublicCredentials struct { type HostPublicCredentials struct {
Cert cert.NebulaCertificate Cert Certificate
SigningKey SigningPublicKey SigningKey SigningPublicKey
} }
@ -28,7 +28,7 @@ type HostPrivateCredentials struct {
// able to be broadcast publicly. The signing public key is the same one which // able to be broadcast publicly. The signing public key is the same one which
// is embedded into the certificate. // is embedded into the certificate.
type CAPublicCredentials struct { type CAPublicCredentials struct {
Cert cert.NebulaCertificate Cert Certificate
SigningKey SigningPublicKey SigningKey SigningPublicKey
} }
@ -47,20 +47,20 @@ func NewHostCert(
hostName string, hostName string,
ip net.IP, ip net.IP,
) ( ) (
cert.NebulaCertificate, error, Certificate, error,
) { ) {
caCert := caCreds.Public.Cert caCert := caCreds.Public.Cert
issuer, err := caCert.Sha256Sum() issuer, err := caCert.inner.Sha256Sum()
if err != nil { if err != nil {
return cert.NebulaCertificate{}, fmt.Errorf("getting ca.crt issuer: %w", err) return Certificate{}, fmt.Errorf("getting ca.crt issuer: %w", err)
} }
expireAt := caCert.Details.NotAfter.Add(-1 * time.Second) expireAt := caCert.inner.Details.NotAfter.Add(-1 * time.Second)
subnet := caCert.Details.Subnets[0] subnet := caCert.inner.Details.Subnets[0]
if !subnet.Contains(ip) { if !subnet.Contains(ip) {
return cert.NebulaCertificate{}, 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{
@ -78,15 +78,15 @@ func NewHostCert(
}, },
} }
if err := hostCert.CheckRootConstrains(&caCert); err != nil { if err := hostCert.CheckRootConstrains(&caCert.inner); err != nil {
return cert.NebulaCertificate{}, fmt.Errorf("validating certificate constraints: %w", err) return Certificate{}, fmt.Errorf("validating certificate constraints: %w", err)
} }
if err := signCert(&hostCert, caCreds.SigningPrivateKey); err != nil { if err := signCert(&hostCert, caCreds.SigningPrivateKey); err != nil {
return cert.NebulaCertificate{}, fmt.Errorf("signing host cert with ca.key: %w", err) return Certificate{}, fmt.Errorf("signing host cert with ca.key: %w", err)
} }
return hostCert, nil return Certificate{hostCert}, nil
} }
// 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
@ -149,7 +149,7 @@ func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
return CACredentials{ return CACredentials{
Public: CAPublicCredentials{ Public: CAPublicCredentials{
Cert: caCert, Cert: Certificate{caCert},
SigningKey: signingPubKey, SigningKey: signingPubKey,
}, },
SigningPrivateKey: signingPrivKey, SigningPrivateKey: signingPrivKey,

View File

@ -16,8 +16,8 @@ func TestSigningKeysJSON(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !strings.HasPrefix(string(pubJSON), `"-----BEGIN `) { if !strings.HasPrefix(string(pubJSON), `"S0`) {
t.Fatalf("pub key didn't marshal to PEM: %q", pubJSON) t.Fatalf("pub key didn't marshal with prefix: %q", pubJSON)
} }
var pub2 SigningPublicKey var pub2 SigningPublicKey
@ -36,8 +36,8 @@ func TestSigningKeysJSON(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !strings.HasPrefix(string(privJSON), `"-----BEGIN `) { if !strings.HasPrefix(string(privJSON), `"s0`) {
t.Fatalf("priv key didn't marshal to PEM: %q", privJSON) t.Fatalf("priv key didn't marshal with prefix: %q", privJSON)
} }
var priv2 SigningPrivateKey var priv2 SigningPrivateKey

View File

@ -0,0 +1,13 @@
# shellcheck source=../../utils/with-1-data-1-empty-node-cluster.sh
source "$UTILS"/with-1-data-1-empty-node-cluster.sh
as_primus
hosts="$(isle hosts list)"
[ "$(echo "$hosts" | jq -r '.[0].Name')" = "primus" ]
[ "$(echo "$hosts" | jq -r '.[0].VPN.IP')" = "10.6.9.1" ]
[ "$(echo "$hosts" | jq -r '.[0].Storage.Instances|length')" = "3" ]
[ "$(echo "$hosts" | jq -r '.[1].Name')" = "secondus" ]
[ "$(echo "$hosts" | jq -r '.[1].VPN.IP')" = "10.6.9.2" ]
[ "$(echo "$hosts" | jq -r '.[1].Storage.Instances|length')" = "0" ]

View File

@ -12,5 +12,6 @@ cat <<EOF
export TMPDIR="$TMPDIR" export TMPDIR="$TMPDIR"
export XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" export XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR"
export XDG_STATE_HOME="$XDG_STATE_HOME" export XDG_STATE_HOME="$XDG_STATE_HOME"
export ISLE_SOCKET_PATH="$ROOT_TMPDIR/$base-daemon.sock"
cd "$TMPDIR" cd "$TMPDIR"
EOF EOF

View File

@ -62,10 +62,10 @@ EOF
pid="$!" pid="$!"
echo "Waiting for primus daemon (process $pid) to initialize" echo "Waiting for primus daemon (process $pid) to initialize"
while ! isle hosts list >/dev/null; do sleep 1; done
$SHELL "$UTILS/register-cleanup.sh" "$pid" "1-data-1-empty-node-cluster/primus" $SHELL "$UTILS/register-cleanup.sh" "$pid" "1-data-1-empty-node-cluster/primus"
while ! isle hosts list >/dev/null; do sleep 1; done
echo "Creating secondus bootstrap" echo "Creating secondus bootstrap"
isle admin create-bootstrap \ isle admin create-bootstrap \
--admin-path admin.json \ --admin-path admin.json \
@ -86,8 +86,7 @@ EOF
pid="$!" pid="$!"
echo "Waiting for secondus daemon (process $!) to initialize" echo "Waiting for secondus daemon (process $!) to initialize"
while ! isle hosts list >/dev/null; do sleep 1; done
$SHELL "$UTILS/register-cleanup.sh" "$pid" "1-data-1-empty-node-cluster/secondus" $SHELL "$UTILS/register-cleanup.sh" "$pid" "1-data-1-empty-node-cluster/secondus"
while ! isle hosts list >/dev/null; do sleep 1; done
) )
fi fi