diff --git a/changes/19464-private-key-errors b/changes/19464-private-key-errors new file mode 100644 index 0000000000..ddd0fd2f65 --- /dev/null +++ b/changes/19464-private-key-errors @@ -0,0 +1 @@ +- Adds clearer error messages when attempting to set up Apple MDM without a server private key configured. \ No newline at end of file diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MacOSMdmPage/components/content/ApplePushCertSetup.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MacOSMdmPage/components/content/ApplePushCertSetup.tsx index 8eb0ea88d5..272c74f75c 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MacOSMdmPage/components/content/ApplePushCertSetup.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MacOSMdmPage/components/content/ApplePushCertSetup.tsx @@ -32,7 +32,10 @@ const ApplePushCertSetup = ({ onSetupSuccess(); } catch (e) { const msg = getErrorReason(e); - if (msg.toLowerCase().includes("invalid certificate")) { + if ( + msg.toLowerCase().includes("invalid certificate") || + msg.toLowerCase().includes("required private key") + ) { renderFlash("error", msg); } else { renderFlash("error", "Couldn’t connect. Please try again."); @@ -46,7 +49,10 @@ const ApplePushCertSetup = ({ const onDownloadError = useCallback( (e: unknown) => { const msg = getErrorReason(e); - if (msg.toLowerCase().includes("email address")) { + if ( + msg.toLowerCase().includes("email address") || + msg.toLowerCase().includes("required private key") + ) { renderFlash("error", msg); } else { renderFlash("error", "Something’s gone wrong. Please try again."); diff --git a/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx b/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx index 70df97ea3e..891ea45a58 100644 --- a/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx +++ b/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx @@ -1,6 +1,14 @@ -import React, { FormEvent, useCallback, useMemo, useState } from "react"; +import React, { + FormEvent, + useCallback, + useMemo, + useState, + useContext, +} from "react"; import mdmAppleBusinessManagerApi from "services/entities/mdm_apple_bm"; +import { NotificationContext } from "context/notification"; +import { getErrorReason } from "interfaces/errors"; import Icon from "components/Icon"; import Button from "components/buttons/Button"; @@ -23,6 +31,7 @@ const useDownloadABMKey = ({ onError, }: Omit) => { const [downloadState, setDownloadState] = useState(undefined); + const { renderFlash } = useContext(NotificationContext); const handleDownload = useCallback( async (evt: FormEvent) => { @@ -34,6 +43,8 @@ const useDownloadABMKey = ({ setDownloadState("success"); onSuccess && onSuccess(); } catch (e) { + const msg = getErrorReason(e); + renderFlash("error", msg); setDownloadState("error"); onError && onError(e); } diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index aaf2a4a505..9799f6e7e0 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -3592,6 +3592,16 @@ func (svc *Service) GenerateABMKeyPair(ctx context.Context) (*fleet.MDMAppleDEPK if err := svc.authz.Authorize(ctx, &fleet.AppleBM{}, fleet.ActionWrite); err != nil { return nil, err } + + privateKey := svc.config.Server.PrivateKey + if testSetEmptyPrivateKey { + privateKey = "" + } + + if len(privateKey) == 0 { + return nil, ctxerr.New(ctx, "Couldn't download public key. Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + } + var publicKeyPEM, privateKeyPEM []byte assets, err := svc.ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{ fleet.MDMAssetABMCert, diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 512bef6e76..4463ab2cda 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -936,6 +936,15 @@ func (s *integrationMDMTestSuite) TestGetMDMCSR() { t := s.T() ctx := context.Background() + // Validate errors if no private key is set + testSetEmptyPrivateKey = true + t.Cleanup(func() { testSetEmptyPrivateKey = false }) + s.uploadAPNSCert([]byte("-----BEGIN CERTIFICATE-----\nZm9vCg==\n-----END CERTIFICATE-----"), http.StatusInternalServerError, "Couldn't upload APNs certificate. Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + + r := s.Do("GET", "/api/latest/fleet/mdm/apple/request_csr", getMDMAppleCSRRequest{}, http.StatusInternalServerError) + require.Contains(t, extractServerErrorText(r.Body), "Couldn't download signed CSR. Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + testSetEmptyPrivateKey = false + // ensure we leave everything in a clean state for other tests t.Cleanup(s.appleCoreCertsSetup) @@ -8674,6 +8683,14 @@ func (s *integrationMDMTestSuite) TestABMAssetManagement() { // ensure enable ABM again for other tests t.Cleanup(s.enableABM) + // Validate error when server private key not set + testSetEmptyPrivateKey = true + t.Cleanup(func() { testSetEmptyPrivateKey = false }) + + r := s.Do("GET", "/api/latest/fleet/mdm/apple/abm_public_key", generateABMKeyPairResponse{}, http.StatusInternalServerError) + require.Contains(t, extractServerErrorText(r.Body), "Couldn't download public key. Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + testSetEmptyPrivateKey = false + // grab the current public key var abmResp generateABMKeyPairResponse s.DoJSON("GET", "/api/latest/fleet/mdm/apple/abm_public_key", nil, http.StatusOK, &abmResp) diff --git a/server/service/mdm.go b/server/service/mdm.go index b631f6818d..069a5ecf1d 100644 --- a/server/service/mdm.go +++ b/server/service/mdm.go @@ -2120,6 +2120,9 @@ func (svc *Service) ResendHostMDMProfile(ctx context.Context, hostID uint, profi // GET /mdm/apple/request_csr //////////////////////////////////////////////////////////////////////////////// +// Used for overriding the env var value in testing +var testSetEmptyPrivateKey bool + type getMDMAppleCSRRequest struct{} type getMDMAppleCSRResponse struct { @@ -2143,8 +2146,13 @@ func (svc *Service) GetMDMAppleCSR(ctx context.Context) ([]byte, error) { return nil, err } - if len(svc.config.Server.PrivateKey) == 0 { - return nil, ctxerr.New(ctx, "no private key configured") + privateKey := svc.config.Server.PrivateKey + if testSetEmptyPrivateKey { + privateKey = "" + } + + if len(privateKey) == 0 { + return nil, ctxerr.New(ctx, "Couldn't download signed CSR. Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") } vc, ok := viewer.FromContext(ctx) @@ -2298,6 +2306,15 @@ func (svc *Service) UploadMDMAppleAPNSCert(ctx context.Context, cert io.ReadSeek return err } + privateKey := svc.config.Server.PrivateKey + if testSetEmptyPrivateKey { + privateKey = "" + } + + if len(privateKey) == 0 { + return ctxerr.New(ctx, "Couldn't upload APNs certificate. Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + } + if cert == nil { return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("certificate", "Invalid certificate. Please provide a valid certificate from Apple Push Certificate Portal.")) }