mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #34542 - Added SCEP endpoint for issuing certs for conditional access for Okta. Functionally similar to host identity and Apple MDM SCEP endpoints. - Changes file will be added later (this is a sub-task of the feature). - A standard SCEP payload can be used to get a cert to an Apple device: ``` <!-- SCEP Configuration --> <dict> <key>PayloadContent</key> <dict> <key>URL</key> <string>https://myfleet.example.com/api/fleet/conditional_access/scep</string> <key>Challenge</key> <string>ENROLLMENT_SECRET</string> <key>Keysize</key> <integer>2048</integer> <key>Key Type</key> <string>RSA</string> <key>Key Usage</key> <integer>5</integer> <key>ExtendedKeyUsage</key> <array> <string>1.3.6.1.5.5.7.3.2</string> </array> <key>Subject</key> <array> <array> <array> <string>CN</string> <string>Fleet conditional access for Okta</string> </array> </array> </array> <key>SubjectAltName</key> <dict> <key>uniformResourceIdentifier</key> <array> <string>urn:device:apple:uuid:%HardwareUUID%</string> </array> </dict> <key>Retries</key> <integer>3</integer> <key>RetryDelay</key> <integer>10</integer> <!-- ACL for browser access --> <key>AllowAllAppsAccess</key> <true/> <!-- Set true for Safari access. Set false if Safari support not needed. --> <key>KeyIsExtractable</key> <false/> </dict> <key>PayloadDescription</key> <string>Configures SCEP for Fleet conditional access for Okta certificate</string> <key>PayloadDisplayName</key> <string>Fleet conditional access SCEP</string> <key>PayloadIdentifier</key> <string>com.fleetdm.conditional-access-scep</string> <key>PayloadType</key> <string>com.apple.security.scep</string> <key>PayloadUUID</key> <string>B2C3D4E5-F6A7-4B6C-9D8E-0F1A2B3C4D5E</string> <key>PayloadVersion</key> <integer>1</integer> </dict> ``` # Checklist for submitter ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually ## Database migrations - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## New Features * Adds Conditional Access SCEP certificate enrollment support, enabling hosts to obtain device identity certificates through secure certificate enrollment protocol endpoints. * Implements rate limiting for certificate enrollment requests to prevent abuse. ## Tests * Adds comprehensive integration tests for Conditional Access SCEP functionality, including certificate operations, rate limiting validation, and edge cases. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
71 lines
2.7 KiB
Go
71 lines
2.7 KiB
Go
package condaccess
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/config"
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSCEPRateLimit(t *testing.T) {
|
|
// Set up suite with rate limiting configuration
|
|
cooldown := 5 * time.Minute
|
|
s := SetUpSuiteWithConfig(t, "integrationtest.ConditionalAccessSCEPRateLimit", func(cfg *config.FleetConfig) {
|
|
cfg.Osquery.EnrollCooldown = cooldown
|
|
})
|
|
|
|
defer mysql.TruncateTables(t, s.BaseSuite.DS, []string{
|
|
"conditional_access_scep_serials", "conditional_access_scep_certificates",
|
|
}...)
|
|
|
|
t.Run("RateLimitSameHost", func(t *testing.T) {
|
|
ctx := t.Context()
|
|
|
|
// Create enrollment secret
|
|
err := s.DS.ApplyEnrollSecrets(ctx, nil, []*fleet.EnrollSecret{{Secret: testEnrollmentSecret}})
|
|
require.NoError(t, err)
|
|
|
|
// Create a test host
|
|
host, err := s.DS.NewHost(ctx, &fleet.Host{
|
|
OsqueryHostID: ptr.String("test-host-rate-limit"),
|
|
NodeKey: ptr.String("test-node-key-rate-limit"),
|
|
UUID: "test-uuid-rate-limit",
|
|
Hostname: "test-hostname-rate-limit",
|
|
Platform: "darwin",
|
|
DetailUpdatedAt: time.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// First certificate request - should succeed
|
|
cert1 := requestSCEPCertificate(t, s, host.UUID, testEnrollmentSecret)
|
|
require.NotNil(t, cert1, "First certificate request should succeed")
|
|
assert.Equal(t, "urn:device:apple:uuid:"+host.UUID, cert1.URIs[0].String())
|
|
|
|
// Second certificate request immediately after - should fail due to rate limit
|
|
httpResp, pkiMsgResp, cert2 := requestSCEPCertificateWithChallenge(t, s, host.UUID, testEnrollmentSecret)
|
|
require.Equal(t, http.StatusTooManyRequests, httpResp.StatusCode, "Should return HTTP 429 for rate limit")
|
|
require.Nil(t, pkiMsgResp, "PKI message not parsed for rate limit errors")
|
|
require.Nil(t, cert2, "Second certificate request should fail due to rate limit")
|
|
|
|
// Different host should be able to get certificate
|
|
differentHost, err := s.DS.NewHost(ctx, &fleet.Host{
|
|
OsqueryHostID: ptr.String("test-host-different"),
|
|
NodeKey: ptr.String("test-node-key-different"),
|
|
UUID: "test-uuid-different",
|
|
Hostname: "test-hostname-different",
|
|
Platform: "darwin",
|
|
DetailUpdatedAt: time.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
certDifferent := requestSCEPCertificate(t, s, differentHost.UUID, testEnrollmentSecret)
|
|
require.NotNil(t, certDifferent, "Different host should be able to get certificate")
|
|
assert.Equal(t, "urn:device:apple:uuid:"+differentHost.UUID, certDifferent.URIs[0].String())
|
|
})
|
|
}
|