Accept a version argument only if the secret has that version (closes #67)
check / check (push) Successful in 1m9s
check / check (push) Successful in 1m9s
version rm, version promote and get --version joined the version argument into a path unchecked, so "", ".", "..", "../../.." removed or read every version, the secret, the vault or directories above it. A version is now accepted only if it is one of the versions ListVersions lists for the secret, compared by name before any path is built (secret.VersionExists, used by all three). An empty --version is rejected instead of meaning the current version: GetSecretVersion no longer treats "" as current, and GetSecret looks the current version up itself. Model: opus-5-5
This commit is contained in:
@@ -49,7 +49,7 @@ var (
|
||||
|
||||
// ErrVersionNotFound indicates the requested secret version does not
|
||||
// exist. Composed as
|
||||
// "version <version> not found for secret <name>".
|
||||
// "version '<version>' not found for secret '<name>'".
|
||||
ErrVersionNotFound = errors.New("not found for secret")
|
||||
|
||||
// ErrNoVersions indicates the source secret has no versions. Composed
|
||||
|
||||
@@ -235,10 +235,10 @@ func testRetrieveSpecificVersions(
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("version-3-data"), value3)
|
||||
|
||||
// Empty version should return current
|
||||
valueCurrent, err := vault.GetSecretVersion(secretName, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("version-3-data"), valueCurrent)
|
||||
// An empty version is not one of the versions; GetSecret gets the
|
||||
// current one
|
||||
_, err = vault.GetSecretVersion(secretName, "")
|
||||
require.ErrorIs(t, err, ErrVersionNotFound)
|
||||
}
|
||||
|
||||
func testPromoteOldVersion(
|
||||
|
||||
+30
-37
@@ -259,18 +259,31 @@ func updateVersionMetadata(
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSecret retrieves a secret from this vault
|
||||
// GetSecret retrieves the current version of a secret from this vault
|
||||
func (v *Vault) GetSecret(name string) ([]byte, error) {
|
||||
secret.DebugWith("Getting secret from vault",
|
||||
slog.String("vault_name", v.Name),
|
||||
slog.String("secret_name", name),
|
||||
)
|
||||
|
||||
return v.GetSecretVersion(name, "")
|
||||
// GetSecretObject validates the name and checks that the secret exists
|
||||
secretObj, err := v.GetSecretObject(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentVersion, err := secret.GetCurrentVersion(v.fs, secretObj.Directory)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get current version", "error", err, "secret_name", name)
|
||||
|
||||
return nil, fmt.Errorf("failed to get current version: %w", err)
|
||||
}
|
||||
|
||||
return v.GetSecretVersion(name, currentVersion)
|
||||
}
|
||||
|
||||
// GetSecretVersion retrieves a specific version of a secret (empty version
|
||||
// means current)
|
||||
// GetSecretVersion retrieves a specific version of a secret. The version
|
||||
// must be one of the secret's versions; GetSecret gets the current one.
|
||||
func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) {
|
||||
secret.DebugWith("Getting secret version from vault",
|
||||
slog.String("vault_name", v.Name),
|
||||
@@ -278,8 +291,8 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) {
|
||||
slog.String("version", version),
|
||||
)
|
||||
|
||||
// Validate the name and resolve the version to fetch
|
||||
version, err := v.resolveSecretVersion(name, version)
|
||||
// Validate the name and check that the version exists
|
||||
err := v.checkSecretVersion(name, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -640,15 +653,15 @@ func (v *Vault) updatePreviousVersion(
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveSecretVersion validates the secret name, verifies the secret and
|
||||
// version exist, and resolves an empty version to the current one.
|
||||
func (v *Vault) resolveSecretVersion(name, version string) (string, error) {
|
||||
// checkSecretVersion validates the secret name and verifies that the secret
|
||||
// exists and that version is one of its versions.
|
||||
func (v *Vault) checkSecretVersion(name, version string) error {
|
||||
// Validate secret name to prevent path traversal
|
||||
err := ValidateSecretName(name)
|
||||
if err != nil {
|
||||
secret.Debug("Invalid secret name provided", "secret_name", name)
|
||||
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
// Get vault directory
|
||||
@@ -656,7 +669,7 @@ func (v *Vault) resolveSecretVersion(name, version string) (string, error) {
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get vault directory", "error", err, "vault_name", v.Name)
|
||||
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert slashes to percent signs for storage
|
||||
@@ -668,50 +681,30 @@ func (v *Vault) resolveSecretVersion(name, version string) (string, error) {
|
||||
if err != nil {
|
||||
secret.Debug("Failed to check if secret exists", "error", err, "secret_name", name)
|
||||
|
||||
return "", fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
return fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
secret.Debug("Secret not found in vault", "secret_name", name, "vault_name", v.Name)
|
||||
|
||||
return "", fmt.Errorf("secret %s %w", name, ErrSecretNotFound)
|
||||
}
|
||||
|
||||
// Determine which version to get
|
||||
if version == "" {
|
||||
// Get current version
|
||||
currentVersion, err := secret.GetCurrentVersion(v.fs, secretDir)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get current version", "error", err, "secret_name", name)
|
||||
|
||||
return "", fmt.Errorf("failed to get current version: %w", err)
|
||||
}
|
||||
|
||||
version = currentVersion
|
||||
|
||||
secret.Debug("Using current version", "version", version, "secret_name", name)
|
||||
return fmt.Errorf("secret %s %w", name, ErrSecretNotFound)
|
||||
}
|
||||
|
||||
// Check if version exists
|
||||
versionPath := filepath.Join(secretDir, "versions", version)
|
||||
|
||||
exists, err = afero.DirExists(v.fs, versionPath)
|
||||
exists, err = secret.VersionExists(v.fs, secretDir, version)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to check if version exists", "error", err, "version", version)
|
||||
|
||||
return "", fmt.Errorf("failed to check if version exists: %w", err)
|
||||
return fmt.Errorf("failed to check if version exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
secret.Debug("Version not found", "version", version, "secret_name", name)
|
||||
|
||||
return "", fmt.Errorf(
|
||||
"version %s %w %s",
|
||||
version, ErrVersionNotFound, name,
|
||||
)
|
||||
return fmt.Errorf("version '%s' %w '%s'", version, ErrVersionNotFound, name)
|
||||
}
|
||||
|
||||
return version, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// createAndSaveVersion generates a new version name, sets the version
|
||||
|
||||
@@ -202,10 +202,10 @@ func TestVaultGetSecretVersion(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("version-2"), value)
|
||||
|
||||
// Get current (empty version)
|
||||
value, err = vault.GetSecretVersion(testSecretPath, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("version-2"), value)
|
||||
// An empty version is not one of the versions; GetSecret gets the
|
||||
// current one
|
||||
_, err = vault.GetSecretVersion(testSecretPath, "")
|
||||
require.ErrorIs(t, err, ErrVersionNotFound)
|
||||
}
|
||||
|
||||
//nolint:paralleltest // createTestVaultWithKey uses t.Setenv
|
||||
|
||||
Reference in New Issue
Block a user