From d2710db8f1d57d8b9d639e98ef6aa7715ee0bcf0 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 14 Jul 2024 13:11:18 +0200 Subject: [PATCH] State CA signing key in secrets store, eliminate admin bundle --- go/admin/admin.go | 37 ---------- go/bootstrap/bootstrap.go | 27 ++++--- go/cmd/entrypoint/admin.go | 28 -------- go/cmd/entrypoint/hosts.go | 23 ++---- go/cmd/entrypoint/nebula.go | 15 +--- go/cmd/entrypoint/network.go | 11 +-- go/daemon/child_dnsmasq.go | 2 +- go/daemon/daemon.go | 70 ++++++++++--------- go/daemon/rpc.go | 15 ++-- go/daemon/secrets.go | 13 ++++ go/nebula/secrets.go | 14 ---- tests/cases/hosts/01-create.sh | 3 +- tests/cases/nebula/01-create-cert.sh | 2 - tests/cases/network/00-create.sh | 8 +-- .../utils/with-1-data-1-empty-node-network.sh | 4 +- 15 files changed, 87 insertions(+), 185 deletions(-) delete mode 100644 go/admin/admin.go delete mode 100644 go/cmd/entrypoint/admin.go delete mode 100644 go/nebula/secrets.go diff --git a/go/admin/admin.go b/go/admin/admin.go deleted file mode 100644 index aa39a1c..0000000 --- a/go/admin/admin.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package admin deals with the parsing and creation of admin.json files. -package admin - -import ( - "encoding/json" - "io" - "isle/nebula" -) - -// CreationParams are general parameters used when creating a new network. These -// are available to all hosts within the network via their bootstrap files. -type CreationParams struct { - ID string - Name string - Domain string -} - -// Admin is used for accessing all information contained within an admin.json. -type Admin struct { - CreationParams CreationParams - - Nebula struct { - CACredentials nebula.CACredentials - } -} - -// FromReader reads an admin.json from the given io.Reader. -func FromReader(r io.Reader) (Admin, error) { - var a Admin - err := json.NewDecoder(r).Decode(&a) - return a, err -} - -// WriteTo writes the Admin as an admin.json to the given io.Writer. -func (a Admin) WriteTo(into io.Writer) error { - return json.NewEncoder(into).Encode(a) -} diff --git a/go/bootstrap/bootstrap.go b/go/bootstrap/bootstrap.go index d51452a..83375f7 100644 --- a/go/bootstrap/bootstrap.go +++ b/go/bootstrap/bootstrap.go @@ -6,7 +6,6 @@ import ( "crypto/sha512" "encoding/json" "fmt" - "isle/admin" "isle/nebula" "net/netip" "path/filepath" @@ -25,11 +24,19 @@ func AppDirPath(appDirPath string) string { return filepath.Join(appDirPath, "share/bootstrap.json") } +// CreationParams are general parameters used when creating a new network. These +// are available to all hosts within the network via their bootstrap files. +type CreationParams struct { + ID string + Name string + Domain string +} + // Bootstrap contains all information which is needed by a host daemon to join a // network on boot. type Bootstrap struct { - AdminCreationParams admin.CreationParams - CAPublicCredentials nebula.CAPublicCredentials + NetworkCreationParams CreationParams + CAPublicCredentials nebula.CAPublicCredentials PrivateCredentials nebula.HostPrivateCredentials HostAssigned `json:"-"` @@ -42,7 +49,7 @@ type Bootstrap struct { // function assigns Hosts an empty map. func New( caCreds nebula.CACredentials, - adminCreationParams admin.CreationParams, + adminCreationParams CreationParams, name nebula.HostName, ip netip.Addr, ) ( @@ -66,12 +73,12 @@ func New( } return Bootstrap{ - AdminCreationParams: adminCreationParams, - CAPublicCredentials: caCreds.Public, - PrivateCredentials: hostPrivCreds, - HostAssigned: assigned, - SignedHostAssigned: signedAssigned, - Hosts: map[nebula.HostName]Host{}, + NetworkCreationParams: adminCreationParams, + CAPublicCredentials: caCreds.Public, + PrivateCredentials: hostPrivCreds, + HostAssigned: assigned, + SignedHostAssigned: signedAssigned, + Hosts: map[nebula.HostName]Host{}, }, nil } diff --git a/go/cmd/entrypoint/admin.go b/go/cmd/entrypoint/admin.go deleted file mode 100644 index a39212c..0000000 --- a/go/cmd/entrypoint/admin.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "fmt" - "isle/admin" - "os" -) - -func readAdmin(path string) (admin.Admin, error) { - - if path == "-" { - - adm, err := admin.FromReader(os.Stdin) - if err != nil { - return admin.Admin{}, fmt.Errorf("parsing admin.json from stdin: %w", err) - } - - return adm, nil - } - - f, err := os.Open(path) - if err != nil { - return admin.Admin{}, fmt.Errorf("opening file: %w", err) - } - defer f.Close() - - return admin.FromReader(f) -} diff --git a/go/cmd/entrypoint/hosts.go b/go/cmd/entrypoint/hosts.go index 40a0b0b..9b93c56 100644 --- a/go/cmd/entrypoint/hosts.go +++ b/go/cmd/entrypoint/hosts.go @@ -33,35 +33,22 @@ var subCmdHostsCreate = subCmd{ textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", ) - adminPath := flags.StringP( - "admin-path", "a", "", - `Path to admin.json file. If the given path is "-" then stdin is used.`, - ) - if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } - if !hostNameF.Changed || - !ipF.Changed || - *adminPath == "" { - return errors.New("--hostname, --ip, and --admin-path are required") - } - - adm, err := readAdmin(*adminPath) - if err != nil { - return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err) + if !hostNameF.Changed || !ipF.Changed { + return errors.New("--hostname and --ip are required") } var res daemon.CreateHostResult - err = subCmdCtx.daemonRCPClient.Call( + err := subCmdCtx.daemonRCPClient.Call( subCmdCtx.ctx, &res, "CreateHost", daemon.CreateHostRequest{ - CASigningPrivateKey: adm.Nebula.CACredentials.SigningPrivateKey, - HostName: hostName, - IP: ip, + HostName: hostName, + IP: ip, }, ) if err != nil { diff --git a/go/cmd/entrypoint/nebula.go b/go/cmd/entrypoint/nebula.go index 94f035e..d112851 100644 --- a/go/cmd/entrypoint/nebula.go +++ b/go/cmd/entrypoint/nebula.go @@ -24,11 +24,6 @@ var subCmdNebulaCreateCert = subCmd{ "Name of the host to generate a certificate for", ) - adminPath := flags.StringP( - "admin-path", "a", "", - `Path to admin.json file. If the given path is "-" then stdin is used.`, - ) - pubKeyPath := flags.StringP( "public-key-path", "p", "", `Path to PEM file containing public key which will be embedded in the cert.`, @@ -38,17 +33,10 @@ var subCmdNebulaCreateCert = subCmd{ return fmt.Errorf("parsing flags: %w", err) } - if !hostNameF.Changed || - *adminPath == "" || - *pubKeyPath == "" { + if !hostNameF.Changed || *pubKeyPath == "" { return errors.New("--hostname, --admin-path, and --pub-key-path are required") } - adm, err := readAdmin(*adminPath) - if err != nil { - return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err) - } - hostPubPEM, err := os.ReadFile(*pubKeyPath) if err != nil { return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err) @@ -65,7 +53,6 @@ var subCmdNebulaCreateCert = subCmd{ &res, "CreateNebulaCertificate", daemon.CreateNebulaCertificateRequest{ - CASigningPrivateKey: adm.Nebula.CACredentials.SigningPrivateKey, HostName: hostName, HostEncryptingPublicKey: hostPub, }, diff --git a/go/cmd/entrypoint/network.go b/go/cmd/entrypoint/network.go index db38758..e1f8214 100644 --- a/go/cmd/entrypoint/network.go +++ b/go/cmd/entrypoint/network.go @@ -3,15 +3,13 @@ package main import ( "errors" "fmt" - "isle/admin" "isle/daemon" "isle/jsonutil" - "os" ) var subCmdNetworkCreate = subCmd{ name: "create", - descr: "Create's a new network, with this host being the first host in that network. The resulting admin.json is output to stdout.", + descr: "Create's a new network, with this host being the first host in that network.", do: func(subCmdCtx subCmdCtx) error { var ( ctx = subCmdCtx.ctx @@ -53,16 +51,11 @@ var subCmdNetworkCreate = subCmd{ return errors.New("--name, --domain, --ip-net, and --hostname are required") } - var adm admin.Admin - err := subCmdCtx.daemonRCPClient.Call(ctx, &adm, "CreateNetwork", req) + err := subCmdCtx.daemonRCPClient.Call(ctx, nil, "CreateNetwork", req) if err != nil { return fmt.Errorf("creating network: %w", err) } - if err := adm.WriteTo(os.Stdout); err != nil { - return fmt.Errorf("writing admin.json to stdout") - } - return nil }, } diff --git a/go/daemon/child_dnsmasq.go b/go/daemon/child_dnsmasq.go index c039dbb..05d055f 100644 --- a/go/daemon/child_dnsmasq.go +++ b/go/daemon/child_dnsmasq.go @@ -33,7 +33,7 @@ func dnsmasqPmuxProcConfig( confData := dnsmasq.ConfData{ Resolvers: daemonConfig.DNS.Resolvers, - Domain: hostBootstrap.AdminCreationParams.Domain, + Domain: hostBootstrap.NetworkCreationParams.Domain, IP: hostBootstrap.ThisHost().IP().String(), Hosts: hostsSlice, } diff --git a/go/daemon/daemon.go b/go/daemon/daemon.go index 10231c7..7d9f850 100644 --- a/go/daemon/daemon.go +++ b/go/daemon/daemon.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "io/fs" - "isle/admin" "isle/bootstrap" "isle/jsonutil" "isle/nebula" @@ -35,15 +34,13 @@ type Daemon interface { // become this first host's IP. // - hostName: The name of this first host in the network. // - // An Admin instance is returned, which is necessary to perform admin - // actions in the future. + // The daemon on which this is called will become the first host in the + // network, and will have full administrative privileges. CreateNetwork( ctx context.Context, name, domain string, ipNet nebula.IPNet, hostName nebula.HostName, - ) ( - admin.Admin, error, - ) + ) error // JoinNetwork joins the Daemon to an existing network using the given // Bootstrap. @@ -66,7 +63,6 @@ type Daemon interface { // address. CreateHost( ctx context.Context, - caSigningPrivateKey nebula.SigningPrivateKey, // TODO load from secrets storage hostName nebula.HostName, ip netip.Addr, // TODO automatically choose IP address ) ( @@ -81,7 +77,6 @@ type Daemon interface { // - ErrHostNotFound CreateNebulaCertificate( ctx context.Context, - caSigningPrivateKey nebula.SigningPrivateKey, // TODO load from secrets storage hostName nebula.HostName, hostPubKey nebula.EncryptingPublicKey, ) ( @@ -519,21 +514,17 @@ func (d *daemon) restartLoop(ctx context.Context, readyCh chan<- struct{}) { func (d *daemon) CreateNetwork( ctx context.Context, name, domain string, ipNet nebula.IPNet, hostName nebula.HostName, -) ( - admin.Admin, error, -) { +) error { nebulaCACreds, err := nebula.NewCACredentials(domain, ipNet) if err != nil { - return admin.Admin{}, fmt.Errorf("creating nebula CA cert: %w", err) + return fmt.Errorf("creating nebula CA cert: %w", err) } var ( - adm = admin.Admin{ - CreationParams: admin.CreationParams{ - ID: randStr(32), - Name: name, - Domain: domain, - }, + creationParams = bootstrap.CreationParams{ + ID: randStr(32), + Name: name, + Domain: domain, } garageRPCSecret = randStr(32) @@ -541,29 +532,34 @@ func (d *daemon) CreateNetwork( err = setGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret) if err != nil { - return admin.Admin{}, fmt.Errorf("storing garage RPC secret: %w", err) + return fmt.Errorf("setting garage RPC secret: %w", err) + } + + err = setNebulaCASigningPrivateKey(ctx, d.secretsStore, nebulaCACreds.SigningPrivateKey) + if err != nil { + return fmt.Errorf("setting nebula CA signing key secret: %w", err) } hostBootstrap, err := bootstrap.New( nebulaCACreds, - adm.CreationParams, + creationParams, hostName, ipNet.FirstAddr(), ) if err != nil { - return adm, fmt.Errorf("initializing bootstrap data: %w", err) + return fmt.Errorf("initializing bootstrap data: %w", err) } d.l.Lock() if d.state != daemonStateNoNetwork { d.l.Unlock() - return adm, ErrAlreadyJoined + return ErrAlreadyJoined } if len(d.daemonConfig.Storage.Allocations) < 3 { d.l.Unlock() - return adm, ErrInvalidConfig.WithData( + return ErrInvalidConfig.WithData( "At least three storage allocations are required.", ) } @@ -573,18 +569,16 @@ func (d *daemon) CreateNetwork( err = d.initialize(hostBootstrap, readyCh) d.l.Unlock() if err != nil { - return adm, fmt.Errorf("initializing daemon: %w", err) + return fmt.Errorf("initializing daemon: %w", err) } select { case <-readyCh: case <-ctx.Done(): - return adm, ctx.Err() + return ctx.Err() } - adm.Nebula.CACredentials = nebulaCACreds - - return adm, nil + return nil } func (d *daemon) JoinNetwork( @@ -674,7 +668,6 @@ func makeCACreds( func (d *daemon) CreateHost( ctx context.Context, - caSigningPrivateKey nebula.SigningPrivateKey, hostName nebula.HostName, ip netip.Addr, ) ( @@ -685,14 +678,17 @@ func (d *daemon) CreateHost( ) ( JoiningBootstrap, error, ) { - var ( - joiningBootstrap JoiningBootstrap - err error + caSigningPrivateKey, err := getNebulaCASigningPrivateKey( + ctx, d.secretsStore, ) + if err != nil { + return JoiningBootstrap{}, fmt.Errorf("getting CA signing key: %w", err) + } + var joiningBootstrap JoiningBootstrap joiningBootstrap.Bootstrap, err = bootstrap.New( makeCACreds(currBootstrap, caSigningPrivateKey), - currBootstrap.AdminCreationParams, + currBootstrap.NetworkCreationParams, hostName, ip, ) @@ -722,7 +718,6 @@ func (d *daemon) CreateHost( func (d *daemon) CreateNebulaCertificate( ctx context.Context, - caSigningPrivateKey nebula.SigningPrivateKey, hostName nebula.HostName, hostPubKey nebula.EncryptingPublicKey, ) ( @@ -738,6 +733,13 @@ func (d *daemon) CreateNebulaCertificate( return nebula.Certificate{}, ErrHostNotFound } + caSigningPrivateKey, err := getNebulaCASigningPrivateKey( + ctx, d.secretsStore, + ) + if err != nil { + return nebula.Certificate{}, fmt.Errorf("getting CA signing key: %w", err) + } + caCreds := makeCACreds(currBootstrap, caSigningPrivateKey) return nebula.NewHostCert(caCreds, hostPubKey, hostName, host.IP()) diff --git a/go/daemon/rpc.go b/go/daemon/rpc.go index 6671d1e..d77f661 100644 --- a/go/daemon/rpc.go +++ b/go/daemon/rpc.go @@ -4,7 +4,6 @@ import ( "cmp" "context" "fmt" - "isle/admin" "isle/bootstrap" "isle/nebula" "net/netip" @@ -47,9 +46,9 @@ type CreateNetworkRequest struct { func (r *RPC) CreateNetwork( ctx context.Context, req CreateNetworkRequest, ) ( - admin.Admin, error, + struct{}, error, ) { - return r.daemon.CreateNetwork( + return struct{}{}, r.daemon.CreateNetwork( ctx, req.Name, req.Domain, req.IPNet, req.HostName, ) } @@ -130,9 +129,8 @@ func (r *RPC) RemoveHost(ctx context.Context, req RemoveHostRequest) (struct{}, // // All fields are required. type CreateHostRequest struct { - CASigningPrivateKey nebula.SigningPrivateKey // TODO load from secrets storage - HostName nebula.HostName - IP netip.Addr + HostName nebula.HostName + IP netip.Addr } // CreateHostResult wraps the results from the CreateHost RPC method. @@ -148,7 +146,7 @@ func (r *RPC) CreateHost( CreateHostResult, error, ) { joiningBootstrap, err := r.daemon.CreateHost( - ctx, req.CASigningPrivateKey, req.HostName, req.IP, + ctx, req.HostName, req.IP, ) if err != nil { return CreateHostResult{}, err @@ -162,7 +160,6 @@ func (r *RPC) CreateHost( // // All fields are required. type CreateNebulaCertificateRequest struct { - CASigningPrivateKey nebula.SigningPrivateKey // TODO load from secrets storage HostName nebula.HostName HostEncryptingPublicKey nebula.EncryptingPublicKey } @@ -181,7 +178,7 @@ func (r *RPC) CreateNebulaCertificate( CreateNebulaCertificateResult, error, ) { cert, err := r.daemon.CreateNebulaCertificate( - ctx, req.CASigningPrivateKey, req.HostName, req.HostEncryptingPublicKey, + ctx, req.HostName, req.HostEncryptingPublicKey, ) if err != nil { return CreateNebulaCertificateResult{}, err diff --git a/go/daemon/secrets.go b/go/daemon/secrets.go index 1b363d3..c68f1ad 100644 --- a/go/daemon/secrets.go +++ b/go/daemon/secrets.go @@ -3,13 +3,26 @@ package daemon import ( "fmt" "isle/garage" + "isle/nebula" "isle/secrets" ) const ( + secretsNSNebula = "nebula" secretsNSGarage = "garage" ) +//////////////////////////////////////////////////////////////////////////////// +// Nebula-related secrets + +var ( + nebulaCASigningPrivateKeySecretID = secrets.NewID(secretsNSNebula, "ca-signing-private-key") +) + +var getNebulaCASigningPrivateKey, setNebulaCASigningPrivateKey = secrets.GetSetFunctions[nebula.SigningPrivateKey]( + nebulaCASigningPrivateKeySecretID, +) + //////////////////////////////////////////////////////////////////////////////// // Garage-related secrets diff --git a/go/nebula/secrets.go b/go/nebula/secrets.go deleted file mode 100644 index 8b2a0ff..0000000 --- a/go/nebula/secrets.go +++ /dev/null @@ -1,14 +0,0 @@ -package nebula - -import ( - "isle/secrets" -) - -var ( - caSigningPrivateKeySecretID = secrets.NewID("nebula", "ca-signing-private-key") -) - -// Get/Set functions for the CA SigningPrivateKey secret. -var GetCASigningPrivateKey, SetCASigningPrivateKey = secrets.GetSetFunctions[SigningPrivateKey]( - caSigningPrivateKeySecretID, -) diff --git a/tests/cases/hosts/01-create.sh b/tests/cases/hosts/01-create.sh index 9d69b23..df08785 100644 --- a/tests/cases/hosts/01-create.sh +++ b/tests/cases/hosts/01-create.sh @@ -4,7 +4,8 @@ source "$UTILS"/with-1-data-1-empty-node-network.sh adminBS="$XDG_STATE_HOME"/isle/bootstrap.json bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh -[ "$(jq -r <"$bs" '.Bootstrap.AdminCreationParams')" = "$(jq -r &1 || true \ ) | grep '\[6\] Host not found' isle nebula create-cert \ - --admin-path admin.json \ --hostname primus \ --public-key-path pubkey \ | grep -- '-----BEGIN NEBULA CERTIFICATE-----' diff --git a/tests/cases/network/00-create.sh b/tests/cases/network/00-create.sh index 8bced65..6844936 100644 --- a/tests/cases/network/00-create.sh +++ b/tests/cases/network/00-create.sh @@ -5,10 +5,8 @@ source "$UTILS"/with-1-data-1-empty-node-network.sh [ "$(cat b/meta/isle/rpc_port)" = "3910" ] [ "$(cat c/meta/isle/rpc_port)" = "3920" ] -[ "$(jq -r admin.json + --name "testing" echo "Creating secondus bootstrap" isle hosts create \ - --admin-path admin.json \ --hostname secondus \ --ip "$secondus_ip" \ > "$secondus_bootstrap"