isle/go/cmd/entrypoint/admin.go

211 lines
4.8 KiB
Go

package main
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"isle/admin"
"isle/bootstrap"
"isle/nebula"
"net"
"os"
)
func randStr(l int) string {
b := make([]byte, l)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return hex.EncodeToString(b)
}
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)
}
var subCmdAdminCreateBootstrap = subCmd{
name: "create-bootstrap",
descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout",
checkLock: false,
do: func(subCmdCtx subCmdCtx) error {
flags := subCmdCtx.flagSet(false)
hostName := flags.StringP(
"hostname", "h", "",
"Name of the host to generate bootstrap.json for",
)
ipStr := flags.StringP(
"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 *hostName == "" || *ipStr == "" || *adminPath == "" {
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)
if err != nil {
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
}
garageBootstrap := bootstrap.Garage{
RPCSecret: adm.Garage.RPCSecret,
AdminToken: randStr(32),
GlobalBucketS3APICredentials: adm.Garage.GlobalBucketS3APICredentials,
}
newHostBootstrap, err := bootstrap.New(
adm.Nebula.CACredentials,
adm.CreationParams,
garageBootstrap,
*hostName,
ip,
)
if err != nil {
return fmt.Errorf("initializing bootstrap data: %w", err)
}
hostsRes, err := subCmdCtx.getHosts()
if err != nil {
return fmt.Errorf("getting hosts: %w", err)
}
for _, host := range hostsRes.Hosts {
newHostBootstrap.Hosts[host.Name] = host
}
return newHostBootstrap.WriteTo(os.Stdout)
},
}
var subCmdAdminCreateNebulaCert = subCmd{
name: "create-nebula-cert",
descr: "Creates a signed nebula certificate file and writes it to stdout",
checkLock: false,
do: func(subCmdCtx subCmdCtx) error {
flags := subCmdCtx.flagSet(false)
hostName := flags.StringP(
"hostname", "h", "",
"Name of the host to generate bootstrap.json for",
)
ipStr := flags.StringP(
"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.`,
)
pubKeyPath := flags.StringP(
"public-key-path", "p", "",
`Path to PEM file containing public key which will be embedded in the cert.`,
)
if err := flags.Parse(subCmdCtx.args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if *hostName == "" || *ipStr == "" || *adminPath == "" || *pubKeyPath == "" {
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)
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)
}
var hostPub nebula.EncryptingPublicKey
if err := hostPub.UnmarshalNebulaPEM(hostPubPEM); err != nil {
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
}
nebulaHostCert, err := nebula.NewHostCert(
adm.Nebula.CACredentials, hostPub, *hostName, ip,
)
if err != nil {
return fmt.Errorf("creating cert: %w", err)
}
nebulaHostCertPEM, err := nebulaHostCert.Unwrap().MarshalToPEM()
if err != nil {
return fmt.Errorf("marshaling cert to PEM: %w", err)
}
if _, err := os.Stdout.Write([]byte(nebulaHostCertPEM)); err != nil {
return fmt.Errorf("writing to stdout: %w", err)
}
return nil
},
}
var subCmdAdmin = subCmd{
name: "admin",
descr: "Sub-commands which only admins can run",
do: func(subCmdCtx subCmdCtx) error {
return subCmdCtx.doSubCmd(
subCmdAdminCreateBootstrap,
subCmdAdminCreateNebulaCert,
)
},
}