Added audit activities when secret variables are upserted
Added audit activities when secret variables are created or updated through the `PUT /api/latest/fleet/spec/secret_variables` endpoint.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Added audit activities when secret variables are created or updated through the `PUT /api/latest/fleet/spec/secret_variables` endpoint.
|
||||
@@ -7441,7 +7441,9 @@ software:
|
||||
t.Setenv("FLEET_SECRET_FOO", "someValue")
|
||||
// The base mock provides ValidateEmbeddedSecretsFunc (used by scripts/batch),
|
||||
// so leave it alone; only the secret-upload func needs a stub here.
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secretVariables []fleet.SecretVariable) error { return nil }
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secretVariables []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
ds.UpsertSecretVariablesFuncInvoked = false
|
||||
|
||||
yml := writeYAML(t, teamYAML(` name_template: "iPad $FLEET_SECRET_FOO"`))
|
||||
|
||||
@@ -2663,6 +2663,21 @@ This activity contains the following fields:
|
||||
}
|
||||
```
|
||||
|
||||
## updated_custom_variable
|
||||
|
||||
Generated when a custom variable's value is updated.
|
||||
|
||||
This activity contains the following fields:
|
||||
- "custom_variable_name": the name of the custom variable.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"custom_variable_name": "SOME_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
## deleted_custom_variable
|
||||
|
||||
Generated when custom variable is deleted.
|
||||
|
||||
@@ -160,6 +160,7 @@ export enum ActivityType {
|
||||
DisabledConditionalAccessAutomations = "disabled_conditional_access_automations",
|
||||
EscrowedDiskEncryptionKey = "escrowed_disk_encryption_key",
|
||||
CreatedCustomVariable = "created_custom_variable",
|
||||
UpdatedCustomVariable = "updated_custom_variable",
|
||||
DeletedCustomVariable = "deleted_custom_variable",
|
||||
EditedCustomHostVitalValue = "edited_custom_host_vital_value",
|
||||
EditedSetupExperienceSoftware = "edited_setup_experience_software",
|
||||
@@ -538,6 +539,7 @@ export const ACTIVITY_TYPE_TO_FILTER_LABEL: Record<ActivityType, string> = {
|
||||
"Deleted conditional access integration: Microsoft",
|
||||
escrowed_disk_encryption_key: "Escrowed disk encryption key",
|
||||
created_custom_variable: "Created custom variable",
|
||||
updated_custom_variable: "Updated custom variable",
|
||||
deleted_custom_variable: "Deleted custom variable",
|
||||
[ActivityType.EditedCustomHostVitalValue]: "Edited custom host vital value",
|
||||
[ActivityType.HostDeleted]: "Host deleted",
|
||||
|
||||
+12
@@ -2035,6 +2035,15 @@ const TAGGED_TEMPLATES = {
|
||||
);
|
||||
},
|
||||
|
||||
updatedCustomVariable: (activity: IActivity) => {
|
||||
const { custom_variable_name } = activity.details || {};
|
||||
return (
|
||||
<>
|
||||
updated custom variable <b>{custom_variable_name}</b>.
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
||||
deletedCustomVariable: (activity: IActivity) => {
|
||||
const { custom_variable_name } = activity.details || {};
|
||||
return (
|
||||
@@ -2650,6 +2659,9 @@ const getDetail = (activity: IActivity, isPremiumTier: boolean) => {
|
||||
case ActivityType.CreatedCustomVariable: {
|
||||
return TAGGED_TEMPLATES.createdCustomVariable(activity);
|
||||
}
|
||||
case ActivityType.UpdatedCustomVariable: {
|
||||
return TAGGED_TEMPLATES.updatedCustomVariable(activity);
|
||||
}
|
||||
case ActivityType.DeletedCustomVariable: {
|
||||
return TAGGED_TEMPLATES.deletedCustomVariable(activity);
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ var secretVariableAllowedOrderKeys = common_mysql.OrderKeyAllowlist{
|
||||
"updated_at": "updated_at",
|
||||
}
|
||||
|
||||
func (ds *Datastore) UpsertSecretVariables(ctx context.Context, secretVariables []fleet.SecretVariable) error {
|
||||
func (ds *Datastore) UpsertSecretVariables(ctx context.Context, secretVariables []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
if len(secretVariables) == 0 {
|
||||
return nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// The secret variables should rarely change, so we do not use a transaction here.
|
||||
@@ -44,7 +44,7 @@ func (ds *Datastore) UpsertSecretVariables(ctx context.Context, secretVariables
|
||||
}
|
||||
existingVariables, err := ds.GetSecretVariables(ctx, names)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get existing secret variables")
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "get existing secret variables")
|
||||
}
|
||||
existingVariableMap := make(map[string]string, len(existingVariables))
|
||||
for _, existingVariable := range existingVariables {
|
||||
@@ -73,12 +73,15 @@ func (ds *Datastore) UpsertSecretVariables(ctx context.Context, secretVariables
|
||||
for _, secretVariable := range variablesToInsert {
|
||||
valueEncrypted, err := encrypt([]byte(secretVariable.Value), ds.serverPrivateKey)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "encrypt secret value for insert with server private key")
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "encrypt secret value for insert with server private key")
|
||||
}
|
||||
args = append(args, secretVariable.Name, valueEncrypted)
|
||||
}
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, stmt, args...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "insert secret variables")
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "insert secret variables")
|
||||
}
|
||||
for _, secretVariable := range variablesToInsert {
|
||||
created = append(created, secretVariable.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +93,12 @@ func (ds *Datastore) UpsertSecretVariables(ctx context.Context, secretVariables
|
||||
for _, secretVariable := range variablesToUpdate {
|
||||
valueEncrypted, err := encrypt([]byte(secretVariable.Value), ds.serverPrivateKey)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "encrypt secret value for update with server private key")
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "encrypt secret value for update with server private key")
|
||||
}
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, stmt, valueEncrypted, secretVariable.Name); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "update secret variables")
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "update secret variables")
|
||||
}
|
||||
updated = append(updated, secretVariable.Name)
|
||||
}
|
||||
|
||||
// A changed secret value changes the resolved name of any host whose host
|
||||
@@ -104,11 +108,11 @@ func (ds *Datastore) UpsertSecretVariables(ctx context.Context, secretVariables
|
||||
changedNames = append(changedNames, secretVariable.Name)
|
||||
}
|
||||
if err := ds.resendDeviceNamesForSecretChange(ctx, changedNames); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "resend device names for secret change")
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "resend device names for secret change")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return created, updated, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) CreateSecretVariable(ctx context.Context, name string, value string) (id uint, err error) {
|
||||
|
||||
@@ -49,11 +49,13 @@ func TestSecretVariables(t *testing.T) {
|
||||
func testUpsertSecretVariables(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
err := ds.UpsertSecretVariables(ctx, nil)
|
||||
assert.NoError(t, err)
|
||||
createdNames, updatedNames, err := ds.UpsertSecretVariables(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, createdNames)
|
||||
require.Empty(t, updatedNames)
|
||||
results, err := ds.GetSecretVariables(ctx, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, results)
|
||||
|
||||
secretMap := map[string]string{
|
||||
"test1": "testValue1",
|
||||
@@ -68,43 +70,50 @@ func testUpsertSecretVariables(t *testing.T, ds *Datastore) {
|
||||
return secrets
|
||||
}
|
||||
secrets := createExpectedSecrets()
|
||||
err = ds.UpsertSecretVariables(ctx, secrets)
|
||||
assert.NoError(t, err)
|
||||
createdNames, updatedNames, err = ds.UpsertSecretVariables(ctx, secrets)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []string{"test1", "test2", "test3"}, createdNames)
|
||||
require.Empty(t, updatedNames)
|
||||
|
||||
results, err = ds.GetSecretVariables(ctx, []string{"test1", "test2", "test3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, results, 3)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 3)
|
||||
for _, result := range results {
|
||||
assert.Equal(t, secretMap[result.Name], result.Value)
|
||||
require.Equal(t, secretMap[result.Name], result.Value)
|
||||
}
|
||||
|
||||
// Update a secret and insert a new one
|
||||
secretMap["test2"] = "newTestValue2"
|
||||
secretMap["test4"] = "testValue4"
|
||||
err = ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{
|
||||
createdNames, updatedNames, err = ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{
|
||||
{Name: "test2", Value: secretMap["test2"]},
|
||||
{Name: "test4", Value: secretMap["test4"]},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []string{"test4"}, createdNames)
|
||||
require.ElementsMatch(t, []string{"test2"}, updatedNames)
|
||||
results, err = ds.GetSecretVariables(ctx, []string{"test2", "test4"})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
assert.Equal(t, secretMap[result.Name], result.Value)
|
||||
require.Equal(t, secretMap[result.Name], result.Value)
|
||||
}
|
||||
|
||||
// Make sure updated_at timestamp does not change when we update a secret with the same value
|
||||
// Make sure updated_at timestamp does not change when we update a secret with the same value,
|
||||
// and that an unchanged value produces neither a created nor an updated result.
|
||||
original, err := ds.GetSecretVariables(ctx, []string{"test1"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, original, 1)
|
||||
err = ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{
|
||||
createdNames, updatedNames, err = ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{
|
||||
{Name: "test1", Value: secretMap["test1"]},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, createdNames)
|
||||
require.Empty(t, updatedNames)
|
||||
updated, err := ds.GetSecretVariables(ctx, []string{"test1"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, original, 1)
|
||||
assert.Equal(t, original[0], updated[0])
|
||||
require.Len(t, updated, 1)
|
||||
require.Equal(t, original[0], updated[0])
|
||||
}
|
||||
|
||||
func testValidateEmbeddedSecrets(t *testing.T, ds *Datastore) {
|
||||
@@ -134,7 +143,7 @@ Hello doc${FLEET_SECRET_INVALID}. $FLEET_SECRET_ALSO_INVALID
|
||||
secrets = append(secrets, fleet.SecretVariable{Name: name, Value: value})
|
||||
}
|
||||
|
||||
err := ds.UpsertSecretVariables(ctx, secrets)
|
||||
_, _, err := ds.UpsertSecretVariables(ctx, secrets)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ds.ValidateEmbeddedSecrets(ctx, []string{noSecrets})
|
||||
@@ -193,7 +202,7 @@ Hello doc${FLEET_SECRET_INVALID}. $FLEET_SECRET_ALSO_INVALID
|
||||
secrets = append(secrets, fleet.SecretVariable{Name: name, Value: value})
|
||||
}
|
||||
|
||||
err := ds.UpsertSecretVariables(ctx, secrets)
|
||||
_, _, err := ds.UpsertSecretVariables(ctx, secrets)
|
||||
require.NoError(t, err)
|
||||
|
||||
expanded, err := ds.ExpandEmbeddedSecrets(ctx, noSecrets)
|
||||
|
||||
@@ -236,7 +236,7 @@ func testListPendingSoftwareInstalls(t *testing.T, ds *Datastore) {
|
||||
host3 := test.NewHost(t, ds, "host3", "3", "host3key", "host3uuid", time.Now())
|
||||
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
|
||||
|
||||
err := ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{
|
||||
_, _, err := ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{
|
||||
{
|
||||
Name: "RUBBER",
|
||||
Value: "DUCKY",
|
||||
|
||||
@@ -1855,6 +1855,14 @@ func (a ActivityCreatedCustomVariable) ActivityName() string {
|
||||
return "created_custom_variable"
|
||||
}
|
||||
|
||||
type ActivityUpdatedCustomVariable struct {
|
||||
CustomVariableName string `json:"custom_variable_name"`
|
||||
}
|
||||
|
||||
func (a ActivityUpdatedCustomVariable) ActivityName() string {
|
||||
return "updated_custom_variable"
|
||||
}
|
||||
|
||||
type ActivityDeletedCustomVariable struct {
|
||||
CustomVariableID uint `json:"custom_variable_id"`
|
||||
CustomVariableName string `json:"custom_variable_name"`
|
||||
|
||||
@@ -3248,7 +3248,9 @@ type Datastore interface {
|
||||
// Secret variables
|
||||
|
||||
// UpsertSecretVariables inserts or updates secret variables in the database.
|
||||
UpsertSecretVariables(ctx context.Context, secretVariables []SecretVariable) error
|
||||
// It returns the names of the variables that were created and the names of
|
||||
// those that were updated, so callers can emit the corresponding activities.
|
||||
UpsertSecretVariables(ctx context.Context, secretVariables []SecretVariable) (created []string, updated []string, err error)
|
||||
|
||||
// CreateSecretVariable inserts a secret variable (value encrypted) and returns its ID.
|
||||
// Returns an AlreadyExistsError error if there's already a secret variable with the same name.
|
||||
|
||||
@@ -1840,7 +1840,7 @@ type ListHostMDMManagedCertificatesFunc func(ctx context.Context, hostUUID strin
|
||||
|
||||
type ResendHostCertificateProfileFunc func(ctx context.Context, hostUUID string, profUUID string) error
|
||||
|
||||
type UpsertSecretVariablesFunc func(ctx context.Context, secretVariables []fleet.SecretVariable) error
|
||||
type UpsertSecretVariablesFunc func(ctx context.Context, secretVariables []fleet.SecretVariable) (created []string, updated []string, err error)
|
||||
|
||||
type CreateSecretVariableFunc func(ctx context.Context, name string, value string) (id uint, err error)
|
||||
|
||||
@@ -11894,7 +11894,7 @@ func (s *DataStore) ResendHostCertificateProfile(ctx context.Context, hostUUID s
|
||||
return s.ResendHostCertificateProfileFunc(ctx, hostUUID, profUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) UpsertSecretVariables(ctx context.Context, secretVariables []fleet.SecretVariable) error {
|
||||
func (s *DataStore) UpsertSecretVariables(ctx context.Context, secretVariables []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
s.mu.Lock()
|
||||
s.UpsertSecretVariablesFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
|
||||
@@ -15117,19 +15117,45 @@ func (s *integrationTestSuite) TestSecretVariablesGitOps() {
|
||||
}
|
||||
// Do dry run
|
||||
req.DryRun = true
|
||||
idBeforeDryRun := s.lastActivityMatches("", "", 0)
|
||||
s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &resp)
|
||||
|
||||
secrets, err := s.ds.GetSecretVariables(ctx, []string{validName})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, secrets)
|
||||
// A dry run persists nothing, so it must not emit any activity.
|
||||
require.Equal(t, idBeforeDryRun, s.lastActivityMatches("", "", 0))
|
||||
|
||||
// Do real run
|
||||
// Do real run: creating the variable emits a created_custom_variable activity.
|
||||
req.DryRun = false
|
||||
s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &resp)
|
||||
secrets, err = s.ds.GetSecretVariables(ctx, []string{validName})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, secrets, 1)
|
||||
assert.Equal(t, "value", secrets[0].Value)
|
||||
s.lastActivityMatches(
|
||||
fleet.ActivityCreatedCustomVariable{}.ActivityName(),
|
||||
fmt.Sprintf(`{"custom_variable_id":0,"custom_variable_name":%q}`, validName),
|
||||
0,
|
||||
)
|
||||
|
||||
// Re-applying the same spec is a no-op and must not emit any activity.
|
||||
idAfterCreate := s.lastActivityMatches("", "", 0)
|
||||
s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &resp)
|
||||
require.Equal(t, idAfterCreate, s.lastActivityMatches("", "", 0))
|
||||
|
||||
// Changing the value via the spec endpoint emits an updated_custom_variable activity.
|
||||
req.SecretVariables[0].Value = "new-value"
|
||||
s.DoJSON("PUT", "/api/latest/fleet/spec/secret_variables", req, http.StatusOK, &resp)
|
||||
secrets, err = s.ds.GetSecretVariables(ctx, []string{validName})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, secrets, 1)
|
||||
assert.Equal(t, "new-value", secrets[0].Value)
|
||||
s.lastActivityMatches(
|
||||
fleet.ActivityUpdatedCustomVariable{}.ActivityName(),
|
||||
fmt.Sprintf(`{"custom_variable_name":%q}`, validName),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestSecretVariables() {
|
||||
|
||||
@@ -26128,7 +26128,8 @@ func (s *integrationMDMTestSuite) TestHostNameTemplateEndToEnd() {
|
||||
requireRowStatus(macHost.UUID, &fleet.MDMDeliveryVerifying)
|
||||
|
||||
// --- changing the secret value re-enqueues a fresh command ---
|
||||
require.NoError(t, s.ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{{Name: "SITE", Value: "NYC"}}))
|
||||
_, _, err = s.ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{{Name: "SITE", Value: "NYC"}})
|
||||
require.NoError(t, err)
|
||||
// the secret change reset the enforcement row back to queued
|
||||
requireRowStatus(macHost.UUID, nil)
|
||||
runDeviceNameCron()
|
||||
@@ -26602,14 +26603,16 @@ func (s *integrationMDMTestSuite) TestHostNameTemplateSecretReenqueue() {
|
||||
requireRowStatus(noTeamHost.UUID, &fleet.MDMDeliveryVerifying)
|
||||
|
||||
// Re-upserting SITE with its current value changes nothing → no re-queue.
|
||||
require.NoError(t, s.ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{{Name: "SITE", Value: "HQ"}}))
|
||||
_, _, err = s.ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{{Name: "SITE", Value: "HQ"}})
|
||||
require.NoError(t, err)
|
||||
requireRowStatus(siteHost.UUID, &fleet.MDMDeliveryVerifying)
|
||||
requireRowStatus(noTeamHost.UUID, &fleet.MDMDeliveryVerifying)
|
||||
|
||||
// Changing SITE re-queues the SITE team and the No-team host, but not the
|
||||
// SITE_CODE team (the trailing word boundary prevents SITE from matching
|
||||
// SITE_CODE).
|
||||
require.NoError(t, s.ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{{Name: "SITE", Value: "NYC"}}))
|
||||
_, _, err = s.ds.UpsertSecretVariables(ctx, []fleet.SecretVariable{{Name: "SITE", Value: "NYC"}})
|
||||
require.NoError(t, err)
|
||||
requireRowStatus(siteHost.UUID, nil)
|
||||
requireRowStatus(noTeamHost.UUID, nil)
|
||||
requireRowStatus(codeHost.UUID, &fleet.MDMDeliveryVerifying)
|
||||
|
||||
@@ -56,9 +56,36 @@ func (svc *Service) CreateSecretVariables(ctx context.Context, secretVariables [
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := svc.ds.UpsertSecretVariables(ctx, secretVariables); err != nil {
|
||||
created, updated, err := svc.ds.UpsertSecretVariables(ctx, secretVariables)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "saving secret variables")
|
||||
}
|
||||
|
||||
// Emit an activity per created/updated variable so secret changes are
|
||||
// auditable.
|
||||
user := authz.UserFromContext(ctx)
|
||||
for _, name := range created {
|
||||
if err := svc.NewActivity(
|
||||
ctx,
|
||||
user,
|
||||
fleet.ActivityCreatedCustomVariable{
|
||||
CustomVariableName: name,
|
||||
},
|
||||
); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "create activity for secret variable creation")
|
||||
}
|
||||
}
|
||||
for _, name := range updated {
|
||||
if err := svc.NewActivity(
|
||||
ctx,
|
||||
user,
|
||||
fleet.ActivityUpdatedCustomVariable{
|
||||
CustomVariableName: name,
|
||||
},
|
||||
); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "create activity for secret variable update")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
activity_api "github.com/fleetdm/fleet/v4/server/activity/api"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateSecretVariables(t *testing.T) {
|
||||
@@ -17,8 +18,8 @@ func TestCreateSecretVariables(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc, ctx := newTestService(t, ds, nil, nil)
|
||||
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) error {
|
||||
return nil
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
t.Run("authorization checks", func(t *testing.T) {
|
||||
@@ -95,13 +96,75 @@ func TestCreateSecretVariables(t *testing.T) {
|
||||
testSetEmptyPrivateKey = false
|
||||
})
|
||||
err := svc.CreateSecretVariables(ctx, []fleet.SecretVariable{{Name: "foo", Value: "bar"}}, true)
|
||||
assert.ErrorContains(t, err, "Couldn't save secret variables. Missing required private key")
|
||||
require.ErrorContains(t, err, "Couldn't save secret variables. Missing required private key")
|
||||
testSetEmptyPrivateKey = false
|
||||
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) error {
|
||||
return errors.New("test error")
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
return nil, nil, errors.New("test error")
|
||||
}
|
||||
err = svc.CreateSecretVariables(ctx, []fleet.SecretVariable{{Name: "FOO", Value: "bar"}}, false)
|
||||
assert.ErrorContains(t, err, "test error")
|
||||
require.ErrorContains(t, err, "test error")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateSecretVariablesEmitsActivities(t *testing.T) {
|
||||
t.Parallel()
|
||||
ds := new(mock.Store)
|
||||
opts := &TestServerOpts{}
|
||||
svc, ctx := newTestService(t, ds, nil, nil, opts)
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{ID: 1, GlobalRole: new(fleet.RoleAdmin)}})
|
||||
|
||||
t.Run("emits a created activity per created variable and an updated activity per updated variable", func(t *testing.T) {
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
return []string{"CREATED"}, []string{"UPDATED"}, nil
|
||||
}
|
||||
var activities []activity_api.ActivityDetails
|
||||
opts.ActivityMock.NewActivityFunc = func(_ context.Context, _ *activity_api.User, activity activity_api.ActivityDetails) error {
|
||||
activities = append(activities, activity)
|
||||
return nil
|
||||
}
|
||||
err := svc.CreateSecretVariables(ctx, []fleet.SecretVariable{
|
||||
{Name: "FLEET_SECRET_CREATED", Value: "a"},
|
||||
{Name: "FLEET_SECRET_UPDATED", Value: "b"},
|
||||
}, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 2)
|
||||
|
||||
createdActivity, ok := activities[0].(fleet.ActivityCreatedCustomVariable)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "CREATED", createdActivity.CustomVariableName)
|
||||
|
||||
updatedActivity, ok := activities[1].(fleet.ActivityUpdatedCustomVariable)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "UPDATED", updatedActivity.CustomVariableName)
|
||||
})
|
||||
|
||||
t.Run("emits no activity when nothing changed", func(t *testing.T) {
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
activityCalled := false
|
||||
opts.ActivityMock.NewActivityFunc = func(_ context.Context, _ *activity_api.User, _ activity_api.ActivityDetails) error {
|
||||
activityCalled = true
|
||||
return nil
|
||||
}
|
||||
err := svc.CreateSecretVariables(ctx, []fleet.SecretVariable{{Name: "FLEET_SECRET_UNCHANGED", Value: "a"}}, false)
|
||||
require.NoError(t, err)
|
||||
require.False(t, activityCalled)
|
||||
})
|
||||
|
||||
t.Run("emits no activity on a dry run", func(t *testing.T) {
|
||||
ds.UpsertSecretVariablesFunc = func(ctx context.Context, secrets []fleet.SecretVariable) (created []string, updated []string, err error) {
|
||||
t.Fatal("UpsertSecretVariables should not be called on a dry run")
|
||||
return nil, nil, nil
|
||||
}
|
||||
activityCalled := false
|
||||
opts.ActivityMock.NewActivityFunc = func(_ context.Context, _ *activity_api.User, _ activity_api.ActivityDetails) error {
|
||||
activityCalled = true
|
||||
return nil
|
||||
}
|
||||
err := svc.CreateSecretVariables(ctx, []fleet.SecretVariable{{Name: "FLEET_SECRET_DRY", Value: "a"}}, true)
|
||||
require.NoError(t, err)
|
||||
require.False(t, activityCalled)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user