Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dc9c34bcc | |||
| b824b8e3be | |||
| e9b109c464 | |||
| 69f71cef7e |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "note-1768964774362-3y9dna8sm",
|
||||
"title": "working",
|
||||
"content": "● Done. Updated all vault locale files with the new translation keys.\n\n Summary of what's now in gitcaddy-vault:\n 1. Templates: list.tmpl, view.tmpl, audit.tmpl, tokens.tmpl (copied)\n 2. Locale files: Added 20+ new translation keys to all locale files\n\n What stays in gitcaddy-server:\n - Router handlers (routers/web/repo/vault/vault.go) - these call the vault service\n - Service interface definitions (services/vault/vault.go)\n - Locale files also need the keys (for ctx.Locale.Tr calls in templates)\n\n```\n Now you need to:\n 1. Commit and tag vault as v1.0.32\n 2. Update server go.mod to v1.0.32\n 3. Release server v3.1.10\n 4. Deploy to git-test\n```\n\n● So:\n\n | Key | Value |\n |---------------------------|------------------------------------------------------------------|\n | Old (SECRET_KEY fallback) | bfe6e73282adba6545c2c9ba3542a332a80d62d1644b3b24c14b020a838c136f |\n | New (MASTER_KEY) | af1a7e9d7fe73258800b434ab5ffe7bf1ee3dff49740ef3cc3c5c014587acc08 |\n",
|
||||
"content": "● Done. Updated all vault locale files with the new translation keys.\n\n Summary of what's now in gitcaddy-vault:\n 1. Templates: list.tmpl, view.tmpl, audit.tmpl, tokens.tmpl (copied)\n 2. Locale files: Added 20+ new translation keys to all locale files\n\n What stays in gitcaddy-server:\n - Router handlers (routers/web/repo/vault/vault.go) - these call the vault service\n - Service interface definitions (services/vault/vault.go)\n - Locale files also need the keys (for ctx.Locale.Tr calls in templates)\n\n```\n Now you need to:\n 1. Commit and tag vault as v1.0.32\n 2. Update server go.mod to v1.0.32\n 3. Release server v3.1.10\n 4. Deploy to git-test\n```\n",
|
||||
"createdAt": 1768964774360,
|
||||
"updatedAt": 1770432337981
|
||||
"updatedAt": 1770512254003
|
||||
}
|
||||
24
README.md
24
README.md
@@ -23,6 +23,30 @@ GitCaddy Vault is a commercial module compiled directly into GitCaddy Server tha
|
||||
| `certificate` | TLS/SSL certificates |
|
||||
| `ssh-key` | SSH private keys |
|
||||
|
||||
### Lockbox (End-to-End Encryption)
|
||||
|
||||
Lockbox provides optional client-side encryption where the server **never sees your plaintext secrets**. Perfect for highly sensitive data where you don't want even the server administrator to have access.
|
||||
|
||||
| Feature | Standard Mode | Lockbox Mode |
|
||||
|---------|--------------|--------------|
|
||||
| Server sees plaintext | Yes | No |
|
||||
| Passphrase required | No | Yes |
|
||||
| Recovery if passphrase lost | Yes | No |
|
||||
| Web UI viewing | Yes | CLI/SDK only |
|
||||
|
||||
**How Lockbox Works:**
|
||||
1. Client encrypts secret with your passphrase using Argon2id + AES-256-GCM
|
||||
2. Encrypted blob is sent to server in `lockbox:v1:...` format
|
||||
3. Server wraps the blob with repository DEK (double encryption)
|
||||
4. On retrieval, server unwraps DEK layer, returns lockbox blob
|
||||
5. Client decrypts with your passphrase
|
||||
|
||||
**Encryption Scheme:**
|
||||
- Key derivation: Argon2id (time=1, memory=64MB, parallelism=4)
|
||||
- Cipher: AES-256-GCM with 12-byte nonce
|
||||
- Salt: 16 bytes random per secret
|
||||
- Format: `lockbox:v1:<base64(salt)>:<base64(nonce||ciphertext||tag)>`
|
||||
|
||||
### Security Architecture
|
||||
|
||||
```
|
||||
|
||||
102
plugin.go
102
plugin.go
@@ -5,6 +5,7 @@ package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.marketally.com/gitcaddy/gitcaddy-vault/crypto"
|
||||
"git.marketally.com/gitcaddy/gitcaddy-vault/license"
|
||||
@@ -98,7 +99,106 @@ func (p *VaultPlugin) RegisterModels() []any {
|
||||
|
||||
// Migrate runs database migrations for this plugin
|
||||
func (p *VaultPlugin) Migrate(ctx context.Context, x *xorm.Engine) error {
|
||||
return x.Sync(p.RegisterModels()...)
|
||||
// First, sync tables (works for fresh installations)
|
||||
if err := x.Sync(p.RegisterModels()...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Then run explicit column migrations for upgrades
|
||||
// xorm.Sync() doesn't reliably add columns to existing tables
|
||||
return runColumnMigrations(ctx, x)
|
||||
}
|
||||
|
||||
// runColumnMigrations adds any missing columns to existing tables.
|
||||
// This handles the case where xorm.Sync() fails to add new columns.
|
||||
func runColumnMigrations(ctx context.Context, x *xorm.Engine) error {
|
||||
migrations := []struct {
|
||||
table string
|
||||
column string
|
||||
columnType string
|
||||
defaultVal string
|
||||
}{
|
||||
// v1.0.31: Add encryption_mode column for lockbox (E2E) secrets
|
||||
{"vault_secret", "encryption_mode", "VARCHAR(20)", "'standard'"},
|
||||
}
|
||||
|
||||
for _, m := range migrations {
|
||||
exists, err := columnExists(x, m.table, m.column)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
log.Info("Vault migration: adding column %s.%s", m.table, m.column)
|
||||
sql := buildAddColumnSQL(x, m.table, m.column, m.columnType, m.defaultVal)
|
||||
if _, err := x.Exec(sql); err != nil {
|
||||
return fmt.Errorf("failed to add column %s.%s: %w", m.table, m.column, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// columnExists checks if a column exists in a table
|
||||
func columnExists(x *xorm.Engine, table, column string) (bool, error) {
|
||||
driverName := x.DriverName()
|
||||
log.Info("Vault migration: checking column %s.%s (driver: %s)", table, column, driverName)
|
||||
var sql string
|
||||
|
||||
switch driverName {
|
||||
case "postgres", "pgx":
|
||||
sql = fmt.Sprintf(`SELECT 1 FROM information_schema.columns WHERE table_name = '%s' AND column_name = '%s'`, table, column)
|
||||
case "mysql":
|
||||
sql = fmt.Sprintf(`SELECT 1 FROM information_schema.columns WHERE table_name = '%s' AND column_name = '%s'`, table, column)
|
||||
case "sqlite3", "sqlite":
|
||||
// SQLite uses PRAGMA
|
||||
results, err := x.Query(fmt.Sprintf("PRAGMA table_info(%s)", table))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, row := range results {
|
||||
if string(row["name"]) == column {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
case "mssql":
|
||||
sql = fmt.Sprintf(`SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID('%s') AND name = '%s'`, table, column)
|
||||
default:
|
||||
// Fallback: try information_schema (works for postgres/mysql)
|
||||
log.Warn("Vault migration: unknown driver '%s', using information_schema fallback", driverName)
|
||||
sql = fmt.Sprintf(`SELECT 1 FROM information_schema.columns WHERE table_name = '%s' AND column_name = '%s'`, table, column)
|
||||
}
|
||||
|
||||
results, err := x.Query(sql)
|
||||
if err != nil {
|
||||
log.Error("Vault migration: column check failed: %v", err)
|
||||
return false, err
|
||||
}
|
||||
exists := len(results) > 0
|
||||
log.Info("Vault migration: column %s.%s exists: %v", table, column, exists)
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// buildAddColumnSQL builds the appropriate ALTER TABLE statement for the database type
|
||||
func buildAddColumnSQL(x *xorm.Engine, table, column, columnType, defaultVal string) string {
|
||||
driverName := x.DriverName()
|
||||
log.Info("Vault migration: building ALTER TABLE for driver: %s", driverName)
|
||||
|
||||
switch driverName {
|
||||
case "postgres", "pgx":
|
||||
return fmt.Sprintf(`ALTER TABLE "%s" ADD COLUMN IF NOT EXISTS "%s" %s NOT NULL DEFAULT %s`, table, column, columnType, defaultVal)
|
||||
case "mysql":
|
||||
// MySQL doesn't have IF NOT EXISTS for columns, but we already checked
|
||||
return fmt.Sprintf("ALTER TABLE `%s` ADD COLUMN `%s` %s NOT NULL DEFAULT %s", table, column, columnType, defaultVal)
|
||||
case "sqlite3", "sqlite":
|
||||
return fmt.Sprintf(`ALTER TABLE "%s" ADD COLUMN "%s" %s NOT NULL DEFAULT %s`, table, column, columnType, defaultVal)
|
||||
case "mssql":
|
||||
return fmt.Sprintf(`ALTER TABLE [%s] ADD [%s] %s NOT NULL DEFAULT %s`, table, column, columnType, defaultVal)
|
||||
default:
|
||||
// Fallback to postgres-style (most compatible)
|
||||
log.Warn("Vault migration: unknown driver '%s', using postgres-style ALTER TABLE", driverName)
|
||||
return fmt.Sprintf(`ALTER TABLE "%s" ADD COLUMN IF NOT EXISTS "%s" %s NOT NULL DEFAULT %s`, table, column, columnType, defaultVal)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRepoWebRoutes adds vault routes under /{owner}/{repo}/vault
|
||||
|
||||
Reference in New Issue
Block a user