Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/42508-rename-abm-to-ab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Renamed Apple Business Manager (ABM) terminology to Apple Business (AB) in the API, GitOps YAML, and `fleetctl` CLI. The new `/api/v1/fleet/ab_tokens` and `/api/v1/fleet/mdm/apple/ab_public_key` endpoints, `mdm.apple_business` YAML key, and `fleetctl get mdm-ab`/`fleetctl generate mdm-ab` commands are canonical however the now-deprecated `/abm_tokens`, `/mdm/apple/abm_public_key`, `apple_business_manager`, `mdm-apple-bm` aliases continue to work for backwards compatibility and log a deprecation warning when used.
105 changes: 64 additions & 41 deletions cmd/fleetctl/fleetctl/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"

"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/urfave/cli/v2"
)

Expand All @@ -23,6 +24,7 @@ func generateCommand() *cli.Command {
},
Subcommands: []*cli.Command{
generateMDMAppleCommand(),
generateMDMABCommand(),
generateMDMAppleBMCommand(),
},
}
Expand Down Expand Up @@ -87,62 +89,83 @@ Go to %s/settings/integrations/mdm/apple and follow the steps.
}
}

func generateMDMABCommand() *cli.Command {
return &cli.Command{
Name: "mdm-ab",
Aliases: []string{"mdm_ab"},
Usage: "Generate Apple Business (AB) public key to enable automatic enrollment for macOS hosts.",
Flags: generateMDMABFlags(),
Action: runGenerateMDMAB,
}
}

