State CA signing key in secrets store, eliminate admin bundle

This commit is contained in:
Brian Picciano 2024-07-14 13:11:18 +02:00
parent 9d5c8ea4db
commit d2710db8f1
15 changed files with 87 additions and 185 deletions

View File

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

View File

@ -6,7 +6,6 @@ import (
"crypto/sha512" "crypto/sha512"
"encoding/json" "encoding/json"
"fmt" "fmt"
"isle/admin"
"isle/nebula" "isle/nebula"
"net/netip" "net/netip"
"path/filepath" "path/filepath"
@ -25,11 +24,19 @@ func AppDirPath(appDirPath string) string {
return filepath.Join(appDirPath, "share/bootstrap.json") 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 // Bootstrap contains all information which is needed by a host daemon to join a
// network on boot. // network on boot.
type Bootstrap struct { type Bootstrap struct {
AdminCreationParams admin.CreationParams NetworkCreationParams CreationParams
CAPublicCredentials nebula.CAPublicCredentials CAPublicCredentials nebula.CAPublicCredentials
PrivateCredentials nebula.HostPrivateCredentials PrivateCredentials nebula.HostPrivateCredentials
HostAssigned `json:"-"` HostAssigned `json:"-"`
@ -42,7 +49,7 @@ type Bootstrap struct {
// function assigns Hosts an empty map. // function assigns Hosts an empty map.
func New( func New(
caCreds nebula.CACredentials, caCreds nebula.CACredentials,
adminCreationParams admin.CreationParams, adminCreationParams CreationParams,
name nebula.HostName, name nebula.HostName,
ip netip.Addr, ip netip.Addr,
) ( ) (
@ -66,12 +73,12 @@ func New(
} }
return Bootstrap{ return Bootstrap{
AdminCreationParams: adminCreationParams, NetworkCreationParams: adminCreationParams,
CAPublicCredentials: caCreds.Public, CAPublicCredentials: caCreds.Public,
PrivateCredentials: hostPrivCreds, PrivateCredentials: hostPrivCreds,
HostAssigned: assigned, HostAssigned: assigned,
SignedHostAssigned: signedAssigned, SignedHostAssigned: signedAssigned,
Hosts: map[nebula.HostName]Host{}, Hosts: map[nebula.HostName]Host{},
}, nil }, nil
} }

View File

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

View File

@ -33,35 +33,22 @@ var subCmdHostsCreate = subCmd{
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", 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 { if err := flags.Parse(subCmdCtx.args); err != nil {
return fmt.Errorf("parsing flags: %w", err) return fmt.Errorf("parsing flags: %w", err)
} }
if !hostNameF.Changed || if !hostNameF.Changed || !ipF.Changed {
!ipF.Changed || return errors.New("--hostname and --ip are required")
*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)
} }
var res daemon.CreateHostResult var res daemon.CreateHostResult
err = subCmdCtx.daemonRCPClient.Call( err := subCmdCtx.daemonRCPClient.Call(
subCmdCtx.ctx, subCmdCtx.ctx,
&res, &res,
"CreateHost", "CreateHost",
daemon.CreateHostRequest{ daemon.CreateHostRequest{
CASigningPrivateKey: adm.Nebula.CACredentials.SigningPrivateKey, HostName: hostName,
HostName: hostName, IP: ip,
IP: ip,
}, },
) )
if err != nil { if err != nil {

View File

@ -24,11 +24,6 @@ var subCmdNebulaCreateCert = subCmd{
"Name of the host to generate a certificate for", "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( pubKeyPath := flags.StringP(
"public-key-path", "p", "", "public-key-path", "p", "",
`Path to PEM file containing public key which will be embedded in the cert.`, `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) return fmt.Errorf("parsing flags: %w", err)
} }
if !hostNameF.Changed || if !hostNameF.Changed || *pubKeyPath == "" {
*adminPath == "" ||
*pubKeyPath == "" {
return errors.New("--hostname, --admin-path, and --pub-key-path are required") 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) hostPubPEM, err := os.ReadFile(*pubKeyPath)
if err != nil { if err != nil {
return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err) return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err)
@ -65,7 +53,6 @@ var subCmdNebulaCreateCert = subCmd{
&res, &res,
"CreateNebulaCertificate", "CreateNebulaCertificate",
daemon.CreateNebulaCertificateRequest{ daemon.CreateNebulaCertificateRequest{
CASigningPrivateKey: adm.Nebula.CACredentials.SigningPrivateKey,
HostName: hostName, HostName: hostName,
HostEncryptingPublicKey: hostPub, HostEncryptingPublicKey: hostPub,
}, },

View File

@ -3,15 +3,13 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"isle/admin"
"isle/daemon" "isle/daemon"
"isle/jsonutil" "isle/jsonutil"
"os"
) )
var subCmdNetworkCreate = subCmd{ var subCmdNetworkCreate = subCmd{
name: "create", 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 { do: func(subCmdCtx subCmdCtx) error {
var ( var (
ctx = subCmdCtx.ctx ctx = subCmdCtx.ctx
@ -53,16 +51,11 @@ var subCmdNetworkCreate = subCmd{
return errors.New("--name, --domain, --ip-net, and --hostname are required") return errors.New("--name, --domain, --ip-net, and --hostname are required")
} }
var adm admin.Admin err := subCmdCtx.daemonRCPClient.Call(ctx, nil, "CreateNetwork", req)
err := subCmdCtx.daemonRCPClient.Call(ctx, &adm, "CreateNetwork", req)
if err != nil { if err != nil {
return fmt.Errorf("creating network: %w", err) 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 return nil
}, },
} }

View File

@ -33,7 +33,7 @@ func dnsmasqPmuxProcConfig(
confData := dnsmasq.ConfData{ confData := dnsmasq.ConfData{
Resolvers: daemonConfig.DNS.Resolvers, Resolvers: daemonConfig.DNS.Resolvers,
Domain: hostBootstrap.AdminCreationParams.Domain, Domain: hostBootstrap.NetworkCreationParams.Domain,
IP: hostBootstrap.ThisHost().IP().String(), IP: hostBootstrap.ThisHost().IP().String(),
Hosts: hostsSlice, Hosts: hostsSlice,
} }

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"isle/admin"
"isle/bootstrap" "isle/bootstrap"
"isle/jsonutil" "isle/jsonutil"
"isle/nebula" "isle/nebula"
@ -35,15 +34,13 @@ type Daemon interface {
// become this first host's IP. // 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 // The daemon on which this is called will become the first host in the
// actions in the future. // network, and will have full administrative privileges.
CreateNetwork( CreateNetwork(
ctx context.Context, name, domain string, ctx context.Context, name, domain string,
ipNet nebula.IPNet, ipNet nebula.IPNet,
hostName nebula.HostName, hostName nebula.HostName,
) ( ) error
admin.Admin, error,
)
// JoinNetwork joins the Daemon to an existing network using the given // JoinNetwork joins the Daemon to an existing network using the given
// Bootstrap. // Bootstrap.
@ -66,7 +63,6 @@ type Daemon interface {
// address. // address.
CreateHost( CreateHost(
ctx context.Context, ctx context.Context,
caSigningPrivateKey nebula.SigningPrivateKey, // TODO load from secrets storage
hostName nebula.HostName, hostName nebula.HostName,
ip netip.Addr, // TODO automatically choose IP address ip netip.Addr, // TODO automatically choose IP address
) ( ) (
@ -81,7 +77,6 @@ type Daemon interface {
// - ErrHostNotFound // - ErrHostNotFound
CreateNebulaCertificate( CreateNebulaCertificate(
ctx context.Context, ctx context.Context,
caSigningPrivateKey nebula.SigningPrivateKey, // TODO load from secrets storage
hostName nebula.HostName, hostName nebula.HostName,
hostPubKey nebula.EncryptingPublicKey, hostPubKey nebula.EncryptingPublicKey,
) ( ) (
@ -519,21 +514,17 @@ 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 string, ipNet nebula.IPNet, hostName nebula.HostName,
) ( ) error {
admin.Admin, error,
) {
nebulaCACreds, err := nebula.NewCACredentials(domain, ipNet) nebulaCACreds, err := nebula.NewCACredentials(domain, ipNet)
if err != nil { if err != nil {
return admin.Admin{}, fmt.Errorf("creating nebula CA cert: %w", err) return fmt.Errorf("creating nebula CA cert: %w", err)
} }
var ( var (
adm = admin.Admin{ creationParams = bootstrap.CreationParams{
CreationParams: admin.CreationParams{ ID: randStr(32),
ID: randStr(32), Name: name,
Name: name, Domain: domain,
Domain: domain,
},
} }
garageRPCSecret = randStr(32) garageRPCSecret = randStr(32)
@ -541,29 +532,34 @@ func (d *daemon) CreateNetwork(
err = setGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret) err = setGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret)
if err != nil { 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( hostBootstrap, err := bootstrap.New(
nebulaCACreds, nebulaCACreds,
adm.CreationParams, creationParams,
hostName, hostName,
ipNet.FirstAddr(), ipNet.FirstAddr(),
) )
if err != nil { if err != nil {
return adm, fmt.Errorf("initializing bootstrap data: %w", err) return fmt.Errorf("initializing bootstrap data: %w", err)
} }
d.l.Lock() d.l.Lock()
if d.state != daemonStateNoNetwork { if d.state != daemonStateNoNetwork {
d.l.Unlock() d.l.Unlock()
return adm, ErrAlreadyJoined return ErrAlreadyJoined
} }
if len(d.daemonConfig.Storage.Allocations) < 3 { if len(d.daemonConfig.Storage.Allocations) < 3 {
d.l.Unlock() d.l.Unlock()
return adm, ErrInvalidConfig.WithData( return ErrInvalidConfig.WithData(
"At least three storage allocations are required.", "At least three storage allocations are required.",
) )
} }
@ -573,18 +569,16 @@ func (d *daemon) CreateNetwork(
err = d.initialize(hostBootstrap, readyCh) err = d.initialize(hostBootstrap, readyCh)
d.l.Unlock() d.l.Unlock()
if err != nil { if err != nil {
return adm, fmt.Errorf("initializing daemon: %w", err) return fmt.Errorf("initializing daemon: %w", err)
} }
select { select {
case <-readyCh: case <-readyCh:
case <-ctx.Done(): case <-ctx.Done():
return adm, ctx.Err() return ctx.Err()
} }
adm.Nebula.CACredentials = nebulaCACreds return nil
return adm, nil
} }
func (d *daemon) JoinNetwork( func (d *daemon) JoinNetwork(
@ -674,7 +668,6 @@ func makeCACreds(
func (d *daemon) CreateHost( func (d *daemon) CreateHost(
ctx context.Context, ctx context.Context,
caSigningPrivateKey nebula.SigningPrivateKey,
hostName nebula.HostName, hostName nebula.HostName,
ip netip.Addr, ip netip.Addr,
) ( ) (
@ -685,14 +678,17 @@ func (d *daemon) CreateHost(
) ( ) (
JoiningBootstrap, error, JoiningBootstrap, error,
) { ) {
var ( caSigningPrivateKey, err := getNebulaCASigningPrivateKey(
joiningBootstrap JoiningBootstrap ctx, d.secretsStore,
err error
) )
if err != nil {
return JoiningBootstrap{}, fmt.Errorf("getting CA signing key: %w", err)
}
var joiningBootstrap JoiningBootstrap
joiningBootstrap.Bootstrap, err = bootstrap.New( joiningBootstrap.Bootstrap, err = bootstrap.New(
makeCACreds(currBootstrap, caSigningPrivateKey), makeCACreds(currBootstrap, caSigningPrivateKey),
currBootstrap.AdminCreationParams, currBootstrap.NetworkCreationParams,
hostName, hostName,
ip, ip,
) )
@ -722,7 +718,6 @@ func (d *daemon) CreateHost(
func (d *daemon) CreateNebulaCertificate( func (d *daemon) CreateNebulaCertificate(
ctx context.Context, ctx context.Context,
caSigningPrivateKey nebula.SigningPrivateKey,
hostName nebula.HostName, hostName nebula.HostName,
hostPubKey nebula.EncryptingPublicKey, hostPubKey nebula.EncryptingPublicKey,
) ( ) (
@ -738,6 +733,13 @@ func (d *daemon) CreateNebulaCertificate(
return nebula.Certificate{}, ErrHostNotFound 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) caCreds := makeCACreds(currBootstrap, caSigningPrivateKey)
return nebula.NewHostCert(caCreds, hostPubKey, hostName, host.IP()) return nebula.NewHostCert(caCreds, hostPubKey, hostName, host.IP())

View File

@ -4,7 +4,6 @@ import (
"cmp" "cmp"
"context" "context"
"fmt" "fmt"
"isle/admin"
"isle/bootstrap" "isle/bootstrap"
"isle/nebula" "isle/nebula"
"net/netip" "net/netip"
@ -47,9 +46,9 @@ type CreateNetworkRequest struct {
func (r *RPC) CreateNetwork( func (r *RPC) CreateNetwork(
ctx context.Context, req CreateNetworkRequest, 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, 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. // All fields are required.
type CreateHostRequest struct { type CreateHostRequest struct {
CASigningPrivateKey nebula.SigningPrivateKey // TODO load from secrets storage HostName nebula.HostName
HostName nebula.HostName IP netip.Addr
IP netip.Addr
} }
// CreateHostResult wraps the results from the CreateHost RPC method. // CreateHostResult wraps the results from the CreateHost RPC method.
@ -148,7 +146,7 @@ func (r *RPC) CreateHost(
CreateHostResult, error, CreateHostResult, error,
) { ) {
joiningBootstrap, err := r.daemon.CreateHost( joiningBootstrap, err := r.daemon.CreateHost(
ctx, req.CASigningPrivateKey, req.HostName, req.IP, ctx, req.HostName, req.IP,
) )
if err != nil { if err != nil {
return CreateHostResult{}, err return CreateHostResult{}, err
@ -162,7 +160,6 @@ func (r *RPC) CreateHost(
// //
// All fields are required. // All fields are required.
type CreateNebulaCertificateRequest struct { type CreateNebulaCertificateRequest struct {
CASigningPrivateKey nebula.SigningPrivateKey // TODO load from secrets storage
HostName nebula.HostName HostName nebula.HostName
HostEncryptingPublicKey nebula.EncryptingPublicKey HostEncryptingPublicKey nebula.EncryptingPublicKey
} }
@ -181,7 +178,7 @@ func (r *RPC) CreateNebulaCertificate(
CreateNebulaCertificateResult, error, CreateNebulaCertificateResult, error,
) { ) {
cert, err := r.daemon.CreateNebulaCertificate( cert, err := r.daemon.CreateNebulaCertificate(
ctx, req.CASigningPrivateKey, req.HostName, req.HostEncryptingPublicKey, ctx, req.HostName, req.HostEncryptingPublicKey,
) )
if err != nil { if err != nil {
return CreateNebulaCertificateResult{}, err return CreateNebulaCertificateResult{}, err

View File

@ -3,13 +3,26 @@ package daemon
import ( import (
"fmt" "fmt"
"isle/garage" "isle/garage"
"isle/nebula"
"isle/secrets" "isle/secrets"
) )
const ( const (
secretsNSNebula = "nebula"
secretsNSGarage = "garage" 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 // Garage-related secrets

View File

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

View File

@ -4,7 +4,8 @@ source "$UTILS"/with-1-data-1-empty-node-network.sh
adminBS="$XDG_STATE_HOME"/isle/bootstrap.json adminBS="$XDG_STATE_HOME"/isle/bootstrap.json
bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh
[ "$(jq -r <"$bs" '.Bootstrap.AdminCreationParams')" = "$(jq -r <admin.json '.CreationParams')" ] [ "$(jq -r <"$bs" '.Bootstrap.NetworkCreationParams.Domain')" = "shared.test" ]
[ "$(jq -r <"$bs" '.Bootstrap.NetworkCreationParams.Name')" = "testing" ]
[ "$(jq -r <"$bs" '.Bootstrap.SignedHostAssigned.Body.Name')" = "secondus" ] [ "$(jq -r <"$bs" '.Bootstrap.SignedHostAssigned.Body.Name')" = "secondus" ]
[ "$(jq -r <"$bs" '.Bootstrap.Hosts.primus.PublicCredentials')" \ [ "$(jq -r <"$bs" '.Bootstrap.Hosts.primus.PublicCredentials')" \

View File

@ -6,14 +6,12 @@ cat pubkey
( (
isle nebula create-cert \ isle nebula create-cert \
--admin-path admin.json \
--hostname non-esiste \ --hostname non-esiste \
--public-key-path pubkey \ --public-key-path pubkey \
2>&1 || true \ 2>&1 || true \
) | grep '\[6\] Host not found' ) | grep '\[6\] Host not found'
isle nebula create-cert \ isle nebula create-cert \
--admin-path admin.json \
--hostname primus \ --hostname primus \
--public-key-path pubkey \ --public-key-path pubkey \
| grep -- '-----BEGIN NEBULA CERTIFICATE-----' | grep -- '-----BEGIN NEBULA CERTIFICATE-----'

View File

@ -5,10 +5,8 @@ source "$UTILS"/with-1-data-1-empty-node-network.sh
[ "$(cat b/meta/isle/rpc_port)" = "3910" ] [ "$(cat b/meta/isle/rpc_port)" = "3910" ]
[ "$(cat c/meta/isle/rpc_port)" = "3920" ] [ "$(cat c/meta/isle/rpc_port)" = "3920" ]
[ "$(jq -r <admin.json '.CreationParams.ID')" != "" ] [ "$(jq -r <"$BOOTSTRAP_FILE" '.NetworkCreationParams.ID')" != "" ]
[ "$(jq -r <admin.json '.CreationParams.Name')" = "testing" ] [ "$(jq -r <"$BOOTSTRAP_FILE" '.NetworkCreationParams.Name')" = "testing" ]
[ "$(jq -r <admin.json '.CreationParams.Domain')" = "shared.test" ] [ "$(jq -r <"$BOOTSTRAP_FILE" '.NetworkCreationParams.Domain')" = "shared.test" ]
[ "$(jq -rc <"$BOOTSTRAP_FILE" '.AdminCreationParams')" = "$(jq -rc <admin.json '.CreationParams')" ]
[ "$(jq -rc <"$BOOTSTRAP_FILE" '.CAPublicCredentials')" = "$(jq -rc <admin.json '.Nebula.CACredentials.Public')" ]
[ "$(jq -r <"$BOOTSTRAP_FILE" '.SignedHostAssigned.Body.Name')" = "primus" ] [ "$(jq -r <"$BOOTSTRAP_FILE" '.SignedHostAssigned.Body.Name')" = "primus" ]

View File

@ -63,12 +63,10 @@ EOF
--domain shared.test \ --domain shared.test \
--hostname primus \ --hostname primus \
--ip-net "$ipNet" \ --ip-net "$ipNet" \
--name "testing" \ --name "testing"
> admin.json
echo "Creating secondus bootstrap" echo "Creating secondus bootstrap"
isle hosts create \ isle hosts create \
--admin-path admin.json \
--hostname secondus \ --hostname secondus \
--ip "$secondus_ip" \ --ip "$secondus_ip" \
> "$secondus_bootstrap" > "$secondus_bootstrap"