Skip to content

Commit e7458b9

Browse files
authored
Improve secret variables error on software upload. (#25052)
1 parent bd51e85 commit e7458b9

File tree

5 files changed

+134
-17
lines changed

5 files changed

+134
-17
lines changed

ee/server/service/maintained_apps.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@ func (svc *Service) AddFleetMaintainedApp(
4444
}
4545

4646
if err := svc.ds.ValidateEmbeddedSecrets(ctx, []string{installScript, postInstallScript, uninstallScript}); err != nil {
47-
return 0, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("script", err.Error()))
47+
// We redo the validation on each script to find out which script has the missing secret.
48+
// This is done to provide a more informative error message to the UI user.
49+
var argErr *fleet.InvalidArgumentError
50+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "install script", &installScript, argErr)
51+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "post-install script", &postInstallScript, argErr)
52+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "uninstall script", &uninstallScript, argErr)
53+
if argErr != nil {
54+
return 0, argErr
55+
}
56+
// We should not get to this point. If we did, it means we have another issue, such as large read replica latency.
57+
return 0, ctxerr.Wrap(ctx, err, "transient server issue validating embedded secrets")
4858
}
4959

5060
app, err := svc.ds.GetMaintainedAppByID(ctx, appID)

ee/server/service/software_installers.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,17 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
9696
preProcessUninstallScript(payload)
9797

9898
if err := svc.ds.ValidateEmbeddedSecrets(ctx, []string{payload.InstallScript, payload.PostInstallScript, payload.UninstallScript}); err != nil {
99-
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("script", err.Error()))
99+
// We redo the validation on each script to find out which script has the missing secret.
100+
// This is done to provide a more informative error message to the UI user.
101+
var argErr *fleet.InvalidArgumentError
102+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "install script", &payload.InstallScript, argErr)
103+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "post-install script", &payload.PostInstallScript, argErr)
104+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "uninstall script", &payload.UninstallScript, argErr)
105+
if argErr != nil {
106+
return argErr
107+
}
108+
// We should not get to this point. If we did, it means we have another issue, such as large read replica latency.
109+
return ctxerr.Wrap(ctx, err, "transient server issue validating embedded secrets")
100110
}
101111

102112
installerID, titleID, err := svc.ds.MatchOrCreateSoftwareInstaller(ctx, payload)
@@ -228,7 +238,17 @@ func (svc *Service) UpdateSoftwareInstaller(ctx context.Context, payload *fleet.
228238
}
229239

230240
if err := svc.ds.ValidateEmbeddedSecrets(ctx, scripts); err != nil {
231-
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("script", err.Error()))
241+
// We redo the validation on each script to find out which script has the missing secret.
242+
// This is done to provide a more informative error message to the UI user.
243+
var argErr *fleet.InvalidArgumentError
244+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "install script", payload.InstallScript, argErr)
245+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "post-install script", payload.PostInstallScript, argErr)
246+
argErr = svc.validateEmbeddedSecretsOnScript(ctx, "uninstall script", payload.UninstallScript, argErr)
247+
if argErr != nil {
248+
return nil, argErr
249+
}
250+
// We should not get to this point. If we did, it means we have another issue, such as large read replica latency.
251+
return nil, ctxerr.Wrap(ctx, err, "transient server issue validating embedded secrets")
232252
}
233253

234254
// get software by ID, fail if it does not exist or does not have an existing installer
@@ -463,6 +483,20 @@ func (svc *Service) UpdateSoftwareInstaller(ctx context.Context, payload *fleet.
463483
return updatedInstaller, nil
464484
}
465485

