{{ctx.Locale.Tr "vault.config_error_fix"}}
+ {{end}} +diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 2b2979380d..0f80f01695 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -4762,6 +4762,13 @@ "vault.config_error_title": "Vault Not Configured", "vault.config_error_message": "The vault encryption key has not been configured. Secrets cannot be encrypted or decrypted.", "vault.config_error_fix": "Add MASTER_KEY to the [vault] section in app.ini or set the GITCADDY_VAULT_KEY environment variable.", + "vault.fallback_key_warning_title": "Vault Using Fallback Encryption Key", + "vault.fallback_key_warning_message": "The vault is currently using Gitea's SECRET_KEY for encryption because no dedicated vault key has been configured. If the SECRET_KEY is ever changed or lost, all vault secrets will become permanently unreadable.", + "vault.fallback_key_warning_fix": "To fix this, copy the current SECRET_KEY value and set it as MASTER_KEY in the [vault] section of app.ini, or set the GITCADDY_VAULT_KEY environment variable. This ensures vault encryption remains stable even if the SECRET_KEY changes.", + "vault.decryption_error_title": "Vault Decryption Failed", + "vault.decryption_error_message": "Unable to decrypt vault secrets. The encryption key may have been changed or is incorrect.", + "vault.decryption_error_fix": "Verify that the MASTER_KEY in the [vault] section of app.ini (or the GITCADDY_VAULT_KEY environment variable) matches the key that was used when the secrets were originally created.", + "vault.encryption_error_message": "Unable to encrypt the secret value. The vault encryption key may not be configured correctly.", "vault.type_file": "File", "vault.compare": "Compare", "vault.compare_version": "Compare this version", diff --git a/routers/web/repo/vault/vault.go b/routers/web/repo/vault/vault.go index d5a0207ce5..4d5ad73453 100644 --- a/routers/web/repo/vault/vault.go +++ b/routers/web/repo/vault/vault.go @@ -6,6 +6,7 @@ package vault import ( "net/http" "strconv" + "strings" unit "code.gitcaddy.com/server/v3/models/unit" "code.gitcaddy.com/server/v3/modules/web" @@ -73,6 +74,8 @@ func setVaultLicenseData(ctx *context.Context) { // Configuration status (for admin warnings) ctx.Data["VaultConfigured"] = vault_service.IsConfigured() ctx.Data["VaultConfigError"] = vault_service.GetConfigurationError() + ctx.Data["VaultUsingFallbackKey"] = vault_service.IsUsingFallbackKey() + ctx.Data["VaultKeySource"] = vault_service.KeySource() } // List displays the vault secrets list @@ -82,8 +85,18 @@ func List(ctx *context.Context) { return } + // Check configuration status upfront so the list page can show a warning + if !vault_service.IsConfigured() { + showConfigError(ctx) + return + } + secrets, err := vault_service.ListSecrets(ctx, ctx.Repo.Repository.ID, false) if err != nil { + if isCryptoError(err) { + showConfigError(ctx) + return + } ctx.ServerError("ListSecrets", err) return } @@ -144,6 +157,10 @@ func View(ctx *context.Context) { // Get decrypted value for display value, err := vault_service.GetSecretValue(ctx, ctx.Repo.Repository.ID, name, 0) // 0 = current version if err != nil { + if isCryptoError(err) { + showConfigError(ctx) + return + } ctx.ServerError("GetSecretValue", err) return } @@ -207,6 +224,10 @@ func CreateSecret(ctx *context.Context) { CreatorID: ctx.Doer.ID, }) if err != nil { + if isCryptoError(err) { + showConfigError(ctx) + return + } ctx.ServerError("CreateSecret", err) return } @@ -236,6 +257,10 @@ func UpdateSecret(ctx *context.Context) { ctx.NotFound(nil) return } + if isCryptoError(err) { + showConfigError(ctx) + return + } ctx.ServerError("UpdateSecret", err) return } @@ -485,3 +510,17 @@ func showPluginNotAvailable(ctx *context.Context) { ctx.Data["VaultPluginNotInstalled"] = true ctx.HTML(http.StatusOK, "repo/vault/not_installed") } + +// isCryptoError returns true if the error is a vault encryption or decryption failure +func isCryptoError(err error) bool { + msg := err.Error() + return strings.Contains(msg, "decryption failed") || strings.Contains(msg, "encryption failed") +} + +// showConfigError shows a user-friendly error page for vault configuration/crypto issues +func showConfigError(ctx *context.Context) { + ctx.Data["PageIsVault"] = true + ctx.Data["VaultConfigured"] = vault_service.IsConfigured() + ctx.Data["IsRepoAdmin"] = ctx.Repo.IsAdmin() + ctx.HTML(http.StatusOK, "repo/vault/config_error") +} diff --git a/services/vault/vault.go b/services/vault/vault.go index 5fe80a9181..f8cb6a5332 100644 --- a/services/vault/vault.go +++ b/services/vault/vault.go @@ -62,6 +62,11 @@ type ConfigurablePlugin interface { IsConfigured() bool // ConfigurationError returns the configuration error message, if any ConfigurationError() string + // IsUsingFallbackKey returns true if the vault is using Gitea's SECRET_KEY as + // the encryption key instead of an explicit vault-specific key. + IsUsingFallbackKey() bool + // KeySource returns a human-readable description of where the master key was loaded from. + KeySource() string } // Secret represents a vault secret @@ -199,6 +204,33 @@ func GetConfigurationError() string { return "" } +// IsUsingFallbackKey returns true if the vault is using Gitea's SECRET_KEY +// as the encryption key instead of an explicit vault-specific key. +// Returns false if the plugin doesn't implement the ConfigurablePlugin interface. +func IsUsingFallbackKey() bool { + vp := GetPlugin() + if vp == nil { + return false + } + if cp, ok := vp.(ConfigurablePlugin); ok { + return cp.IsUsingFallbackKey() + } + return false +} + +// KeySource returns a human-readable description of where the master key was loaded from. +// Returns empty string if the plugin doesn't implement the ConfigurablePlugin interface. +func KeySource() string { + vp := GetPlugin() + if vp == nil { + return "" + } + if cp, ok := vp.(ConfigurablePlugin); ok { + return cp.KeySource() + } + return "" +} + // GetLicenseInfo returns the license info for the vault plugin // Returns default Solo license if no license file is present func GetLicenseInfo() *plugins.LicenseInfo { diff --git a/templates/repo/vault/config_error.tmpl b/templates/repo/vault/config_error.tmpl new file mode 100644 index 0000000000..168b10a9d4 --- /dev/null +++ b/templates/repo/vault/config_error.tmpl @@ -0,0 +1,42 @@ +{{template "base/head" .}} +
{{ctx.Locale.Tr "vault.config_error_fix"}}
+ {{end}} +