// Package bootstrap deals with the creation of bootstrap files package bootstrap import ( "context" crypticnet "cryptic-net" "cryptic-net/garage" "cryptic-net/nebula" "cryptic-net/tarutil" "fmt" "io" "io/fs" ) // GetHashFromFS returns the hash of the contents of the given bootstrap file. // It may return nil if the bootstrap file doesn't have a hash. func GetHashFromFS(bootstrapFS fs.FS) ([]byte, error) { b, err := fs.ReadFile(bootstrapFS, tarutil.HashBinPath) if err != nil { return nil, fmt.Errorf("reading file %q from bootstrap fs: %w", tarutil.HashBinPath, err) } return b, nil } // GetHashFromReader reads the given tgz file as an fs.FS, and passes that to // GetHashFromFS. func GetHashFromReader(r io.Reader) ([]byte, error) { bootstrapFS, err := tarutil.FSFromReader(r) if err != nil { return nil, fmt.Errorf("reading tar fs from reader: %w", err) } return GetHashFromFS(bootstrapFS) } func newBootstrap( ctx context.Context, into io.Writer, hostname provider, nebulaCerts provider, nebulaHosts provider, garageRPCSecret provider, garageGlobalBucketKey provider, garageHosts provider, ) error { pairs := []struct { path string provider provider }{ {"hostname", hostname}, {"nebula/certs", nebulaCerts}, {"nebula/hosts", nebulaHosts}, {"garage/rpc-secret.txt", garageRPCSecret}, {"garage/global-bucket-key.yml", garageGlobalBucketKey}, {"garage/hosts", garageHosts}, } w := tarutil.NewTGZWriter(into) for _, pair := range pairs { if err := pair.provider(ctx, w, pair.path); err != nil { return fmt.Errorf("populating %q in new bootstrap: %w", pair.path, err) } } return w.Close() } // NewForThisHost generates a new bootstrap file for the current host, based on // the existing environment as well as data in garage. func NewForThisHost(env *crypticnet.Env, into io.Writer) error { client, err := garage.GlobalBucketAPIClient(env) if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } return newBootstrap( env.Context, into, provideFromFS(env.BootstrapFS), // hostname provideDirFromFS(env.BootstrapFS), // nebulaCerts provideDirFromGarage(client), // nebulaHosts provideFromFS(env.BootstrapFS), // garageRPCSecret provideFromFS(env.BootstrapFS), // garageGlobalBucketKey provideDirFromGarage(client), // garageHosts ) } // NewForHost generates a new bootstrap file for an arbitrary host, based on the // given admin file's FS and data in garage. func NewForHost(env *crypticnet.Env, adminFS fs.FS, name string, into io.Writer) error { host, ok := env.Hosts[name] if !ok { return fmt.Errorf("unknown host %q, make sure host entry has been created", name) } client, err := garage.GlobalBucketAPIClient(env) if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } nebulaHostCert, err := nebula.NewHostCert(adminFS, host.Nebula) if err != nil { return fmt.Errorf("creating new nebula host key/cert: %w", err) } nebulaHostCertDir := map[string][]byte{ "ca.crt": nebulaHostCert.CACert, "host.key": nebulaHostCert.HostKey, "host.crt": nebulaHostCert.HostCert, } return newBootstrap( env.Context, into, provideFromBytes([]byte(name)), // hostname provideDirFromMap(nebulaHostCertDir), // nebulaCerts provideDirFromGarage(client), // nebulaHosts provideFromFS(adminFS), // garageRPCSecret provideFromFS(adminFS), // garageGlobalBucketKey provideDirFromGarage(client), // garageHosts ) }