486+
func (svc *Service) validateEmbeddedSecretsOnScript(ctx context.Context, scriptName string, script *string,
487+
argErr *fleet.InvalidArgumentError) *fleet.InvalidArgumentError {
488+
if script != nil {
489+
if errScript := svc.ds.ValidateEmbeddedSecrets(ctx, []string{*script}); errScript != nil {
490+
if argErr != nil {
491+
argErr.Append(scriptName, errScript.Error())
492+
} else {
493+
argErr = fleet.NewInvalidArgumentError(scriptName, errScript.Error())
494+
}
495+
}
496+
}
497+
return argErr
498+
}
499+
466500
func ValidateSoftwareLabelsForUpdate(ctx context.Context, svc fleet.Service, existingInstaller *fleet.SoftwareInstaller, includeAny, excludeAny []string) (shouldUpdate bool, validatedLabels *fleet.LabelIdentsWithScope, err error) {
467501
if authctx, ok := authz_ctx.FromContext(ctx); !ok {
468502
return false, nil, fleet.NewAuthRequiredError("batch validate labels: missing authorization context")

server/service/base_client_errors.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,40 @@ type serverError struct {
104104
}
105105

106106
func extractServerErrorText(body io.Reader) string {
107+
_, reason := extractServerErrorNameReason(body)
108+
return reason
109+
}
110+
111+
func extractServerErrorNameReason(body io.Reader) (string, string) {
107112
var serverErr serverError
108113
if err := json.NewDecoder(body).Decode(&serverErr); err != nil {
109-
return "unknown"
114+
return "", "unknown"
110115
}
111116

112-
errText := serverErr.Message
117+
errName := ""
118+
errReason := serverErr.Message
113119
if len(serverErr.Errors) > 0 {
114-
errText += ": " + serverErr.Errors[0].Reason
120+
errReason += ": " + serverErr.Errors[0].Reason
121+
errName = serverErr.Errors[0].Name
122+
}
123+
124+
return errName, errReason
125+
}
126+
127+
func extractServerErrorNameReasons(body io.Reader) ([]string, []string) {
128+
var serverErr serverError
129+
if err := json.NewDecoder(body).Decode(&serverErr); err != nil {
130+
return []string{""}, []string{"unknown"}
131+
}
132+
133+
var errName []string
134+
var errReason []string
135+
for _, err := range serverErr.Errors {
136+
errName = append(errName, err.Name)
137+
errReason = append(errReason, err.Reason)
115138
}
116139

117-
return errText
140+
return errName, errReason
118141
}
119142

120143
type statusCodeErr struct {

server/service/integration_enterprise_test.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8930,15 +8930,17 @@ func (s *integrationEnterpriseTestSuite) TestAllSoftwareTitles() {
89308930
PostInstallScript: "d",
89318931
SelfService: true,
89328932
}
8933-
s.uploadSoftwareInstaller(t, payloadEmacsMissingSecret, http.StatusUnprocessableEntity, "$FLEET_SECRET_INVALID")
8933+
s.uploadSoftwareInstallerWithErrorNameReason(t, payloadEmacsMissingSecret, http.StatusUnprocessableEntity, "$FLEET_SECRET_INVALID",
8934+
"install script")
89348935

89358936
payloadEmacsMissingPostSecret := &fleet.UploadSoftwareInstallerPayload{
89368937
InstallScript: "install",
89378938
Filename: "emacs.deb",
89388939
PostInstallScript: "d $FLEET_SECRET_INVALID",
89398940
SelfService: true,
89408941
}
8941-
s.uploadSoftwareInstaller(t, payloadEmacsMissingPostSecret, http.StatusUnprocessableEntity, "$FLEET_SECRET_INVALID")
8942+
s.uploadSoftwareInstallerWithErrorNameReason(t, payloadEmacsMissingPostSecret, http.StatusUnprocessableEntity, "$FLEET_SECRET_INVALID",
8943+
"post-install script")
89428944

89438945
payloadEmacsMissingUnSecret := &fleet.UploadSoftwareInstallerPayload{
89448946
InstallScript: "install",
@@ -8947,7 +8949,8 @@ func (s *integrationEnterpriseTestSuite) TestAllSoftwareTitles() {
89478949
UninstallScript: "delet $FLEET_SECRET_INVALID",
89488950
SelfService: true,
89498951
}
8950-
s.uploadSoftwareInstaller(t, payloadEmacsMissingUnSecret, http.StatusUnprocessableEntity, "$FLEET_SECRET_INVALID")
8952+
s.uploadSoftwareInstallerWithErrorNameReason(t, payloadEmacsMissingUnSecret, http.StatusUnprocessableEntity, "$FLEET_SECRET_INVALID",
8953+
"uninstall script")
89518954

89528955
payloadEmacs := &fleet.UploadSoftwareInstallerPayload{
89538956
InstallScript: "install",
@@ -15943,10 +15946,42 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
1594315946
UninstallScript: "echo $FLEET_SECRET_INVALID3",
1594415947
}
1594515948
respBadSecret := s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", reqInvalidSecret, http.StatusUnprocessableEntity)
15946-
errMsg := extractServerErrorText(respBadSecret.Body)
15947-
require.Contains(t, errMsg, "$FLEET_SECRET_INVALID1")
15948-
require.Contains(t, errMsg, "$FLEET_SECRET_INVALID2")
15949-
require.Contains(t, errMsg, "$FLEET_SECRET_INVALID3")
15949+
errNames, errReasons := extractServerErrorNameReasons(respBadSecret.Body)
15950+
assert.ElementsMatch(t, []string{"install script", "post-install script", "uninstall script"}, errNames)
15951+
assert.Len(t, errReasons, 3)
15952+
for _, reason := range errReasons {
15953+
assert.Contains(t, reason, "$FLEET_SECRET_INVALID")
15954+
}
15955+
15956+
reqInvalidSecret = &addFleetMaintainedAppRequest{
15957+
AppID: 1,
15958+
TeamID: &team.ID,
15959+
SelfService: true,
15960+
PreInstallQuery: "SELECT 1",
15961+
InstallScript: "echo foo",
15962+
PostInstallScript: "echo done $FLEET_SECRET_INVALID2",
15963+
UninstallScript: "echo",
15964+
}
15965+
respBadSecret = s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", reqInvalidSecret, http.StatusUnprocessableEntity)
15966+
errNames, errReasons = extractServerErrorNameReasons(respBadSecret.Body)
15967+
assert.ElementsMatch(t, []string{"post-install script"}, errNames)
15968+
require.Len(t, errReasons, 1)
15969+
assert.Contains(t, errReasons[0], "$FLEET_SECRET_INVALID2")
15970+
15971+
reqInvalidSecret = &addFleetMaintainedAppRequest{
15972+
AppID: 1,
15973+
TeamID: &team.ID,
15974+
SelfService: true,
15975+
PreInstallQuery: "SELECT 1",
15976+
InstallScript: "echo foo",
15977+
PostInstallScript: "echo done",
15978+
UninstallScript: "echo $FLEET_SECRET_INVALID3",
15979+
}
15980+
respBadSecret = s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", reqInvalidSecret, http.StatusUnprocessableEntity)
15981+
errNames, errReasons = extractServerErrorNameReasons(respBadSecret.Body)
15982+
assert.ElementsMatch(t, []string{"uninstall script"}, errNames)
15983+
require.Len(t, errReasons, 1)
15984+
assert.Contains(t, errReasons[0], "$FLEET_SECRET_INVALID3")
1595015985

1595115986
// Add an ingested app to the team
1595215987
var addMAResp addFleetMaintainedAppResponse

server/service/testing_client.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,16 @@ func (ts *withServer) uploadSoftwareInstaller(
557557
payload *fleet.UploadSoftwareInstallerPayload,
558558
expectedStatus int,
559559
expectedError string,
560+
) {
561+
ts.uploadSoftwareInstallerWithErrorNameReason(t, payload, expectedStatus, expectedError, "")
562+
}
563+
564+
func (ts *withServer) uploadSoftwareInstallerWithErrorNameReason(
565+
t *testing.T,
566+
payload *fleet.UploadSoftwareInstallerPayload,
567+
expectedStatus int,
568+
expectedErrorReason string,
569+
expectedErrorName string,
560570
) {
561571
t.Helper()
562572

@@ -621,9 +631,14 @@ func (ts *withServer) uploadSoftwareInstaller(
621631
r := ts.DoRawWithHeaders("POST", "/api/latest/fleet/software/package", b.Bytes(), expectedStatus, headers)
622632
defer r.Body.Close()
623633

624-
if expectedError != "" {
625-
errMsg := extractServerErrorText(r.Body)
626-
require.Contains(t, errMsg, expectedError)
634+
if expectedErrorReason != "" || expectedErrorName != "" {
635+
errName, errReason := extractServerErrorNameReason(r.Body)
636+
if expectedErrorName != "" {
637+
require.Equal(t, expectedErrorName, errName)
638+
}
639+
if expectedErrorReason != "" {
640+
require.Contains(t, errReason, expectedErrorReason)
641+
}
627642
}
628643
}
629644

0 commit comments

Comments
 (0)