diff --git a/changes/issue-11416-return-400-for-auth-error b/changes/issue-11416-return-400-for-auth-error new file mode 100644 index 0000000000..32610498f9 --- /dev/null +++ b/changes/issue-11416-return-400-for-auth-error @@ -0,0 +1 @@ +* Updated the `/api/latest/fleet/mdm/apple_bm` to return 400 instead of 500 when it fails to authenticate with Apple's Business Manager API, as this indicates a Fleet configuration issue with the Apple BM certificate or token. diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go index f3c96ec880..733ac03426 100644 --- a/ee/server/service/mdm.go +++ b/ee/server/service/mdm.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "io" + "net/http" "net/url" "strings" @@ -23,6 +24,7 @@ import ( kitlog "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/google/uuid" + depclient "github.com/micromdm/nanodep/client" "github.com/micromdm/nanodep/storage" ) @@ -66,6 +68,20 @@ func getAppleBMAccountDetail(ctx context.Context, depStorage storage.AllStorage, depClient := apple_mdm.NewDEPClient(depStorage, ds, logger) res, err := depClient.AccountDetail(ctx, apple_mdm.DEPName) if err != nil { + var authErr *depclient.AuthError + if errors.As(err, &authErr) { + // authentication failure with 401 unauthorized means that the configured + // Apple BM certificate and/or token are invalid. Fail with a 400 Bad + // Request. + msg := err.Error() + if authErr.StatusCode == http.StatusUnauthorized { + msg = "The Apple Business Manager certificate or server token is invalid. Restart Fleet with a valid certificate and token. See https://fleetdm.com/docs/using-fleet/mdm-setup#apple-business-manager-abm for help." + } + return nil, ctxerr.Wrap(ctx, &fleet.BadRequestError{ + Message: msg, + InternalErr: err, + }, "apple GET /account request failed with authentication error") + } return nil, ctxerr.Wrap(ctx, err, "apple GET /account request failed") } diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 3b23d8f92b..167dbbe347 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -301,19 +301,50 @@ func (s *integrationMDMTestSuite) TestAppleGetAppleMDM() { func (s *integrationMDMTestSuite) TestABMExpiredToken() { t := s.T() + var returnType string s.mockDEPResponse(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusForbidden) - _, _ = w.Write([]byte(`{"code": "T_C_NOT_SIGNED"}`)) + switch returnType { + case "not_signed": + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte(`{"code": "T_C_NOT_SIGNED"}`)) + case "unauthorized": + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{}`)) + case "success": + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"auth_session_token": "abcd"}`)) + default: + require.Fail(t, "unexpected return type: %s", returnType) + } })) config := s.getConfig() require.False(t, config.MDM.AppleBMTermsExpired) - var getAppleBMResp getAppleBMResponse - s.DoJSON("GET", "/api/latest/fleet/mdm/apple_bm", nil, http.StatusInternalServerError, &getAppleBMResp) + // not signed error flips the AppleBMTermsExpired flag + returnType = "not_signed" + res := s.DoRaw("GET", "/api/latest/fleet/mdm/apple_bm", nil, http.StatusBadRequest) + errMsg := extractServerErrorText(res.Body) + require.Contains(t, errMsg, "DEP auth error: 403 Forbidden") config = s.getConfig() require.True(t, config.MDM.AppleBMTermsExpired) + + // a successful call clears it + returnType = "success" + s.DoRaw("GET", "/api/latest/fleet/mdm/apple_bm", nil, http.StatusOK) + + config = s.getConfig() + require.False(t, config.MDM.AppleBMTermsExpired) + + // an unauthorized call returns 400 but does not flip the terms expired flag + returnType = "unauthorized" + res = s.DoRaw("GET", "/api/latest/fleet/mdm/apple_bm", nil, http.StatusBadRequest) + errMsg = extractServerErrorText(res.Body) + require.Contains(t, errMsg, "Apple Business Manager certificate or server token is invalid") + + config = s.getConfig() + require.False(t, config.MDM.AppleBMTermsExpired) } func (s *integrationMDMTestSuite) TestProfileManagement() {