func generateMDMAppleBMCommand() *cli.Command {
return &cli.Command{
Name: "mdm-apple-bm",
Aliases: []string{"mdm_apple_bm"},
Usage: "Generate Apple Business public key to enable automatic enrollment for macOS hosts.",
Flags: []cli.Flag{
contextFlag(),
debugFlag(),
&cli.StringFlag{
Name: "public-key",
Usage: "The output path for the Apple Business public key certificate.",
Value: bmPublicKeyCertPath,
},
},
Usage: "Deprecated. Use mdm-ab instead.",
Flags: generateMDMABFlags(),
Action: func(c *cli.Context) error {
publicKeyPath := c.String("public-key")

// get the fleet API client first, so that any login requirement are met
// before printing the CSR output message.
client, err := clientFromCLI(c)
if err != nil {
fmt.Fprintf(c.App.ErrWriter, "client from CLI: %s", err)
return ErrGeneric
if logging.TopicEnabled(logging.DeprecatedFieldTopic) {
fmt.Fprintf(c.App.ErrWriter, "[!] 'fleetctl generate mdm-apple-bm' is deprecated; use 'fleetctl generate mdm-ab' instead\n")
}
return runGenerateMDMAB(c)
},
}
}

publicKey, err := client.RequestAppleABM()
if err != nil {
fmt.Fprintf(c.App.ErrWriter, "requesting ABM public key: %s", err)
return ErrGeneric
}
func generateMDMABFlags() []cli.Flag {
return []cli.Flag{
contextFlag(),
debugFlag(),
&cli.StringFlag{
Name: "public-key",
Usage: "The output path for the Apple Business (AB) public key certificate.",
Value: bmPublicKeyCertPath,
Comment thread
JordanMontgomery marked this conversation as resolved.
},
}
}

if err := os.WriteFile(publicKeyPath, publicKey, defaultFileMode); err != nil {
fmt.Fprintf(c.App.ErrWriter, "write public key: %s", err)
return ErrGeneric
}
func runGenerateMDMAB(c *cli.Context) error {
publicKeyPath := c.String("public-key")

appCfg, err := client.GetAppConfig()
if err != nil {
fmt.Fprintf(c.App.ErrWriter, "fetching app config: %s", err)
return ErrGeneric
}
// get the fleet API client first, so that any login requirement are met
// before printing the CSR output message.
client, err := clientFromCLI(c)
if err != nil {
fmt.Fprintf(c.App.ErrWriter, "client from CLI: %s", err)
return ErrGeneric
}

fmt.Fprintf(
c.App.Writer,
`Success!
publicKey, err := client.RequestAppleABM()
if err != nil {
fmt.Fprintf(c.App.ErrWriter, "requesting Apple Business public key: %s", err)
return ErrGeneric
}

if err := os.WriteFile(publicKeyPath, publicKey, defaultFileMode); err != nil {
fmt.Fprintf(c.App.ErrWriter, "write public key: %s", err)
return ErrGeneric
}

appCfg, err := client.GetAppConfig()
if err != nil {
fmt.Fprintf(c.App.ErrWriter, "fetching app config: %s", err)
return ErrGeneric
}

fmt.Fprintf(
c.App.Writer,
`Success!

Generated your public key at %s

Go to %s/settings/integrations/automatic-enrollment/apple and follow the steps.

`,
publicKeyPath,
appCfg.ServerSettings.ServerURL,
)
publicKeyPath,
appCfg.ServerSettings.ServerURL,
)

return nil
},
}
return nil
}
7 changes: 5 additions & 2 deletions cmd/fleetctl/fleetctl/generate_gitops.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,12 @@ func jsonFieldName(t reflect.Type, fieldName string) string {
panic(fieldName + " not found in " + t.Name())
}

// Prefer the renameto tag (new canonical name) if it exists.
// Prefer the renameto tag (new canonical name) if it exists, stripping any
// options like ",inline".
if renameTo := field.Tag.Get("renameto"); renameTo != "" {
return renameTo
if name, _, _ := strings.Cut(renameTo, ","); name != "" {
return name
}
}

tag := field.Tag.Get("json")
Expand Down
2 changes: 1 addition & 1 deletion cmd/fleetctl/fleetctl/generate_gitops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2141,7 +2141,7 @@ func TestGenerateControlsAndMDMWithoutMDMEnabledAndConfigured(t *testing.T) {
require.NoError(t, err)
// Verify all keys are set to empty.
for _, key := range []string{
"apple_business_manager",
"apple_business",
"apple_server_url",
"end_user_authentication",
"end_user_license_agreement",
Expand Down
102 changes: 62 additions & 40 deletions cmd/fleetctl/fleetctl/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/fleetdm/fleet/v4/pkg/rawjson"
"github.com/fleetdm/fleet/v4/pkg/secure"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/ghodss/yaml"
kithttp "github.com/go-kit/kit/transport/http"
Expand Down Expand Up @@ -494,6 +495,7 @@ func getCommand() *cli.Command {
getFleetsCommand(),
getSoftwareCommand(),
getMDMAppleCommand(),
getMDMABCommand(),
getMDMAppleBMCommand(),
getMDMCommandResultsCommand(),
getMDMCommandsCommand(),
Expand Down Expand Up @@ -1633,58 +1635,78 @@ func getMDMAppleCommand() *cli.Command {
}
}

func getMDMABCommand() *cli.Command {
return &cli.Command{
Name: "mdm-ab",
Aliases: []string{"mdm_ab"},
Usage: "Show information about Apple Business (AB) for automatic enrollment",
Flags: getMDMABFlags(),
Action: runGetMDMAB,
}
}

func getMDMAppleBMCommand() *cli.Command {
return &cli.Command{
Name: "mdm-apple-bm",
Aliases: []string{"mdm_apple_bm"},
Usage: "Show information about Apple Business for automatic enrollment",
Flags: []cli.Flag{
configFlag(),
contextFlag(),
debugFlag(),
},
Usage: "Deprecated. Use mdm-ab instead.",
Flags: getMDMABFlags(),
Action: func(c *cli.Context) error {
const expirationWarning = 30 * 24 * time.Hour // 30 days

client, err := clientFromCLI(c)
if err != nil {
return err
if logging.TopicEnabled(logging.DeprecatedFieldTopic) {
fmt.Fprintf(c.App.ErrWriter, "[!] 'fleetctl get mdm-apple-bm' is deprecated; use 'fleetctl get mdm-ab' instead\n")
}
return runGetMDMAB(c)
},
}
}

bm, err := client.GetAppleBM()
if err != nil {
var nfe service.NotFoundErr
if errors.As(err, &nfe) {
log(c, "Error: No Apple Business server token found. Use `fleetctl generate mdm-apple-bm` and then `fleet serve` with `mdm` configuration to automatically enroll macOS hosts to Fleet.\n")
return nil
}
return fmt.Errorf("could not get Apple BM information: %w", err)
}
func getMDMABFlags() []cli.Flag {
return []cli.Flag{
configFlag(),
contextFlag(),
debugFlag(),
}
}

defaultTeam := bm.DefaultTeam
if defaultTeam == "" {
defaultTeam = "No team"
}
printKeyValueTable(c, [][]string{
{"Apple ID:", bm.AppleID},
{"Organization name:", bm.OrgName},
{"MDM server URL:", bm.MDMServerURL},
{"Renew date:", bm.RenewDate.Format("January 2, 2006")},
{"Default team:", defaultTeam},
})
func runGetMDMAB(c *cli.Context) error {
const expirationWarning = 30 * 24 * time.Hour // 30 days

warnDate := time.Now().Add(expirationWarning)
if bm.RenewDate.Before(time.Now()) {
// certificate is expired, print an error
color.New(color.FgRed).Fprintln(c.App.Writer, "\nERROR: Your Apple Business (AB) server token is expired. Laptops newly purchased via ABM will not automatically enroll in Fleet. To renew your ABM server token, follow these instructions: https://fleetdm.com/docs/using-fleet/faq#how-can-i-renew-my-apple-business-manager-server-token")
} else if bm.RenewDate.Before(warnDate) {
// certificate will soon expire, print a warning
color.New(color.FgYellow).Fprintln(c.App.Writer, "\nWARNING: Your Apple Business (AB) server token is less than 30 days from expiration. If it expires, laptops newly purchased via ABM will not automatically enroll in Fleet. To renew your ABM server token, follow these instructions: https://fleetdm.com/docs/using-fleet/faq#how-can-i-renew-my-apple-business-manager-server-token")
}
client, err := clientFromCLI(c)
if err != nil {
return err
}

bm, err := client.GetAppleBM()
if err != nil {
if _, ok := errors.AsType[service.NotFoundErr](err); ok {
log(c, "Error: No Apple Business (AB) server token found. Use `fleetctl generate mdm-ab` and then `fleet serve` with `mdm` configuration to automatically enroll macOS hosts to Fleet.\n")
return nil
},
}
return fmt.Errorf("could not get Apple Business information: %w", err)
}

defaultTeam := bm.DefaultTeam
if defaultTeam == "" {
defaultTeam = "Unassigned"
}
printKeyValueTable(c, [][]string{
{"Apple ID:", bm.AppleID},
{"Organization name:", bm.OrgName},
{"MDM server URL:", bm.MDMServerURL},
{"Renew date:", bm.RenewDate.Format("January 2, 2006")},
{"Default fleet:", defaultTeam},
})
Comment thread
JordanMontgomery marked this conversation as resolved.

warnDate := time.Now().Add(expirationWarning)
if bm.RenewDate.Before(time.Now()) {
// certificate is expired, print an error
color.New(color.FgRed).Fprintln(c.App.Writer, "\nERROR: Your Apple Business (AB) server token is expired. Laptops newly purchased via Apple Business will not automatically enroll in Fleet. To renew your AB server token, follow these instructions: https://fleetdm.com/docs/using-fleet/faq#how-can-i-renew-my-apple-business-manager-server-token")
} else if bm.RenewDate.Before(warnDate) {
// certificate will soon expire, print a warning
color.New(color.FgYellow).Fprintln(c.App.Writer, "\nWARNING: Your Apple Business (AB) server token is less than 30 days from expiration. If it expires, laptops newly purchased via Apple Business will not automatically enroll in Fleet. To renew your AB server token, follow these instructions: https://fleetdm.com/docs/using-fleet/faq#how-can-i-renew-my-apple-business-manager-server-token")
}

return nil
}

func getMDMCommandResultsCommand() *cli.Command {
Expand Down
8 changes: 4 additions & 4 deletions cmd/fleetctl/fleetctl/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2565,7 +2565,7 @@ func TestGetAppleBM(t *testing.T) {
t.Run("free license", func(t *testing.T) {
testing_utils.RunServerWithMockedDS(t)

expected := `could not get Apple BM information: missing or invalid license`
expected := `could not get Apple Business information: missing or invalid license`
_, err := runAppNoChecks([]string{"get", "mdm_apple_bm"})
require.Error(t, err)
assert.Contains(t, err.Error(), expected)
Expand All @@ -2585,7 +2585,7 @@ func TestGetAppleBM(t *testing.T) {
assert.Contains(t, out, "Organization name:")
assert.Contains(t, out, "MDM server URL:")
assert.Contains(t, out, "Renew date:")
assert.Contains(t, out, "Default team:")
assert.Contains(t, out, "Default fleet:")
})

t.Run("premium license, no token", func(t *testing.T) {
Expand All @@ -2596,7 +2596,7 @@ func TestGetAppleBM(t *testing.T) {
}

out := runAppForTest(t, []string{"get", "mdm_apple_bm"})
assert.Contains(t, out, "No Apple Business server token found.")
assert.Contains(t, out, "No Apple Business (AB) server token found.")
})

t.Run("premium license, multiple tokens", func(t *testing.T) {
Expand All @@ -2610,7 +2610,7 @@ func TestGetAppleBM(t *testing.T) {
}

_, err := runAppNoChecks([]string{"get", "mdm_apple_bm"})
assert.ErrorContains(t, err, "This API endpoint has been deprecated. Please use the new GET /abm_tokens API endpoint")
assert.ErrorContains(t, err, "This API endpoint has been deprecated. Please use the new GET /ab_tokens API endpoint")
})
}

Expand Down
15 changes: 9 additions & 6 deletions cmd/fleetctl/fleetctl/gitops.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,15 +507,15 @@ func gitopsCommand() *cli.Command {
if hasMissingABMTeam {
if mdm, ok := config.OrgSettings["mdm"]; ok {
if mdmMap, ok := mdm.(map[string]any); ok {
if appleBM, ok := mdmMap["apple_business_manager"]; ok {
if appleBM, ok := mdmMap["apple_business"]; ok {
if bmSettings, ok := appleBM.([]any); ok {
originalABMConfig = bmSettings
}
}

// If team is not found, we need to remove the AppleBMDefaultTeam from
// the global config, and then apply it after teams are processed
mdmMap["apple_business_manager"] = nil
mdmMap["apple_business"] = nil
mdmMap["apple_bm_default_team"] = ""
Comment thread
MagnusHJensen marked this conversation as resolved.
}
}
Expand Down Expand Up @@ -662,7 +662,7 @@ func gitopsCommand() *cli.Command {
if usesLegacyABMConfig {
return fmt.Errorf("apple_bm_default_team %s cannot be deleted", team.Name)
}
return fmt.Errorf("apple_business_manager team %s cannot be deleted", team.Name)
return fmt.Errorf("apple_business team %s cannot be deleted", team.Name)
}
if slices.Contains(vppTeams, team.Name) {
return fmt.Errorf("volume_purchasing_program team %s cannot be deleted", team.Name)
Expand Down Expand Up @@ -1005,7 +1005,10 @@ func checkABMTeamAssignments(config *spec.GitOps, fleetClient *service.Client) (
if mdm, ok := config.OrgSettings["mdm"]; ok {
if mdmMap, ok := mdm.(map[string]any); ok {
appleBMDT, hasLegacyConfig := mdmMap["apple_bm_default_team"]
appleBM, hasNewConfig := mdmMap["apple_business_manager"]
// After ApplyDeprecatedKeyMappings runs, any legacy
// "apple_business_manager" key has already been migrated to
// "apple_business", so we only look up the new name here.
appleBM, hasNewConfig := mdmMap["apple_business"]

if hasLegacyConfig && hasNewConfig {
return nil, false, false, errors.New(fleet.AppleABMDefaultTeamDeprecatedMessage)
Expand Down Expand Up @@ -1130,13 +1133,13 @@ func applyABMTokenAssignmentIfNeeded(
continue
}
if _, ok := knownTeams[norm.NFC.String(abmTeam)]; !ok {
return fmt.Errorf("apple_business_manager team %q not found in team configs", abmTeam)
return fmt.Errorf("apple_business team %q not found in team configs", abmTeam)
}
}

appConfigUpdate = map[string]map[string]any{
"mdm": {
"apple_business_manager": originalMDMConfig,
"apple_business": originalMDMConfig,
},
}
}
Expand Down
Loading
Loading