mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
Adds the deployment option `--user-secrets-disable-file-path` / `CODER_USER_SECRETS_DISABLE_FILE_PATH` / `userSecretsDisableFilePath` (default `false`). When it is set, the create and batch-create user-secret endpoints reject requests with a non-empty file path, and PATCH validates the post-update state under a `SELECT ... FOR UPDATE` row lock, rejecting a new or changed file path as well as enabled states that would have no effective delivery target. Legacy stored paths are preserved, so users can still edit, disable, clear the path, or migrate the secret to env delivery. This is PR 1 of a 4-PR stack; follow-ups cover manifest filtering, the dashboard, and CLI/docs. Refs #27488 Reviewed and updated by Coder Agents on behalf of @dylanhuff-at-coder.
113 lines
4.0 KiB
Go
113 lines
4.0 KiB
Go
package coderd_test
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/audit"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func TestUserSecretsCapabilities(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
disableFilePath bool
|
|
filePathDeliveryEnabled bool
|
|
}{
|
|
{name: "FilePathDeliveryEnabled", disableFilePath: false, filePathDeliveryEnabled: true},
|
|
{name: "FilePathDeliveryDisabled", disableFilePath: true, filePathDeliveryEnabled: false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dv := coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
|
|
dv.DisableUserSecretFilePath = serpent.Bool(tt.disableFilePath)
|
|
})
|
|
ownerClient := coderdtest.New(t, &coderdtest.Options{DeploymentValues: dv})
|
|
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
|
memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
capabilities, err := memberClient.UserSecretsCapabilities(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.filePathDeliveryEnabled, capabilities.FilePathDeliveryEnabled)
|
|
|
|
// The same member may not read the deployment configuration that
|
|
// holds the underlying option.
|
|
_, err = memberClient.DeploymentConfig(ctx)
|
|
var sdkErr *codersdk.Error
|
|
require.ErrorAs(t, err, &sdkErr)
|
|
assert.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUserSecretFilePathDisabledHandlers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
auditor := audit.NewMock()
|
|
dv := coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
|
|
dv.DisableUserSecretFilePath = true
|
|
})
|
|
db, ps := dbtestutil.NewDB(t)
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
Database: db, Pubsub: ps, DeploymentValues: dv, Auditor: auditor,
|
|
})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
_, err := client.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
Name: "file-secret", Value: "value", EnvName: "FILE_SECRET", FilePath: "/tmp/file-secret",
|
|
})
|
|
requireSecretValidationContainsError(t, err, http.StatusBadRequest, "file_path", "disabled")
|
|
|
|
legacy := dbgen.UserSecret(t, db, database.UserSecret{UserID: owner.UserID, Name: "legacy"},
|
|
func(p *database.CreateUserSecretParams) {
|
|
p.EnvName, p.FilePath, p.Enabled = "", "/tmp/legacy", true
|
|
})
|
|
|
|
description := "updated"
|
|
updated, err := client.UpdateUserSecret(ctx, codersdk.Me, legacy.Name, codersdk.UpdateUserSecretRequest{
|
|
Description: &description,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "/tmp/legacy", updated.FilePath)
|
|
assert.True(t, updated.Enabled)
|
|
|
|
empty := ""
|
|
_, err = client.UpdateUserSecret(ctx, codersdk.Me, legacy.Name, codersdk.UpdateUserSecretRequest{FilePath: &empty})
|
|
requireSecretValidationContainsError(t, err, http.StatusBadRequest, "env_name", "Add env_name")
|
|
|
|
// Changing the path of the still-enabled legacy row is rejected.
|
|
auditor.ResetLogs()
|
|
newPath := "/tmp/other"
|
|
_, err = client.UpdateUserSecret(ctx, codersdk.Me, legacy.Name, codersdk.UpdateUserSecretRequest{
|
|
FilePath: &newPath,
|
|
})
|
|
requireSecretValidationContainsError(t, err, http.StatusBadRequest, "file_path", "disabled")
|
|
|
|
logs := auditor.AuditLogs()
|
|
require.Len(t, logs, 1)
|
|
assert.Equal(t, database.AuditActionWrite, logs[0].Action)
|
|
assert.EqualValues(t, http.StatusBadRequest, logs[0].StatusCode)
|
|
assert.Equal(t, legacy.ID, logs[0].ResourceID)
|
|
assert.JSONEq(t, "{}", string(logs[0].Diff))
|
|
|
|
disabled := false
|
|
updated, err = client.UpdateUserSecret(ctx, codersdk.Me, legacy.Name, codersdk.UpdateUserSecretRequest{Enabled: &disabled})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "/tmp/legacy", updated.FilePath)
|
|
}
|