Support generate-gitops for android certificates (#36457)

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

# Details

Implements outputting `android_settings.certificates` when running
`fleetctl generate-gitops`.

# Checklist for submitter

## Testing

- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually

* Added a certificate authority via the UI, then added some certificates
via GitOps, and verified that the certificates were outputted correctly
when using `fleetctl generate-gitops`.
This commit is contained in:
Scott Gress
2025-12-02 10:25:13 -06:00
committed by GitHub
parent 17ad695397
commit ee1eaf21cf
6 changed files with 108 additions and 3 deletions
+41 -2
View File
@@ -83,6 +83,8 @@ type generateGitopsClient interface {
GetSetupExperienceScript(teamID uint) (*fleet.Script, error)
GetAppleMDMEnrollmentProfile(teamID uint) (*fleet.MDMAppleSetupAssistant, error)
GetCertificateAuthoritiesSpec(includeSecrets bool) (*fleet.GroupedCertificateAuthorities, error)
GetCertificateTemplates(teamID string) ([]*fleet.CertificateTemplateResponseSummary, error)
GetCertificateTemplate(certificateID uint, hostUUID *string) (*fleet.CertificateTemplateResponseFull, error)
}
// Given a struct type and a field name, return the JSON field name.
@@ -1064,9 +1066,46 @@ func (cmd *GenerateGitopsCommand) generateControls(teamId *uint, teamName string
}
}
if cmd.AppConfig.License.IsPremium() {
mdmT := reflect.TypeOf(fleet.TeamMDM{})
// Get any Android certificate templates.
var certSummaries []*fleet.CertificateTemplateResponseSummary
var err error
if teamId == nil {
certSummaries, err = cmd.Client.GetCertificateTemplates("")
} else {
certSummaries, err = cmd.Client.GetCertificateTemplates(strconv.FormatUint(uint64(*teamId), 10))
}
if err != nil {
fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error getting certificate templates: %s\n", err)
return nil, err
}
mdmT := reflect.TypeOf(fleet.TeamMDM{})
if len(certSummaries) > 0 {
androidSettingsType := reflect.TypeOf(fleet.AndroidSettings{})
certType := reflect.TypeOf(fleet.CertificateTemplateResponseFull{})
fullCerts := make([]map[string]interface{}, 0, len(certSummaries))
for _, certSummary := range certSummaries {
certFull, err := cmd.Client.GetCertificateTemplate(certSummary.ID, nil)
if err != nil {
fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error getting certificate template details for ID %d: %s\n", certSummary.ID, err)
return nil, err
}
fullCerts = append(fullCerts, map[string]interface{}{
jsonFieldName(certType, "Name"): certFull.Name,
jsonFieldName(certType, "CertificateAuthorityName"): certFull.CertificateAuthorityName,
jsonFieldName(certType, "SubjectName"): certFull.SubjectName,
})
}
androidSettings, ok := result[jsonFieldName(mdmT, "AndroidSettings")].(map[string]interface{})
if !ok {
androidSettings = map[string]interface{}{}
}
androidSettings[jsonFieldName(androidSettingsType, "Certificates")] = fullCerts
result[jsonFieldName(mdmT, "AndroidSettings")] = androidSettings
}
if cmd.AppConfig.License.IsPremium() {
if teamMdm != nil {
result[jsonFieldName(mdmT, "EnableDiskEncryption")] = teamMdm.EnableDiskEncryption
result[jsonFieldName(mdmT, "RequireBitLockerPIN")] = teamMdm.RequireBitLockerPIN
@@ -581,6 +581,35 @@ func (MockClient) GetCertificateAuthoritiesSpec(includeSecrets bool) (*fleet.Gro
return &res, nil
}
func (MockClient) GetCertificateTemplates(teamID string) ([]*fleet.CertificateTemplateResponseSummary, error) {
var res []*fleet.CertificateTemplateResponseSummary
if teamID == "1" {
res = []*fleet.CertificateTemplateResponseSummary{
{
ID: 1,
CertificateAuthorityName: "DIGIDOO",
Name: "my_certypoo",
},
}
}
return res, nil
}
func (MockClient) GetCertificateTemplate(certificateID uint, hostUUID *string) (*fleet.CertificateTemplateResponseFull, error) {
var res *fleet.CertificateTemplateResponseFull
if certificateID == 1 {
res = &fleet.CertificateTemplateResponseFull{
CertificateTemplateResponseSummary: fleet.CertificateTemplateResponseSummary{
ID: 1,
CertificateAuthorityName: "DIGIDOO",
Name: "my_certypoo",
},
SubjectName: "CN=OU=$FLEET_VAR_HOST_UUID/ST=$FLEET_VAR_HOST_HARDWARE_SERIAL",
}
}
return res, nil
}
func maskSecret(value string, shouldShowSecret bool) string {
if shouldShowSecret {
return value
@@ -2,4 +2,9 @@ macos_settings:
custom_settings:
- path: ../lib/some_team/profiles/team-macos-mobileconfig-profile.mobileconfig
scripts:
- path: ../lib/some_team/scripts/Script B.ps1
- path: ../lib/some_team/scripts/Script B.ps1
android_settings:
certificates:
- certificate_authority_name: DIGIDOO
name: my_certypoo
subject_name: CN=OU=$FLEET_VAR_HOST_UUID/ST=$FLEET_VAR_HOST_HARDWARE_SERIAL
@@ -134,6 +134,15 @@
},
"windows_settings": {
"custom_settings": null
},
"android_settings": {
"certificates": [
{
"certificate_authority_name": "DIGIDOO",
"name": "my_certypoo",
"subject_name": "CN=OU=$FLEET_VAR_HOST_UUID/ST=$FLEET_VAR_HOST_HARDWARE_SERIAL"
}
]
}
},
"scripts": [
@@ -18,6 +18,11 @@ agent_options:
osqueryd: edge
controls:
android_enabled_and_configured: true
android_settings:
certificates:
- certificate_authority_name: DIGIDOO
name: my_certypoo
subject_name: CN=OU=$FLEET_VAR_HOST_UUID/ST=$FLEET_VAR_HOST_HARDWARE_SERIAL
enable_disk_encryption: true
ios_updates:
deadline: "2021-12-31"
@@ -1,6 +1,8 @@
package service
import (
"strconv"
"github.com/fleetdm/fleet/v4/server/fleet"
)
@@ -16,6 +18,22 @@ func (c *Client) GetCertificateTemplates(teamID string) ([]*fleet.CertificateTem
return responseBody.Certificates, nil
}
// GetCertificateTemplate retrieves the full details of a single certificate, optionally
// replacing vars in the subject with values from a specified host.
func (c *Client) GetCertificateTemplate(certificateID uint, hostUUID *string) (*fleet.CertificateTemplateResponseFull, error) {
verb, path := "GET", "/api/latest/fleet/certificates/"+strconv.FormatUint(uint64(certificateID), 10)
var responseBody getCertificateTemplateResponse
var query string
if hostUUID != nil {
query = "host_uuid=" + *hostUUID
}
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query)
if err != nil {
return nil, err
}
return responseBody.Certificate, nil
}
// ApplyCertificateSpecs sends a list of certificate specs to the fleet instance to be added/updated.
func (c *Client) ApplyCertificateSpecs(specs []*fleet.CertificateRequestSpec) error {
req := applyCertificateTemplateSpecsRequest{Specs: specs}