Files
fleet/ee/server/service/condaccess/config.go
T
Victor Lyuboslavsky 5cfc28ae5a Okta IdP factor (#35143)
<!-- 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 -->
2025-11-07 16:19:25 -06:00

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
}