<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #34544 Demo video: https://www.youtube.com/watch?v=VzOkISWmEKw [Original research doc](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/research/orchestration/okta-conditional-access.md) [Victor's POC branch](https://github.com/fleetdm/fleet/tree/victor/33165-okta-conditional-access-poc) # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Conditional Access IdP integration added (IdP metadata & SSO) with device-health aware session checks. * Endpoint to download the IdP signing certificate (PEM) added. * Automatic revocation of old conditional access certificates with a configurable grace period. * **Tests** * Extensive tests for certificate rotation, lifecycle, SSO flows, URL construction, and IdP metadata. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
97 lines
3.3 KiB
Go
97 lines
3.3 KiB
Go
package condaccess
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/certificate"
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/scep/depot"
|
|
)
|
|
|
|
func initAssets(ctx context.Context, ds fleet.Datastore) error {
|
|
// Check if we have existing assets for both SCEP CA and IdP
|
|
expectedAssets := []fleet.MDMAssetName{
|
|
fleet.MDMAssetConditionalAccessCACert,
|
|
fleet.MDMAssetConditionalAccessCAKey,
|
|
fleet.MDMAssetConditionalAccessIDPCert,
|
|
fleet.MDMAssetConditionalAccessIDPKey,
|
|
}
|
|
savedAssets, err := ds.GetAllMDMConfigAssetsByName(ctx, expectedAssets, nil)
|
|
if err != nil {
|
|
// Allow not found errors or partial results (some assets exist, some don't).
|
|
// If we got some assets back, continue to create the missing ones.
|
|
if !fleet.IsNotFound(err) && len(savedAssets) == 0 {
|
|
return fmt.Errorf("loading existing conditional access assets from the database: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check if CA assets need to be created
|
|
_, hasCACert := savedAssets[fleet.MDMAssetConditionalAccessCACert]
|
|
_, hasCAKey := savedAssets[fleet.MDMAssetConditionalAccessCAKey]
|
|
if !hasCACert || !hasCAKey {
|
|
// Create CA cert and key for SCEP
|
|
caCert := depot.NewCACert(
|
|
depot.WithYears(10),
|
|
depot.WithCommonName("Fleet conditional access CA"),
|
|
// Signal that the CA is local to the deployment and not necessarily managed by Fleet or another external vendor
|
|
depot.WithOrganization("Local certificate authority"),
|
|
)
|
|
scepCert, scepKey, err := depot.NewCACertKey(caCert)
|
|
if err != nil {
|
|
return fmt.Errorf("generating conditional access SCEP cert and key: %w", err)
|
|
}
|
|
|
|
// Store CA assets encrypted
|
|
var assets []fleet.MDMConfigAsset
|
|
for k, v := range map[fleet.MDMAssetName][]byte{
|
|
fleet.MDMAssetConditionalAccessCACert: certificate.EncodeCertPEM(scepCert),
|
|
fleet.MDMAssetConditionalAccessCAKey: certificate.EncodePrivateKeyPEM(scepKey),
|
|
} {
|
|
assets = append(assets, fleet.MDMConfigAsset{
|
|
Name: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
|
|
if err := ds.InsertMDMConfigAssets(ctx, assets, nil); err != nil && !mysql.IsDuplicate(err) {
|
|
return fmt.Errorf("inserting conditional access SCEP assets: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check if IdP assets need to be created
|
|
_, hasIdPCert := savedAssets[fleet.MDMAssetConditionalAccessIDPCert]
|
|
_, hasIdPKey := savedAssets[fleet.MDMAssetConditionalAccessIDPKey]
|
|
if !hasIdPCert || !hasIdPKey {
|
|
// Create IdP cert and key for SAML signing
|
|
idpCert := depot.NewCACert(
|
|
depot.WithYears(10),
|
|
depot.WithCommonName("Fleet conditional access IdP"),
|
|
depot.WithOrganization("Local certificate authority"),
|
|
)
|
|
idpCertX509, idpKey, err := depot.NewCACertKey(idpCert)
|
|
if err != nil {
|
|
return fmt.Errorf("generating conditional access IdP cert and key: %w", err)
|
|
}
|
|
|
|
// Store IdP assets encrypted
|
|
var assets []fleet.MDMConfigAsset
|
|
for k, v := range map[fleet.MDMAssetName][]byte{
|
|
fleet.MDMAssetConditionalAccessIDPCert: certificate.EncodeCertPEM(idpCertX509),
|
|
fleet.MDMAssetConditionalAccessIDPKey: certificate.EncodePrivateKeyPEM(idpKey),
|
|
} {
|
|
assets = append(assets, fleet.MDMConfigAsset{
|
|
Name: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
|
|
if err := ds.InsertMDMConfigAssets(ctx, assets, nil); err != nil && !mysql.IsDuplicate(err) {
|
|
return fmt.Errorf("inserting conditional access IdP assets: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|