mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 16:39:01 +00:00
#21955 <div> <a href="https://www.loom.com/share/ba40b440502845d2861fd3ec7611bade"> <p>[Demo] Deploy SCEP certificates from Network Device Enrollment Service (NDES) #21955 - Watch Video</p> </a> <a href="https://www.loom.com/share/ba40b440502845d2861fd3ec7611bade"> <img style="max-width:300px;" src="https://cdn.loom.com/sessions/thumbnails/ba40b440502845d2861fd3ec7611bade-84f2d88c9f5106c2-full-play.gif"> </a> </div> Note: A few remaining subtasks will be done in a follow-up PR. See #22123 for a detailed list. # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [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/Committing-Changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
125 lines
3.8 KiB
Go
125 lines
3.8 KiB
Go
package assets
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/cryptoutil"
|
|
)
|
|
|
|
func CAKeyPair(ctx context.Context, ds fleet.MDMAssetRetriever) (*tls.Certificate, error) {
|
|
return KeyPair(ctx, ds, fleet.MDMAssetCACert, fleet.MDMAssetCAKey)
|
|
}
|
|
|
|
func APNSKeyPair(ctx context.Context, ds fleet.MDMAssetRetriever) (*tls.Certificate, error) {
|
|
return KeyPair(ctx, ds, fleet.MDMAssetAPNSCert, fleet.MDMAssetAPNSKey)
|
|
}
|
|
|
|
func KeyPair(ctx context.Context, ds fleet.MDMAssetRetriever, certName, keyName fleet.MDMAssetName) (*tls.Certificate, error) {
|
|
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{
|
|
certName,
|
|
keyName,
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("loading %s, %s keypair from the database: %w", certName, keyName, err)
|
|
}
|
|
|
|
cert, err := tls.X509KeyPair(assets[certName].Value, assets[keyName].Value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing %s, %s keypair: %w", certName, keyName, err)
|
|
}
|
|
|
|
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing %s certificate leaf: %w", certName, err)
|
|
}
|
|
|
|
return &cert, nil
|
|
}
|
|
|
|
func X509Cert(ctx context.Context, ds fleet.MDMAssetRetriever, certName fleet.MDMAssetName) (*x509.Certificate, error) {
|
|
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{certName}, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("loading certificate %s from the database: %w", certName, err)
|
|
}
|
|
|
|
block, _ := pem.Decode(assets[certName].Value)
|
|
if block == nil || block.Type != "CERTIFICATE" {
|
|
return nil, fmt.Errorf("decoding certificate PEM data: %w", err)
|
|
}
|
|
|
|
return x509.ParseCertificate(block.Bytes)
|
|
}
|
|
|
|
func APNSTopic(ctx context.Context, ds fleet.MDMAssetRetriever) (string, error) {
|
|
cert, err := X509Cert(ctx, ds, fleet.MDMAssetAPNSCert)
|
|
if err != nil {
|
|
return "", fmt.Errorf("retrieving APNs cert: %w", err)
|
|
}
|
|
|
|
mdmPushCertTopic, err := cryptoutil.TopicFromCert(cert)
|
|
if err != nil {
|
|
return "", fmt.Errorf("extracting topic from APNs certificate: %w", err)
|
|
}
|
|
|
|
return mdmPushCertTopic, nil
|
|
}
|
|
|
|
func ABMToken(ctx context.Context, ds fleet.MDMAssetRetriever, abmOrgName string) (*nanodep_client.OAuth1Tokens, error) {
|
|
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{
|
|
fleet.MDMAssetABMKey,
|
|
fleet.MDMAssetABMCert,
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("loading ABM assets from the database: %w", err)
|
|
}
|
|
|
|
abmTok, err := ds.GetABMTokenByOrgName(ctx, abmOrgName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get ABM token by name: %w", err)
|
|
}
|
|
|
|
cert, err := tls.X509KeyPair(assets[fleet.MDMAssetABMCert].Value, assets[fleet.MDMAssetABMKey].Value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing ABM keypair: %w", err)
|
|
}
|
|
|
|
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing ABM certificate: %w", err)
|
|
}
|
|
|
|
oAuthTok, err := DecryptRawABMToken(
|
|
abmTok.EncryptedToken,
|
|
leaf,
|
|
assets[fleet.MDMAssetABMKey].Value,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decrypting ABM token: %w", err)
|
|
}
|
|
|
|
return oAuthTok, nil
|
|
}
|
|
|
|
func DecryptRawABMToken(tokenBytes []byte, cert *x509.Certificate, keyPEM []byte) (*nanodep_client.OAuth1Tokens, error) {
|
|
bmKey, err := tokenpki.RSAKeyFromPEM(keyPEM)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse private key: %w", err)
|
|
}
|
|
token, err := tokenpki.DecryptTokenJSON(tokenBytes, cert, bmKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decrypt token: %w", err)
|
|
}
|
|
var jsonTok nanodep_client.OAuth1Tokens
|
|
if err := json.Unmarshal(token, &jsonTok); err != nil {
|
|
return nil, fmt.Errorf("unmarshal JSON token: %w", err)
|
|
}
|
|
return &jsonTok, nil
|
|
}
|