mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
Added cert authority feature for init
This commit is contained in:
+21
-5
@@ -90,6 +90,23 @@ Example single node installation to get started, this will create a new region `
|
||||
./bin/hive admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1
|
||||
```
|
||||
|
||||
## Trust CA Certificate (Required)
|
||||
|
||||
Hive generates a local Certificate Authority (CA) during initialization. For AWS CLI and other tools to trust Hive services over HTTPS, you must add the CA certificate to your system's trust store.
|
||||
|
||||
```bash
|
||||
sudo cp ~/hive/config/ca.pem /usr/local/share/ca-certificates/hive-ca.crt
|
||||
sudo update-ca-certificates
|
||||
```
|
||||
|
||||
Verify the certificate was added:
|
||||
|
||||
```bash
|
||||
ls -la /usr/local/share/ca-certificates/hive-ca.crt
|
||||
```
|
||||
|
||||
This step is required for AWS CLI 2.33+ which requires proper CA trust chains. Without this, you would need to use `--no-verify-ssl` for every AWS CLI command.
|
||||
|
||||
Next, set the AWS profile to use `hive` which points to the local environment.
|
||||
|
||||
```bash
|
||||
@@ -550,13 +567,12 @@ Once the multi-node is configured, follow the original installation instructions
|
||||
- Clone an AMI
|
||||
- Launch an instance
|
||||
|
||||
Note - when using the AWS CLI tool you must specify the IP address of the node (10.11.12.1, 10.11.12.2, or 10.11.12.3 ) and ignore SSL verification for development purposes, specifically appending the arguments `--endpoint-url https://10.11.12.3:9999/ --no-verify-ssl`.
|
||||
Note - when using the AWS CLI tool you must specify the IP address of the node (10.11.12.1, 10.11.12.2, or 10.11.12.3).
|
||||
|
||||
**Important**: For multi-node setups, the server certificates need to include the additional IP addresses. You may need to regenerate certificates with the `--force` flag after configuring multi-node, or ensure the CA certificate is trusted system-wide (see "Trust CA Certificate" section above).
|
||||
|
||||
```bash
|
||||
aws --endpoint-url https://10.11.12.3:9999/ --no-verify-ssl ec2 describe-instances --insta
|
||||
nce-ids i-9f5f648adc57ea46d
|
||||
|
||||
urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host '10.11.12.3'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
|
||||
aws --endpoint-url https://10.11.12.3:9999/ ec2 describe-instances --instance-ids i-9f5f648adc57ea46d
|
||||
```
|
||||
|
||||
```json
|
||||
|
||||
+210
-15
@@ -78,26 +78,49 @@ func GenerateConfigFile(configPath string, configTemplate string, configSettings
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateCertificatesIfNeeded(configDir string, force bool) (certPath string) {
|
||||
// Generate SSL certificates
|
||||
certPath = filepath.Join(configDir, "server.pem")
|
||||
keyPath := filepath.Join(configDir, "server.key")
|
||||
func GenerateCertificatesIfNeeded(configDir string, force bool) (caCertPath string) {
|
||||
// Certificate paths
|
||||
caCertPath = filepath.Join(configDir, "ca.pem")
|
||||
caKeyPath := filepath.Join(configDir, "ca.key")
|
||||
serverCertPath := filepath.Join(configDir, "server.pem")
|
||||
serverKeyPath := filepath.Join(configDir, "server.key")
|
||||
|
||||
if force || !FileExists(certPath) || !FileExists(keyPath) {
|
||||
fmt.Println("\n🔐 Generating SSL certificates...")
|
||||
if err := GenerateSelfSignedCert(certPath, keyPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating SSL certificates: %v\n", err)
|
||||
// Check if we need to generate certificates
|
||||
needsGeneration := force ||
|
||||
!FileExists(caCertPath) || !FileExists(caKeyPath) ||
|
||||
!FileExists(serverCertPath) || !FileExists(serverKeyPath)
|
||||
|
||||
if needsGeneration {
|
||||
fmt.Println("\n🔐 Generating Certificate Authority and SSL certificates...")
|
||||
|
||||
// Step 1: Generate CA certificate
|
||||
if err := GenerateCACert(caCertPath, caKeyPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating CA certificate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✅ SSL certificates generated:\n")
|
||||
fmt.Printf(" Certificate: %s\n", certPath)
|
||||
fmt.Printf(" Key: %s\n", keyPath)
|
||||
fmt.Printf("✅ CA certificate generated:\n")
|
||||
fmt.Printf(" CA Certificate: %s\n", caCertPath)
|
||||
fmt.Printf(" CA Key: %s\n", caKeyPath)
|
||||
|
||||
// Step 2: Generate server certificate signed by CA
|
||||
if err := GenerateSignedCert(serverCertPath, serverKeyPath, caCertPath, caKeyPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating server certificate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✅ Server certificate generated (signed by CA):\n")
|
||||
fmt.Printf(" Certificate: %s\n", serverCertPath)
|
||||
fmt.Printf(" Key: %s\n", serverKeyPath)
|
||||
|
||||
// Print instructions for adding CA to system trust store
|
||||
fmt.Println("\n📋 To trust the Hive CA system-wide (recommended):")
|
||||
fmt.Printf(" sudo cp %s /usr/local/share/ca-certificates/hive-ca.crt\n", caCertPath)
|
||||
fmt.Println(" sudo update-ca-certificates")
|
||||
fmt.Println("\n This allows AWS CLI and other tools to trust Hive services automatically.")
|
||||
} else {
|
||||
fmt.Println("\n✅ SSL certificates already exist")
|
||||
fmt.Println("\n✅ CA and SSL certificates already exist")
|
||||
}
|
||||
|
||||
return certPath
|
||||
|
||||
return caCertPath
|
||||
}
|
||||
|
||||
func CreateServiceDirectories(hiveRoot string) {
|
||||
@@ -213,7 +236,179 @@ func GenerateNATSToken() string {
|
||||
return "nats_" + base64.URLEncoding.EncodeToString(bytes)[:32]
|
||||
}
|
||||
|
||||
// generateSelfSignedCert generates a self-signed SSL certificate
|
||||
// GenerateCACert generates a Certificate Authority certificate and key
|
||||
func GenerateCACert(caCertPath, caKeyPath string) error {
|
||||
// Generate CA private key
|
||||
caPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate CA private key: %w", err)
|
||||
}
|
||||
|
||||
// Create CA certificate template
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(3650 * 24 * time.Hour) // 10 years
|
||||
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
|
||||
caTemplate := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Hive Local CA",
|
||||
Organization: []string{"Hive Platform"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: 1,
|
||||
}
|
||||
|
||||
// Self-sign the CA certificate
|
||||
caDerBytes, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPrivateKey.PublicKey, caPrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CA certificate: %w", err)
|
||||
}
|
||||
|
||||
// Write CA certificate to file
|
||||
caCertOut, err := os.Create(caCertPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CA cert file: %w", err)
|
||||
}
|
||||
defer caCertOut.Close()
|
||||
|
||||
if err := pem.Encode(caCertOut, &pem.Block{Type: "CERTIFICATE", Bytes: caDerBytes}); err != nil {
|
||||
return fmt.Errorf("failed to write CA cert: %w", err)
|
||||
}
|
||||
|
||||
// Write CA private key to file
|
||||
caKeyOut, err := os.OpenFile(caKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CA key file: %w", err)
|
||||
}
|
||||
defer caKeyOut.Close()
|
||||
|
||||
caPrivBytes, err := x509.MarshalPKCS8PrivateKey(caPrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal CA private key: %w", err)
|
||||
}
|
||||
|
||||
if err := pem.Encode(caKeyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: caPrivBytes}); err != nil {
|
||||
return fmt.Errorf("failed to write CA key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateSignedCert generates a server certificate signed by the CA
|
||||
func GenerateSignedCert(certPath, keyPath, caCertPath, caKeyPath string) error {
|
||||
// Load CA certificate
|
||||
caCertPEM, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read CA cert: %w", err)
|
||||
}
|
||||
|
||||
caCertBlock, _ := pem.Decode(caCertPEM)
|
||||
if caCertBlock == nil {
|
||||
return fmt.Errorf("failed to decode CA cert PEM")
|
||||
}
|
||||
|
||||
caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse CA cert: %w", err)
|
||||
}
|
||||
|
||||
// Load CA private key
|
||||
caKeyPEM, err := os.ReadFile(caKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read CA key: %w", err)
|
||||
}
|
||||
|
||||
caKeyBlock, _ := pem.Decode(caKeyPEM)
|
||||
if caKeyBlock == nil {
|
||||
return fmt.Errorf("failed to decode CA key PEM")
|
||||
}
|
||||
|
||||
caPrivateKey, err := x509.ParsePKCS8PrivateKey(caKeyBlock.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse CA private key: %w", err)
|
||||
}
|
||||
|
||||
caRSAKey, ok := caPrivateKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("CA key is not RSA")
|
||||
}
|
||||
|
||||
// Generate server private key
|
||||
serverPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate server private key: %w", err)
|
||||
}
|
||||
|
||||
// Create server certificate template
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(365 * 24 * time.Hour) // 1 year for server certs
|
||||
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
|
||||
serverTemplate := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "localhost",
|
||||
Organization: []string{"Hive Platform"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
||||
}
|
||||
|
||||
// Sign the server certificate with the CA
|
||||
serverDerBytes, err := x509.CreateCertificate(rand.Reader, &serverTemplate, caCert, &serverPrivateKey.PublicKey, caRSAKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server certificate: %w", err)
|
||||
}
|
||||
|
||||
// Write server certificate to file
|
||||
certOut, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create cert file: %w", err)
|
||||
}
|
||||
defer certOut.Close()
|
||||
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: serverDerBytes}); err != nil {
|
||||
return fmt.Errorf("failed to write cert: %w", err)
|
||||
}
|
||||
|
||||
// Write server private key to file
|
||||
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create key file: %w", err)
|
||||
}
|
||||
defer keyOut.Close()
|
||||
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(serverPrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal private key: %w", err)
|
||||
}
|
||||
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
||||
return fmt.Errorf("failed to write key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateSelfSignedCert generates a self-signed SSL certificate (legacy, kept for compatibility)
|
||||
func GenerateSelfSignedCert(certPath, keyPath string) error {
|
||||
// Generate private key
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
|
||||
Reference in New Issue
Block a user