return 422 status code if fleetdm.com returns any 4xx status for CSR (#9610)

Related to https://github.com/fleetdm/fleet/issues/9588, we now handle 4xx responses from the fleetdm.com server and forward those to the client.

At the time of this commit, the only 4xx response that wasn't already handled by the server is because of an invalid email domain, so we assume that, but we should look into establishing a pattern of error messages with the website instead.
This commit is contained in:
Roberto Dip 2023-02-01 12:50:22 -03:00 committed by GitHub
parent f47b7f538f
commit ffed7f8ebe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 21 deletions

View file

@ -60,7 +60,7 @@ func TestGenerateMDMApple(t *testing.T) {
"--email", "user@example.com",
"--org", "Acme",
},
`POST /api/latest/fleet/mdm/apple/request_csr received status 502 Bad Gateway: FleetDM CSR request failed: api responded with 400: bad request`,
`POST /api/latest/fleet/mdm/apple/request_csr received status 422 Validation Failed: this email address is not valid: bad request`,
)
})

View file

@ -60,6 +60,19 @@ func GenerateAPNSCSRKey(email, org string) (*x509.CertificateRequest, *rsa.Priva
return certReq, key, nil
}
type FleetWebsiteError struct {
Status int
message string
}
func (e FleetWebsiteError) Error() string {
if e.message != "" {
return e.message
}
return "Unknown Error"
}
type getSignedAPNSCSRRequest struct {
UnsignedCSRData []byte `json:"unsignedCsrData"`
}
@ -98,7 +111,7 @@ func GetSignedAPNSCSR(client *http.Client, csr *x509.CertificateRequest) error {
if resp.StatusCode != http.StatusOK {
b, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("api responded with %d: %s", resp.StatusCode, string(b))
return FleetWebsiteError{Status: resp.StatusCode, message: string(b)}
}
return nil
}

View file

@ -59,11 +59,11 @@ func TestIntegrationsMDM(t *testing.T) {
type integrationMDMTestSuite struct {
suite.Suite
withServer
fleetCfg config.FleetConfig
fleetDMFailCSR atomic.Bool
pushProvider *mock.APNSPushProvider
depStorage nanodep_storage.AllStorage
depSchedule *schedule.Schedule
fleetCfg config.FleetConfig
fleetDMNextCSRStatus atomic.Value
pushProvider *mock.APNSPushProvider
depStorage nanodep_storage.AllStorage
depSchedule *schedule.Schedule
}
func (s *integrationMDMTestSuite) SetupSuite() {
@ -131,25 +131,20 @@ func (s *integrationMDMTestSuite) SetupSuite() {
s.depSchedule = depSchedule
fleetdmSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if s.fleetDMFailCSR.Swap(false) {
// fail this call
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("bad request"))
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
status := s.fleetDMNextCSRStatus.Swap(http.StatusOK)
w.WriteHeader(status.(int))
_, _ = w.Write([]byte(fmt.Sprintf("status: %d", status)))
}))
s.T().Setenv("TEST_FLEETDM_API_URL", fleetdmSrv.URL)
s.T().Cleanup(fleetdmSrv.Close)
}
func (s *integrationMDMTestSuite) FailNextCSRRequest() {
s.fleetDMFailCSR.Store(true)
func (s *integrationMDMTestSuite) FailNextCSRRequestWith(status int) {
s.fleetDMNextCSRStatus.Store(status)
}
func (s *integrationMDMTestSuite) SucceedNextCSRRequest() {
s.fleetDMFailCSR.Store(false)
s.fleetDMNextCSRStatus.Store(http.StatusOK)
}
func (s *integrationMDMTestSuite) TearDownTest() {
@ -517,7 +512,13 @@ func (s *integrationMDMTestSuite) TestAppleMDMCSRRequest() {
require.Equal(t, errResp.Errors[0].Name, "organization")
// fleetdm CSR request failed
s.FailNextCSRRequest()
s.FailNextCSRRequestWith(http.StatusBadRequest)
errResp = validationErrResp{}
s.DoJSON("POST", "/api/latest/fleet/mdm/apple/request_csr", requestMDMAppleCSRRequest{EmailAddress: "a@b.c", Organization: "test"}, http.StatusUnprocessableEntity, &errResp)
require.Len(t, errResp.Errors, 1)
require.Contains(t, errResp.Errors[0].Reason, "this email address is not valid")
s.FailNextCSRRequestWith(http.StatusInternalServerError)
errResp = validationErrResp{}
s.DoJSON("POST", "/api/latest/fleet/mdm/apple/request_csr", requestMDMAppleCSRRequest{EmailAddress: "a@b.c", Organization: "test"}, http.StatusBadGateway, &errResp)
require.Len(t, errResp.Errors, 1)

View file

@ -148,8 +148,36 @@ func (svc *Service) RequestMDMAppleCSR(ctx context.Context, email, org string) (
// request the signed APNs CSR from fleetdm.com
client := fleethttp.NewClient(fleethttp.WithTimeout(10 * time.Second))
if err := apple_mdm.GetSignedAPNSCSR(client, apnsCSR); err != nil {
err = fleet.NewUserMessageError(fmt.Errorf("FleetDM CSR request failed: %w", err), http.StatusBadGateway)
return nil, ctxerr.Wrap(ctx, err)
if ferr, ok := err.(apple_mdm.FleetWebsiteError); ok {
status := http.StatusBadGateway
if ferr.Status >= 400 && ferr.Status <= 499 {
// TODO: fleetdm.com returns a genereric "Bad
// Request" message, we should coordinate and
// stablish a response schema from which we can get
// the invalid field and use
// fleet.NewInvalidArgumentError instead
//
// For now, since we have already validated
// everything else, we assume that a 4xx
// response is an email with an invalid domain
return nil, ctxerr.Wrap(
ctx,
fleet.NewInvalidArgumentError(
"email_address",
fmt.Sprintf("this email address is not valid: %v", err),
),
)
}
return nil, ctxerr.Wrap(
ctx,
fleet.NewUserMessageError(
fmt.Errorf("FleetDM CSR request failed: %w", err),
status,
),
)
}
return nil, ctxerr.Wrap(ctx, err, "get signed CSR")
}
// PEM-encode the cert and keys