fleet/server/datastore/mysql/host_identity_scep.go
Victor Lyuboslavsky 909a48b0ea
Host identity SCEP rate limit. (#31038)
Fixes #30989 

# Checklist for submitter

- [x] Added/updated automated tests
- [x] Where appropriate, automated tests simulate multiple hosts and
test for host isolation (updates to one hosts's records do not affect
another.)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added rate limiting to SCEP certificate enrollment, returning a "Too
Many Requests" (HTTP 429) response if hosts request certificates too
frequently.

* **Bug Fixes**
* Improved error handling for rate-limited SCEP requests, providing
clear feedback when rate limits are exceeded.

* **Tests**
  * Introduced integration tests to verify SCEP rate limiting behavior.

* **Chores**
* Enhanced internal configuration handling for SCEP certificate
management.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-18 10:04:14 -03:00

62 lines
2.1 KiB
Go

package mysql
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/fleetdm/fleet/v4/ee/server/service/hostidentity/types"
"github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql"
"github.com/jmoiron/sqlx"
)
// Most of the code for the host identity feature is located at ./ee/server/service/hostidentity
func (ds *Datastore) GetHostIdentityCertBySerialNumber(ctx context.Context, serialNumber uint64) (*types.HostIdentityCertificate, error) {
var hostIdentityCert types.HostIdentityCertificate
err := sqlx.GetContext(ctx, ds.reader(ctx), &hostIdentityCert, fmt.Sprintf(`
SELECT serial, host_id, name, not_valid_after, public_key_raw
FROM host_identity_scep_certificates
WHERE serial = %d
AND not_valid_after > NOW()
AND revoked = 0`, serialNumber))
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, notFound("host identity certificate")
case err != nil:
return nil, err
}
return &hostIdentityCert, nil
}
func (ds *Datastore) UpdateHostIdentityCertHostIDBySerial(ctx context.Context, serialNumber uint64, hostID uint) error {
return common_mysql.WithRetryTxx(ctx, ds.writer(ctx), func(tx sqlx.ExtContext) error {
return updateHostIdentityCertHostIDBySerial(ctx, tx, hostID, serialNumber)
}, ds.logger)
}
func updateHostIdentityCertHostIDBySerial(ctx context.Context, tx sqlx.ExtContext, hostID uint, serialNumber uint64) error {
_, err := tx.ExecContext(ctx, `
UPDATE host_identity_scep_certificates
SET host_id = ?
WHERE serial = ?`, hostID, serialNumber)
return err
}
func (ds *Datastore) GetHostIdentityCertByName(ctx context.Context, name string) (*types.HostIdentityCertificate, error) {
var hostIdentityCert types.HostIdentityCertificate
err := sqlx.GetContext(ctx, ds.reader(ctx), &hostIdentityCert, `
SELECT serial, host_id, name, not_valid_after, public_key_raw, created_at
FROM host_identity_scep_certificates
WHERE name = ?
AND not_valid_after > NOW()
AND revoked = 0`, name)
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, notFound("host identity certificate")
case err != nil:
return nil, err
}
return &hostIdentityCert, nil
}