Initial commit with server startup infrastructure
Core infrastructure: - Uber fx dependency injection - Chi router with middleware stack - SQLite database with embedded migrations - Embedded templates and static assets - Structured logging with slog Features implemented: - Authentication (login, logout, session management, argon2id hashing) - App management (create, edit, delete, list) - Deployment pipeline (clone, build, deploy, health check) - Webhook processing for Gitea - Notifications (ntfy, Slack) - Environment variables, labels, volumes per app - SSH key generation for deploy keys Server startup: - Server.Run() starts HTTP server on configured port - Server.Shutdown() for graceful shutdown - SetupRoutes() wires all handlers with chi router
This commit is contained in:
53
internal/ssh/keygen.go
Normal file
53
internal/ssh/keygen.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Package ssh provides SSH key generation utilities.
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// KeyPair contains an SSH key pair.
|
||||
type KeyPair struct {
|
||||
PrivateKey string
|
||||
PublicKey string
|
||||
}
|
||||
|
||||
// GenerateKeyPair generates a new Ed25519 SSH key pair.
|
||||
func GenerateKeyPair() (*KeyPair, error) {
|
||||
// Generate Ed25519 key pair
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate key pair: %w", err)
|
||||
}
|
||||
|
||||
// Convert private key to PEM format
|
||||
privateKeyPEM, err := ssh.MarshalPrivateKey(privateKey, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal private key: %w", err)
|
||||
}
|
||||
|
||||
// Convert public key to authorized_keys format
|
||||
sshPublicKey, err := ssh.NewPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SSH public key: %w", err)
|
||||
}
|
||||
|
||||
return &KeyPair{
|
||||
PrivateKey: string(pem.EncodeToMemory(privateKeyPEM)),
|
||||
PublicKey: string(ssh.MarshalAuthorizedKey(sshPublicKey)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidatePrivateKey validates that a private key is valid.
|
||||
func ValidatePrivateKey(privateKeyPEM string) error {
|
||||
_, err := ssh.ParsePrivateKey([]byte(privateKeyPEM))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid private key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
internal/ssh/keygen_test.go
Normal file
70
internal/ssh/keygen_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package ssh_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.eeqj.de/sneak/upaas/internal/ssh"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateKeyPair(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("generates valid key pair", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
keyPair, err := ssh.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, keyPair)
|
||||
|
||||
// Private key should be PEM encoded
|
||||
assert.Contains(t, keyPair.PrivateKey, "-----BEGIN OPENSSH PRIVATE KEY-----")
|
||||
assert.Contains(t, keyPair.PrivateKey, "-----END OPENSSH PRIVATE KEY-----")
|
||||
|
||||
// Public key should be in authorized_keys format
|
||||
assert.True(t, strings.HasPrefix(keyPair.PublicKey, "ssh-ed25519 "))
|
||||
})
|
||||
|
||||
t.Run("generates unique keys each time", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
keyPair1, err := ssh.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
keyPair2, err := ssh.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, keyPair1.PrivateKey, keyPair2.PrivateKey)
|
||||
assert.NotEqual(t, keyPair1.PublicKey, keyPair2.PublicKey)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidatePrivateKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("validates generated key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
keyPair, err := ssh.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ssh.ValidatePrivateKey(keyPair.PrivateKey)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("rejects invalid key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := ssh.ValidatePrivateKey("not a valid key")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("rejects empty key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := ssh.ValidatePrivateKey("")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user