diff --git a/changes/41532-ca-delete-error-message b/changes/41532-ca-delete-error-message new file mode 100644 index 0000000000..fcdd4477ed --- /dev/null +++ b/changes/41532-ca-delete-error-message @@ -0,0 +1 @@ +* Fixed error message when deleting a certificate authority (that is referenced by a certificate template) to show a helpful message instead of a raw database error. diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx index 144b678a48..830ab8d350 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx @@ -1,6 +1,7 @@ import React, { useContext, useState } from "react"; import { ICertificateAuthorityPartial } from "interfaces/certificates"; +import { getErrorReason } from "interfaces/errors"; import certificatesAPI from "services/entities/certificates"; import { NotificationContext } from "context/notification"; @@ -33,9 +34,11 @@ const DeleteCertificateAuthorityModal = ({ onExit(); } catch (e) { setIsUpdating(false); + const status = (e as { status?: number })?.status; + const reason = status === 409 ? getErrorReason(e) : ""; renderFlash( "error", - "Couldn't delete certificate authority. Please try again." + reason || "Couldn't delete certificate authority. Please try again." ); } }; diff --git a/server/datastore/mysql/certificate_authorities.go b/server/datastore/mysql/certificate_authorities.go index 0d12f02475..7056f5f19b 100644 --- a/server/datastore/mysql/certificate_authorities.go +++ b/server/datastore/mysql/certificate_authorities.go @@ -394,6 +394,11 @@ func (ds *Datastore) DeleteCertificateAuthority(ctx context.Context, certificate stmt = "DELETE FROM certificate_authorities WHERE id = ?" result, err := ds.writer(ctx).ExecContext(ctx, stmt, certificateAuthorityID) if err != nil { + if isMySQLForeignKey(err) { + return nil, fleet.ConflictError{ + Message: "Couldn't delete. This certificate authority is used in a certificate. Please remove the certificate first.", + } + } return nil, ctxerr.Wrap(ctx, err, fmt.Sprintf("deleting certificate authority with id %d", certificateAuthorityID)) } diff --git a/server/datastore/mysql/certificate_authorities_test.go b/server/datastore/mysql/certificate_authorities_test.go index 9645c34477..f4f2e6bf72 100644 --- a/server/datastore/mysql/certificate_authorities_test.go +++ b/server/datastore/mysql/certificate_authorities_test.go @@ -378,6 +378,31 @@ func testDeleteCertificateAuthority(t *testing.T, ds *Datastore) { _, err = ds.DeleteCertificateAuthority(ctx, ca.ID) require.Error(t, err) require.Contains(t, err.Error(), "not found") + + // Test that deleting a CA referenced by a certificate template returns a conflict error + ca, err = ds.NewCertificateAuthority(ctx, &fleet.CertificateAuthority{ + Type: string(fleet.CATypeCustomSCEPProxy), + Name: ptr.String("Referenced CA"), + URL: ptr.String("https://localhost"), + }) + require.NoError(t, err) + require.NotNil(t, ca) + + team, err := ds.NewTeam(ctx, &fleet.Team{Name: "cert-test-team"}) + require.NoError(t, err) + + // Insert a certificate template that references the CA + _, err = ds.writer(ctx).ExecContext(ctx, + `INSERT INTO certificate_templates (team_id, certificate_authority_id, name, subject_name) VALUES (?, ?, ?, ?)`, + team.ID, ca.ID, "test-cert", "CN=test", + ) + require.NoError(t, err) + + _, err = ds.DeleteCertificateAuthority(ctx, ca.ID) + require.Error(t, err) + var conflictErr fleet.ConflictError + require.ErrorAs(t, err, &conflictErr) + require.Contains(t, conflictErr.Error(), "certificate authority is used in a certificate") } func testUpdateCertificateAuthorityByID(t *testing.T, ds *Datastore) {