Complete infrastructure platform CLI: - Container runtime (systemd-nspawn) - VoltVisor VMs (Neutron Stardust / QEMU) - Stellarium CAS (content-addressed storage) - ORAS Registry - GitOps integration - Landlock LSM security - Compose orchestration - Mesh networking Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
163 lines
4.0 KiB
Go
163 lines
4.0 KiB
Go
/*
|
|
Volt Platform — License Persistence
|
|
Store and retrieve license data and cryptographic keys
|
|
*/
|
|
package license
|
|
|
|
import (
|
|
"crypto/ecdh"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
LicenseDir = "/etc/volt/license"
|
|
LicenseFile = "/etc/volt/license/license.yaml"
|
|
NodeKeyFile = "/etc/volt/license/node.key"
|
|
NodePubFile = "/etc/volt/license/node.pub"
|
|
)
|
|
|
|
// Store handles license persistence
|
|
type Store struct {
|
|
Dir string
|
|
}
|
|
|
|
// NewStore creates a license store with the default directory
|
|
func NewStore() *Store {
|
|
return &Store{Dir: LicenseDir}
|
|
}
|
|
|
|
// licensePath returns the full path for the license file
|
|
func (s *Store) licensePath() string {
|
|
return filepath.Join(s.Dir, "license.yaml")
|
|
}
|
|
|
|
// keyPath returns the full path for the node private key
|
|
func (s *Store) keyPath() string {
|
|
return filepath.Join(s.Dir, "node.key")
|
|
}
|
|
|
|
// pubPath returns the full path for the node public key
|
|
func (s *Store) pubPath() string {
|
|
return filepath.Join(s.Dir, "node.pub")
|
|
}
|
|
|
|
// Load reads the license from disk
|
|
func (s *Store) Load() (*License, error) {
|
|
data, err := os.ReadFile(s.licensePath())
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("no license found (not registered)")
|
|
}
|
|
return nil, fmt.Errorf("failed to read license: %w", err)
|
|
}
|
|
|
|
var lic License
|
|
if err := yaml.Unmarshal(data, &lic); err != nil {
|
|
return nil, fmt.Errorf("failed to parse license: %w", err)
|
|
}
|
|
|
|
return &lic, nil
|
|
}
|
|
|
|
// Save writes the license to disk
|
|
func (s *Store) Save(lic *License) error {
|
|
if err := os.MkdirAll(s.Dir, 0700); err != nil {
|
|
return fmt.Errorf("failed to create license directory: %w", err)
|
|
}
|
|
|
|
data, err := yaml.Marshal(lic)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal license: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(s.licensePath(), data, 0600); err != nil {
|
|
return fmt.Errorf("failed to write license: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsRegistered checks if a valid license exists on disk
|
|
func (s *Store) IsRegistered() bool {
|
|
_, err := s.Load()
|
|
return err == nil
|
|
}
|
|
|
|
// IsExpired checks if the current license has expired
|
|
func (s *Store) IsExpired() (bool, error) {
|
|
lic, err := s.Load()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if lic.ExpiresAt.IsZero() {
|
|
return false, nil // no expiry = never expires
|
|
}
|
|
return time.Now().After(lic.ExpiresAt), nil
|
|
}
|
|
|
|
// HasFeature checks if the current license tier includes a feature
|
|
func (s *Store) HasFeature(feature string) (bool, error) {
|
|
lic, err := s.Load()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return TierIncludes(lic.Tier, feature), nil
|
|
}
|
|
|
|
// GenerateKeypair generates an X25519 keypair and stores it on disk
|
|
func (s *Store) GenerateKeypair() (pubHex string, err error) {
|
|
if err := os.MkdirAll(s.Dir, 0700); err != nil {
|
|
return "", fmt.Errorf("failed to create license directory: %w", err)
|
|
}
|
|
|
|
// Generate X25519 keypair using crypto/ecdh
|
|
curve := ecdh.X25519()
|
|
privKey, err := curve.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate keypair: %w", err)
|
|
}
|
|
|
|
// Encode to hex
|
|
privHex := hex.EncodeToString(privKey.Bytes())
|
|
pubHex = hex.EncodeToString(privKey.PublicKey().Bytes())
|
|
|
|
// Store private key (restrictive permissions)
|
|
if err := os.WriteFile(s.keyPath(), []byte(privHex+"\n"), 0600); err != nil {
|
|
return "", fmt.Errorf("failed to write private key: %w", err)
|
|
}
|
|
|
|
// Store public key
|
|
if err := os.WriteFile(s.pubPath(), []byte(pubHex+"\n"), 0644); err != nil {
|
|
return "", fmt.Errorf("failed to write public key: %w", err)
|
|
}
|
|
|
|
return pubHex, nil
|
|
}
|
|
|
|
// ReadPublicKey reads the stored node public key
|
|
func (s *Store) ReadPublicKey() (string, error) {
|
|
data, err := os.ReadFile(s.pubPath())
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read public key: %w", err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// Remove deletes the license and keypair from disk
|
|
func (s *Store) Remove() error {
|
|
files := []string{s.licensePath(), s.keyPath(), s.pubPath()}
|
|
for _, f := range files {
|
|
if err := os.Remove(f); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to remove %s: %w", f, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|