Fix: Missing guide: How to try Windows MDM (fleetctl preview) (#42451)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #32773

# 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] QA'd all new/changed functionality manually

I ran `./build/fleetctl preview --preview-config-path
./tools/osquery/in-a-box`. Not sure if this is the standard way of
running `fleetctl preview`.

#### Before


https://github.com/user-attachments/assets/d1aef9e4-83fe-4e8e-8ec9-91b6258caefa

#### After


https://github.com/user-attachments/assets/67937211-0d7c-421a-99ba-e10842c503b3

#### Test on Windows 11 desktop PC

```
$ go build -o fleetctl.exe .\cmd\fleetctl
$ .\fleetctl.exe preview --preview-config-path .\tools\osquery\in-a-box
```

<img width="650" height="256" alt="1"
src="https://github.com/user-attachments/assets/9072010a-2182-4a10-a30b-c7a10bb1a76e"
/>
<img width="884" height="304" alt="2"
src="https://github.com/user-attachments/assets/322e3f66-4543-4a38-90ad-29be3df22863"
/>
This commit is contained in:
Nico
2026-03-27 12:15:35 -03:00
committed by GitHub
parent e3c8bbea71
commit 521593aa61
3 changed files with 99 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
* Enabled Windows MDM in `fleetctl preview` by auto-generating WSTEP certificates on startup.
+94
View File
@@ -3,12 +3,17 @@ package fleetctl
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io"
"log/slog"
"math/big"
"net/http"
"os"
"os/exec"
@@ -269,6 +274,18 @@ Use the stop and reset subcommands to manage the server and dependencies once st
return fmt.Errorf("failed to set private key: %w", err)
}
// Generate a self-signed WSTEP certificate and key for Windows MDM
// and save them to the config directory for use in subsequent runs.
if err := ensureWSTEPCerts(filepath.Join(previewDir, "config")); err != nil {
return fmt.Errorf("generating WSTEP certificates: %w", err)
}
if err := os.Setenv("FLEET_MDM_WINDOWS_WSTEP_IDENTITY_CERT", "/config/wstep.crt"); err != nil {
return fmt.Errorf("failed to set WSTEP cert path: %w", err)
}
if err := os.Setenv("FLEET_MDM_WINDOWS_WSTEP_IDENTITY_KEY", "/config/wstep.key"); err != nil {
return fmt.Errorf("failed to set WSTEP key path: %w", err)
}
if err := os.Setenv("FLEET_VERSION", c.String(tagFlagName)); err != nil {
return fmt.Errorf("failed to set Fleet version: %w", err)
}
@@ -489,6 +506,83 @@ func copyDirectory(destDir, sourceDir string) error {
})
}
// ensureWSTEPCerts generates a self-signed WSTEP identity certificate and key
// for Windows MDM if they don't already exist in configDir. The generated files
// match the parameters recommended in the Windows MDM setup guide.
func ensureWSTEPCerts(configDir string) error {
certPath := filepath.Join(configDir, "wstep.crt")
keyPath := filepath.Join(configDir, "wstep.key")
_, certErr := os.Stat(certPath)
if certErr != nil && !os.IsNotExist(certErr) {
return fmt.Errorf("checking WSTEP certificate: %w", certErr)
}
_, keyErr := os.Stat(keyPath)
if keyErr != nil && !os.IsNotExist(keyErr) {
return fmt.Errorf("checking WSTEP key: %w", keyErr)
}
certExists := certErr == nil
keyExists := keyErr == nil
// Both cert and key already exist; nothing to do.
if certExists && keyExists {
return nil
}
// One exists without the other: inconsistent state; refuse to overwrite and provide remediation.
if certExists != keyExists {
return fmt.Errorf("inconsistent WSTEP certificate/key state: certificate exists=%t, key exists=%t. Please delete the existing wstep.crt and/or wstep.key in %q and re-run `fleetctl preview` to regenerate them", certExists, keyExists, configDir)
}
// Both files are missing; generate a new keypair and self-signed certificate.
key, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return fmt.Errorf("generating RSA key: %w", err)
}
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return fmt.Errorf("generating certificate serial number: %w", err)
}
if serialNumber.Sign() <= 0 {
serialNumber = big.NewInt(1)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "Fleet Root CA",
Country: []string{"US"},
Organization: []string{"Fleet."},
},
NotBefore: time.Now().Add(-10 * time.Minute),
NotAfter: time.Now().AddDate(10, 0, 0),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return fmt.Errorf("creating certificate: %w", err)
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
if err := os.WriteFile(certPath, certPEM, 0o644); err != nil {
return fmt.Errorf("writing WSTEP certificate: %w", err)
}
if err := os.WriteFile(keyPath, keyPEM, 0o644); err != nil {
return fmt.Errorf("writing WSTEP key: %w", err)
}
return nil
}
var TestOverridePreviewDirectory string
func previewDirectory() string {
@@ -64,6 +64,8 @@ services:
FLEET_VULNERABILITIES_PERIODICITY: 5m
FLEET_LOGGING_DEBUG: 'true'
FLEET_SERVER_PRIVATE_KEY: ${FLEET_SERVER_PRIVATE_KEY}
FLEET_MDM_WINDOWS_WSTEP_IDENTITY_CERT: "${FLEET_MDM_WINDOWS_WSTEP_IDENTITY_CERT:-}"
FLEET_MDM_WINDOWS_WSTEP_IDENTITY_KEY: "${FLEET_MDM_WINDOWS_WSTEP_IDENTITY_KEY:-}"
# This can be configured for testing purposes but otherwise uses the
# typical default of provided.
FLEET_OSQUERY_HOST_IDENTIFIER: ${FLEET_OSQUERY_HOST_IDENTIFIER:-provided}
@@ -121,6 +123,8 @@ services:
FLEET_OSQUERY_LABEL_UPDATE_INTERVAL: 1m
FLEET_VULNERABILITIES_CURRENT_INSTANCE_CHECKS: "no"
FLEET_SERVER_PRIVATE_KEY: ${FLEET_SERVER_PRIVATE_KEY}
FLEET_MDM_WINDOWS_WSTEP_IDENTITY_CERT: "${FLEET_MDM_WINDOWS_WSTEP_IDENTITY_CERT:-}"
FLEET_MDM_WINDOWS_WSTEP_IDENTITY_KEY: "${FLEET_MDM_WINDOWS_WSTEP_IDENTITY_KEY:-}"
# This can be configured for testing purposes but otherwise uses the
# typical default of provided.
FLEET_OSQUERY_HOST_IDENTIFIER: ${FLEET_OSQUERY_HOST_IDENTIFIER:-provided}