2018-05-04 16:53:21 +00:00
package service
import (
"bytes"
2021-08-24 12:50:03 +00:00
"context"
2018-05-04 16:53:21 +00:00
"encoding/json"
2021-11-22 14:13:26 +00:00
"errors"
2018-05-04 16:53:21 +00:00
"fmt"
2024-02-27 18:55:05 +00:00
"golang.org/x/text/unicode/norm"
2021-02-03 02:55:16 +00:00
"io"
2018-05-04 16:53:21 +00:00
"net/http"
2021-02-03 02:55:16 +00:00
"os"
2023-02-15 18:01:44 +00:00
"path/filepath"
2023-11-15 21:04:24 +00:00
"strings"
2021-02-03 02:55:16 +00:00
"time"
2018-05-04 16:53:21 +00:00
2023-04-25 13:36:01 +00:00
"github.com/fleetdm/fleet/v4/pkg/optjson"
2022-08-05 22:07:32 +00:00
"github.com/fleetdm/fleet/v4/pkg/spec"
2021-11-22 14:13:26 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
2021-08-26 13:28:53 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2023-11-15 21:04:24 +00:00
"github.com/fleetdm/fleet/v4/server/mdm"
2023-06-07 17:29:36 +00:00
kithttp "github.com/go-kit/kit/transport/http"
2018-05-04 16:53:21 +00:00
)
2022-06-01 23:05:05 +00:00
// Client is used to consume Fleet APIs from Go code
2018-05-04 16:53:21 +00:00
type Client struct {
2022-06-01 23:05:05 +00:00
* baseClient
2022-06-07 20:00:09 +00:00
addr string
token string
customHeaders map [ string ] string
2021-08-26 13:28:53 +00:00
2023-06-15 17:13:41 +00:00
outputWriter io . Writer
errWriter io . Writer
2018-05-04 16:53:21 +00:00
}
2021-02-03 02:55:16 +00:00
type ClientOption func ( * Client ) error
func NewClient ( addr string , insecureSkipVerify bool , rootCA , urlPrefix string , options ... ClientOption ) ( * Client , error ) {
// TODO #265 refactor all optional parameters to functional options
// API breaking change, needs a major version release
2023-04-27 11:44:39 +00:00
baseClient , err := newBaseClient ( addr , insecureSkipVerify , rootCA , urlPrefix , nil , fleet . CapabilityMap { } )
2022-06-01 23:05:05 +00:00
if err != nil {
return nil , err
2018-10-01 22:23:46 +00:00
}
2021-02-03 02:55:16 +00:00
client := & Client {
2022-06-01 23:05:05 +00:00
baseClient : baseClient ,
addr : addr ,
2021-02-03 02:55:16 +00:00
}
for _ , option := range options {
err := option ( client )
if err != nil {
return nil , err
}
}
return client , nil
}
func EnableClientDebug ( ) ClientOption {
return func ( c * Client ) error {
httpClient , ok := c . http . ( * http . Client )
if ! ok {
return errors . New ( "client is not *http.Client" )
}
httpClient . Transport = & logRoundTripper { roundtripper : httpClient . Transport }
return nil
}
2018-05-04 16:53:21 +00:00
}
2023-06-15 17:13:41 +00:00
func SetClientOutputWriter ( w io . Writer ) ClientOption {
2021-08-26 13:28:53 +00:00
return func ( c * Client ) error {
2023-06-15 17:13:41 +00:00
c . outputWriter = w
return nil
}
}
func SetClientErrorWriter ( w io . Writer ) ClientOption {
return func ( c * Client ) error {
c . errWriter = w
2021-08-26 13:28:53 +00:00
return nil
}
}
2022-06-07 20:00:09 +00:00
// WithCustomHeaders sets custom headers to be sent with every request made
// with the client.
func WithCustomHeaders ( headers map [ string ] string ) ClientOption {
return func ( c * Client ) error {
// clone the map to prevent any changes in the original affecting the client
m := make ( map [ string ] string , len ( headers ) )
for k , v := range headers {
m [ k ] = v
}
c . customHeaders = m
return nil
}
}
2022-10-05 22:53:54 +00:00
func ( c * Client ) doContextWithBodyAndHeaders ( ctx context . Context , verb , path , rawQuery string , bodyBytes [ ] byte , headers map [ string ] string ) ( * http . Response , error ) {
2021-08-24 12:50:03 +00:00
request , err := http . NewRequestWithContext (
ctx ,
2018-05-04 16:53:21 +00:00
verb ,
2020-11-05 04:45:16 +00:00
c . url ( path , rawQuery ) . String ( ) ,
2018-05-07 19:44:40 +00:00
bytes . NewBuffer ( bodyBytes ) ,
2018-05-04 16:53:21 +00:00
)
if err != nil {
2021-11-22 14:13:26 +00:00
return nil , ctxerr . Wrap ( ctx , err , "creating request object" )
2018-05-04 16:53:21 +00:00
}
2022-06-07 20:00:09 +00:00
// set the custom headers first, they should not override the actual headers
// we set explicitly.
for k , v := range c . customHeaders {
request . Header . Set ( k , v )
}
2018-05-04 16:53:21 +00:00
for k , v := range headers {
request . Header . Set ( k , v )
}
2021-08-26 13:28:53 +00:00
resp , err := c . http . Do ( request )
if err != nil {
2021-11-22 14:13:26 +00:00
return nil , ctxerr . Wrap ( ctx , err , "do request" )
2021-08-26 13:28:53 +00:00
}
if resp . Header . Get ( fleet . HeaderLicenseKey ) == fleet . HeaderLicenseValueExpired {
2023-06-15 17:13:41 +00:00
fleet . WriteExpiredLicenseBanner ( c . errWriter )
2021-08-26 13:28:53 +00:00
}
return resp , nil
2018-05-04 16:53:21 +00:00
}
2022-10-05 22:53:54 +00:00
func ( c * Client ) doContextWithHeaders ( ctx context . Context , verb , path , rawQuery string , params interface { } , headers map [ string ] string ) ( * http . Response , error ) {
var bodyBytes [ ] byte
var err error
if params != nil {
bodyBytes , err = json . Marshal ( params )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "marshaling json" )
}
}
return c . doContextWithBodyAndHeaders ( ctx , verb , path , rawQuery , bodyBytes , headers )
}
2020-11-05 04:45:16 +00:00
func ( c * Client ) Do ( verb , path , rawQuery string , params interface { } ) ( * http . Response , error ) {
2021-08-24 12:50:03 +00:00
return c . DoContext ( context . Background ( ) , verb , path , rawQuery , params )
}
func ( c * Client ) DoContext ( ctx context . Context , verb , path , rawQuery string , params interface { } ) ( * http . Response , error ) {
2018-05-04 16:53:21 +00:00
headers := map [ string ] string {
"Content-type" : "application/json" ,
"Accept" : "application/json" ,
}
2021-08-24 12:50:03 +00:00
return c . doContextWithHeaders ( ctx , verb , path , rawQuery , params , headers )
2018-05-04 16:53:21 +00:00
}
2020-11-05 04:45:16 +00:00
func ( c * Client ) AuthenticatedDo ( verb , path , rawQuery string , params interface { } ) ( * http . Response , error ) {
2018-05-04 16:53:21 +00:00
if c . token == "" {
return nil , errors . New ( "authentication token is empty" )
}
headers := map [ string ] string {
"Content-Type" : "application/json" ,
"Accept" : "application/json" ,
"Authorization" : fmt . Sprintf ( "Bearer %s" , c . token ) ,
}
2021-08-24 12:50:03 +00:00
return c . doContextWithHeaders ( context . Background ( ) , verb , path , rawQuery , params , headers )
2018-05-04 16:53:21 +00:00
}
func ( c * Client ) SetToken ( t string ) {
c . token = t
}
2021-02-03 02:55:16 +00:00
// http.RoundTripper that will log debug information about the request and
// response, including paths, timing, and body.
//
// Inspired by https://stackoverflow.com/a/39528716/491710 and
// github.com/motemen/go-loghttp
type logRoundTripper struct {
roundtripper http . RoundTripper
}
// RoundTrip implements http.RoundTripper
func ( l * logRoundTripper ) RoundTrip ( req * http . Request ) ( * http . Response , error ) {
// Log request
fmt . Fprintf ( os . Stderr , "%s %s\n" , req . Method , req . URL )
reqBody , err := req . GetBody ( )
if err != nil {
fmt . Fprintf ( os . Stderr , "GetBody error: %v\n" , err )
} else {
defer reqBody . Close ( )
if _ , err := io . Copy ( os . Stderr , reqBody ) ; err != nil {
fmt . Fprintf ( os . Stderr , "Copy body error: %v\n" , err )
}
}
fmt . Fprintf ( os . Stderr , "\n" )
// Perform request using underlying roundtripper
start := time . Now ( )
res , err := l . roundtripper . RoundTrip ( req )
if err != nil {
fmt . Fprintf ( os . Stderr , "RoundTrip error: %v" , err )
return nil , err
}
// Log response
2021-05-17 17:29:50 +00:00
took := time . Since ( start ) . Truncate ( time . Millisecond )
2021-02-03 02:55:16 +00:00
fmt . Fprintf ( os . Stderr , "%s %s %s (%s)\n" , res . Request . Method , res . Request . URL , res . Status , took )
resBody := & bytes . Buffer { }
resBodyReader := io . TeeReader ( res . Body , resBody )
if _ , err := io . Copy ( os . Stderr , resBodyReader ) ; err != nil {
fmt . Fprintf ( os . Stderr , "Read body error: %v" , err )
return nil , err
}
2021-09-15 19:27:53 +00:00
res . Body = io . NopCloser ( resBody )
2021-02-03 02:55:16 +00:00
return res , nil
}
2021-07-16 18:28:13 +00:00
2021-09-14 13:58:48 +00:00
func ( c * Client ) authenticatedRequestWithQuery ( params interface { } , verb string , path string , responseDest interface { } , query string ) error {
response , err := c . AuthenticatedDo ( verb , path , query , params )
2021-07-16 18:28:13 +00:00
if err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "%s %s: %w" , verb , path , err )
2021-07-16 18:28:13 +00:00
}
defer response . Body . Close ( )
2022-06-01 23:05:05 +00:00
return c . parseResponse ( verb , path , response , responseDest )
2021-07-16 18:28:13 +00:00
}
2021-09-14 13:58:48 +00:00
func ( c * Client ) authenticatedRequest ( params interface { } , verb string , path string , responseDest interface { } ) error {
return c . authenticatedRequestWithQuery ( params , verb , path , responseDest , "" )
}
2022-08-05 22:07:32 +00:00
2023-11-01 14:13:12 +00:00
func ( c * Client ) CheckAnyMDMEnabled ( ) error {
return c . runAppConfigChecks ( func ( ac * fleet . EnrichedAppConfig ) error {
if ! ac . MDM . EnabledAndConfigured && ! ac . MDM . WindowsEnabledAndConfigured {
return errors . New ( fleet . MDMNotConfiguredMessage )
}
return nil
} )
}
func ( c * Client ) CheckAppleMDMEnabled ( ) error {
2023-06-07 17:29:36 +00:00
return c . runAppConfigChecks ( func ( ac * fleet . EnrichedAppConfig ) error {
if ! ac . MDM . EnabledAndConfigured {
2023-11-01 14:13:12 +00:00
return errors . New ( fleet . AppleMDMNotConfiguredMessage )
2023-06-07 17:29:36 +00:00
}
return nil
} )
2023-04-07 20:31:02 +00:00
}
2023-05-02 19:03:10 +00:00
func ( c * Client ) CheckPremiumMDMEnabled ( ) error {
2023-06-07 17:29:36 +00:00
return c . runAppConfigChecks ( func ( ac * fleet . EnrichedAppConfig ) error {
if ac . License == nil || ! ac . License . IsPremium ( ) {
return errors . New ( "missing or invalid license" )
}
if ! ac . MDM . EnabledAndConfigured {
2023-11-01 14:13:12 +00:00
return errors . New ( fleet . AppleMDMNotConfiguredMessage )
2023-06-07 17:29:36 +00:00
}
return nil
} )
}
func ( c * Client ) runAppConfigChecks ( fn func ( ac * fleet . EnrichedAppConfig ) error ) error {
2023-05-02 19:03:10 +00:00
appCfg , err := c . GetAppConfig ( )
if err != nil {
2023-06-07 17:29:36 +00:00
var sce kithttp . StatusCoder
if errors . As ( err , & sce ) && sce . StatusCode ( ) == http . StatusForbidden {
// do not return an error, user may not have permission to read app
// config (e.g. gitops) and those appconfig checks are just convenience
// to avoid the round-trip with potentially large payload to the server.
// Those will still be validated with the actual API call.
return nil
}
2023-05-02 19:03:10 +00:00
return err
}
2023-06-07 17:29:36 +00:00
return fn ( appCfg )
2023-05-02 19:03:10 +00:00
}
2024-01-26 16:00:58 +00:00
// getProfilesContents takes file paths and creates a slice of profile payloads
// ready to batch-apply.
func getProfilesContents ( baseDir string , profiles [ ] fleet . MDMProfileSpec ) ( [ ] fleet . MDMProfileBatchPayload , error ) {
fileNameMap := make ( map [ string ] struct { } , len ( profiles ) )
result := make ( [ ] fleet . MDMProfileBatchPayload , 0 , len ( profiles ) )
for _ , profile := range profiles {
filePath := resolveApplyRelativePath ( baseDir , profile . Path )
fileContents , err := os . ReadFile ( filePath )
2023-11-15 21:04:24 +00:00
if err != nil {
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
}
// by default, use the file name. macOS profiles use their PayloadDisplayName
2024-01-26 16:00:58 +00:00
name := strings . TrimSuffix ( filepath . Base ( filePath ) , filepath . Ext ( filePath ) )
if mdm . GetRawProfilePlatform ( fileContents ) == "darwin" {
mc , err := fleet . NewMDMAppleConfigProfile ( fileContents , nil )
2023-11-15 21:04:24 +00:00
if err != nil {
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
}
name = strings . TrimSpace ( mc . Name )
}
2024-01-26 16:00:58 +00:00
if _ , isDuplicate := fileNameMap [ name ] ; isDuplicate {
2023-11-15 21:04:24 +00:00
return nil , errors . New ( "Couldn't edit windows_settings.custom_settings. More than one configuration profile have the same name (Windows .xml file name or macOS PayloadDisplayName)." )
}
2024-01-26 16:00:58 +00:00
fileNameMap [ name ] = struct { } { }
result = append ( result , fleet . MDMProfileBatchPayload {
Name : name ,
Contents : fileContents ,
Labels : profile . Labels ,
} )
2023-11-15 21:04:24 +00:00
2024-01-26 16:00:58 +00:00
}
return result , nil
2023-11-15 21:04:24 +00:00
}
2022-08-05 22:07:32 +00:00
// ApplyGroup applies the given spec group to Fleet.
2023-02-22 16:14:53 +00:00
func ( c * Client ) ApplyGroup (
ctx context . Context ,
specs * spec . Group ,
baseDir string ,
logf func ( format string , args ... interface { } ) ,
opts fleet . ApplySpecOptions ,
2024-02-09 19:34:57 +00:00
) ( map [ string ] uint , error ) {
2022-08-05 22:07:32 +00:00
logfn := func ( format string , args ... interface { } ) {
if logf != nil {
logf ( format , args ... )
}
}
2023-07-25 00:17:20 +00:00
// specs.Queries must be applied before specs.Packs because packs reference queries.
2022-08-05 22:07:32 +00:00
if len ( specs . Queries ) > 0 {
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[!] ignoring queries, dry run mode only supported for 'config' and 'team' specs\n" )
} else {
if err := c . ApplyQueries ( specs . Queries ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying queries: %w" , err )
2022-09-19 17:53:44 +00:00
}
logfn ( "[+] applied %d queries\n" , len ( specs . Queries ) )
2022-08-05 22:07:32 +00:00
}
}
if len ( specs . Labels ) > 0 {
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[!] ignoring labels, dry run mode only supported for 'config' and 'team' specs\n" )
} else {
if err := c . ApplyLabels ( specs . Labels ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying labels: %w" , err )
2022-09-19 17:53:44 +00:00
}
logfn ( "[+] applied %d labels\n" , len ( specs . Labels ) )
2022-08-05 22:07:32 +00:00
}
}
if len ( specs . Policies ) > 0 {
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[!] ignoring policies, dry run mode only supported for 'config' and 'team' specs\n" )
} else {
2023-08-21 16:22:07 +00:00
// Policy names must be unique, return error if duplicate policy names are found
if policyName := fleet . FirstDuplicatePolicySpecName ( specs . Policies ) ; policyName != "" {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf (
"applying policies: policy names must be unique. Please correct policy %q and try again." , policyName ,
)
2023-08-21 16:22:07 +00:00
}
2023-02-22 16:14:53 +00:00
// If set, override the team in all the policies.
if opts . TeamForPolicies != "" {
for _ , policySpec := range specs . Policies {
policySpec . Team = opts . TeamForPolicies
}
}
2022-09-19 17:53:44 +00:00
if err := c . ApplyPolicies ( specs . Policies ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying policies: %w" , err )
2022-09-19 17:53:44 +00:00
}
logfn ( "[+] applied %d policies\n" , len ( specs . Policies ) )
2022-08-05 22:07:32 +00:00
}
}
if len ( specs . Packs ) > 0 {
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[!] ignoring packs, dry run mode only supported for 'config' and 'team' specs\n" )
} else {
if err := c . ApplyPacks ( specs . Packs ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying packs: %w" , err )
2022-09-19 17:53:44 +00:00
}
logfn ( "[+] applied %d packs\n" , len ( specs . Packs ) )
2022-08-05 22:07:32 +00:00
}
}
if specs . AppConfig != nil {
2023-11-29 14:32:42 +00:00
windowsCustomSettings := extractAppCfgWindowsCustomSettings ( specs . AppConfig )
2023-11-15 21:04:24 +00:00
macosCustomSettings := extractAppCfgMacOSCustomSettings ( specs . AppConfig )
allCustomSettings := append ( macosCustomSettings , windowsCustomSettings ... )
2024-01-26 16:00:58 +00:00
// if there is no custom setting but the windows and mac settings are
// non-nil, this means that we want to clear the existing custom settings,
// so we still go on with calling the batch-apply endpoint.
//
// TODO(mna): shouldn't that be an || instead of && ? I.e. if there are no
// custom settings but windows is present and empty (but mac is absent),
// shouldn't that clear the windows ones?
if ( windowsCustomSettings != nil && macosCustomSettings != nil ) || len ( allCustomSettings ) > 0 {
2023-11-15 21:04:24 +00:00
fileContents , err := getProfilesContents ( baseDir , allCustomSettings )
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , err
}
// Figure out if MDM should be enabled.
assumeEnabled := false
// This cast is safe because we've already checked AppConfig when extracting custom settings
mdmConfigMap , ok := specs . AppConfig . ( map [ string ] interface { } ) [ "mdm" ] . ( map [ string ] interface { } )
if ok {
mdmEnabled , ok := mdmConfigMap [ "windows_enabled_and_configured" ]
if ok {
assumeEnabled , ok = mdmEnabled . ( bool )
assumeEnabled = ok && assumeEnabled
}
2023-02-15 18:01:44 +00:00
}
2024-02-09 19:34:57 +00:00
if err := c . ApplyNoTeamProfiles ( fileContents , opts , assumeEnabled ) ; err != nil {
return nil , fmt . Errorf ( "applying custom settings: %w" , err )
2023-02-15 18:01:44 +00:00
}
}
2023-04-07 20:31:02 +00:00
if macosSetup := extractAppCfgMacOSSetup ( specs . AppConfig ) ; macosSetup != nil {
2023-04-26 21:09:21 +00:00
if macosSetup . BootstrapPackage . Value != "" {
pkg , err := c . ValidateBootstrapPackageFromURL ( macosSetup . BootstrapPackage . Value )
2023-04-07 20:31:02 +00:00
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2023-04-07 20:31:02 +00:00
}
if ! opts . DryRun {
if err := c . EnsureBootstrapPackage ( pkg , uint ( 0 ) ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2023-04-25 13:36:01 +00:00
}
}
}
if macosSetup . MacOSSetupAssistant . Value != "" {
content , err := c . validateMacOSSetupAssistant ( resolveApplyRelativePath ( baseDir , macosSetup . MacOSSetupAssistant . Value ) )
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2023-04-25 13:36:01 +00:00
}
if ! opts . DryRun {
if err := c . uploadMacOSSetupAssistant ( content , nil , macosSetup . MacOSSetupAssistant . Value ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2023-04-07 20:31:02 +00:00
}
}
}
}
2023-10-10 22:00:45 +00:00
if scripts := extractAppCfgScripts ( specs . AppConfig ) ; scripts != nil {
files := resolveApplyRelativePaths ( baseDir , scripts )
scriptPayloads := make ( [ ] fleet . ScriptPayload , len ( files ) )
for i , f := range files {
b , err := os . ReadFile ( f )
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2023-10-10 22:00:45 +00:00
}
scriptPayloads [ i ] = fleet . ScriptPayload {
ScriptContents : b ,
Name : filepath . Base ( f ) ,
}
}
if err := c . ApplyNoTeamScripts ( scriptPayloads , opts ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying custom settings: %w" , err )
2023-10-10 22:00:45 +00:00
}
}
2022-09-19 17:53:44 +00:00
if err := c . ApplyAppConfig ( specs . AppConfig , opts ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2022-08-05 22:07:32 +00:00
}
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[+] would've applied fleet config\n" )
} else {
logfn ( "[+] applied fleet config\n" )
}
2022-08-05 22:07:32 +00:00
}
if specs . EnrollSecret != nil {
2022-09-19 17:53:44 +00:00
if opts . DryRun {
2024-02-09 19:34:57 +00:00
logfn ( "[+] would've applied enroll secrets\n" )
2022-09-19 17:53:44 +00:00
} else {
if err := c . ApplyEnrollSecretSpec ( specs . EnrollSecret ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying enroll secrets: %w" , err )
2022-09-19 17:53:44 +00:00
}
logfn ( "[+] applied enroll secrets\n" )
2022-08-05 22:07:32 +00:00
}
}
2024-02-09 19:34:57 +00:00
var teamIDsByName map [ string ] uint
2022-08-05 22:07:32 +00:00
if len ( specs . Teams ) > 0 {
2023-02-15 18:01:44 +00:00
// extract the teams' custom settings and resolve the files immediately, so
// that any non-existing file error is found before applying the specs.
2023-11-15 21:04:24 +00:00
tmMDMSettings := extractTmSpecsMDMCustomSettings ( specs . Teams )
2023-02-15 18:01:44 +00:00
2024-01-26 16:00:58 +00:00
tmFileContents := make ( map [ string ] [ ] fleet . MDMProfileBatchPayload , len ( tmMDMSettings ) )
2023-11-15 21:04:24 +00:00
for k , paths := range tmMDMSettings {
fileContents , err := getProfilesContents ( baseDir , paths )
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , err
2023-02-15 18:01:44 +00:00
}
tmFileContents [ k ] = fileContents
}
2023-04-07 20:31:02 +00:00
tmMacSetup := extractTmSpecsMacOSSetup ( specs . Teams )
tmBootstrapPackages := make ( map [ string ] * fleet . MDMAppleBootstrapPackage , len ( tmMacSetup ) )
2023-04-25 13:36:01 +00:00
tmMacSetupAssistants := make ( map [ string ] [ ] byte , len ( tmMacSetup ) )
2023-04-07 20:31:02 +00:00
for k , setup := range tmMacSetup {
2023-04-26 21:09:21 +00:00
if setup . BootstrapPackage . Value != "" {
bp , err := c . ValidateBootstrapPackageFromURL ( setup . BootstrapPackage . Value )
2023-04-07 20:31:02 +00:00
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying teams: %w" , err )
2023-04-07 20:31:02 +00:00
}
tmBootstrapPackages [ k ] = bp
}
2023-04-25 13:36:01 +00:00
if setup . MacOSSetupAssistant . Value != "" {
b , err := c . validateMacOSSetupAssistant ( resolveApplyRelativePath ( baseDir , setup . MacOSSetupAssistant . Value ) )
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying teams: %w" , err )
2023-04-25 13:36:01 +00:00
}
tmMacSetupAssistants [ k ] = b
}
2023-04-07 20:31:02 +00:00
}
2023-10-10 22:00:45 +00:00
tmScripts := extractTmSpecsScripts ( specs . Teams )
tmScriptsPayloads := make ( map [ string ] [ ] fleet . ScriptPayload , len ( tmScripts ) )
for k , paths := range tmScripts {
files := resolveApplyRelativePaths ( baseDir , paths )
scriptPayloads := make ( [ ] fleet . ScriptPayload , len ( files ) )
for i , f := range files {
b , err := os . ReadFile ( f )
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying fleet config: %w" , err )
2023-10-10 22:00:45 +00:00
}
scriptPayloads [ i ] = fleet . ScriptPayload {
ScriptContents : b ,
Name : filepath . Base ( f ) ,
}
}
tmScriptsPayloads [ k ] = scriptPayloads
}
2023-02-15 18:01:44 +00:00
// Next, apply the teams specs before saving the profiles, so that any
// non-existing team gets created.
2024-02-09 19:34:57 +00:00
var err error
teamIDsByName , err = c . ApplyTeams ( specs . Teams , opts )
2023-06-07 17:29:36 +00:00
if err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying teams: %w" , err )
2022-08-05 22:07:32 +00:00
}
2023-02-15 18:01:44 +00:00
if len ( tmFileContents ) > 0 {
for tmName , profs := range tmFileContents {
2024-02-09 19:34:57 +00:00
teamID , ok := teamIDsByName [ tmName ]
if opts . DryRun && ( teamID == 0 || ! ok ) {
logfn ( "[+] would've applied MDM profiles for new team %s\n" , tmName )
} else {
logfn ( "[+] applying MDM profiles for team %s\n" , tmName )
if err := c . ApplyTeamProfiles ( tmName , profs , opts ) ; err != nil {
return nil , fmt . Errorf ( "applying custom settings for team %q: %w" , tmName , err )
}
2023-02-15 18:01:44 +00:00
}
}
}
2023-04-25 13:36:01 +00:00
if len ( tmBootstrapPackages ) + len ( tmMacSetupAssistants ) > 0 && ! opts . DryRun {
2023-06-07 17:29:36 +00:00
for tmName , tmID := range teamIDsByName {
if bp , ok := tmBootstrapPackages [ tmName ] ; ok {
if err := c . EnsureBootstrapPackage ( bp , tmID ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "uploading bootstrap package for team %q: %w" , tmName , err )
2023-04-07 20:31:02 +00:00
}
}
2023-06-07 17:29:36 +00:00
if b , ok := tmMacSetupAssistants [ tmName ] ; ok {
if err := c . uploadMacOSSetupAssistant ( b , & tmID , tmMacSetup [ tmName ] . MacOSSetupAssistant . Value ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "uploading macOS setup assistant for team %q: %w" , tmName , err )
2023-04-25 13:36:01 +00:00
}
}
2023-04-07 20:31:02 +00:00
}
}
2023-10-10 22:00:45 +00:00
if len ( tmScriptsPayloads ) > 0 {
for tmName , scripts := range tmScriptsPayloads {
if err := c . ApplyTeamScripts ( tmName , scripts , opts ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying scripts for team %q: %w" , tmName , err )
2023-10-10 22:00:45 +00:00
}
}
}
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[+] would've applied %d teams\n" , len ( specs . Teams ) )
} else {
logfn ( "[+] applied %d teams\n" , len ( specs . Teams ) )
}
2022-08-05 22:07:32 +00:00
}
if specs . UsersRoles != nil {
2022-09-19 17:53:44 +00:00
if opts . DryRun {
logfn ( "[!] ignoring user roles, dry run mode only supported for 'config' and 'team' specs\n" )
} else {
if err := c . ApplyUsersRoleSecretSpec ( specs . UsersRoles ) ; err != nil {
2024-02-09 19:34:57 +00:00
return nil , fmt . Errorf ( "applying user roles: %w" , err )
2022-09-19 17:53:44 +00:00
}
logfn ( "[+] applied user roles\n" )
2022-08-05 22:07:32 +00:00
}
}
2024-02-09 19:34:57 +00:00
return teamIDsByName , nil
2022-08-05 22:07:32 +00:00
}
2023-02-15 18:01:44 +00:00
2023-04-07 20:31:02 +00:00
func extractAppCfgMacOSSetup ( appCfg any ) * fleet . MacOSSetup {
asMap , ok := appCfg . ( map [ string ] interface { } )
if ! ok {
return nil
}
mmdm , ok := asMap [ "mdm" ] . ( map [ string ] interface { } )
if ! ok {
return nil
}
mos , ok := mmdm [ "macos_setup" ] . ( map [ string ] interface { } )
if ! ok || mos == nil {
return nil
}
2023-04-25 13:36:01 +00:00
bp , _ := mos [ "bootstrap_package" ] . ( string ) // if not a string, bp == ""
msa , _ := mos [ "macos_setup_assistant" ] . ( string )
2023-04-07 20:31:02 +00:00
return & fleet . MacOSSetup {
2023-04-26 21:09:21 +00:00
BootstrapPackage : optjson . SetString ( bp ) ,
2023-04-25 13:36:01 +00:00
MacOSSetupAssistant : optjson . SetString ( msa ) ,
2023-04-07 20:31:02 +00:00
}
}
2023-04-25 13:36:01 +00:00
func resolveApplyRelativePath ( baseDir , path string ) string {
return resolveApplyRelativePaths ( baseDir , [ ] string { path } ) [ 0 ]
}
// resolves the paths to an absolute path relative to the baseDir, which should
// be the path of the YAML file where the relative paths were specified. If the
// path is already absolute, it is left untouched.
func resolveApplyRelativePaths ( baseDir string , paths [ ] string ) [ ] string {
2023-02-15 18:01:44 +00:00
if baseDir == "" {
return paths
}
resolved := make ( [ ] string , 0 , len ( paths ) )
for _ , p := range paths {
if ! filepath . IsAbs ( p ) {
p = filepath . Join ( baseDir , p )
}
resolved = append ( resolved , p )
}
return resolved
}
2024-01-26 16:00:58 +00:00
func extractAppCfgCustomSettings ( appCfg interface { } , platformKey string ) [ ] fleet . MDMProfileSpec {
2023-02-15 18:01:44 +00:00
asMap , ok := appCfg . ( map [ string ] interface { } )
if ! ok {
return nil
}
2023-02-28 20:34:46 +00:00
mmdm , ok := asMap [ "mdm" ] . ( map [ string ] interface { } )
if ! ok {
return nil
}
2024-01-26 16:00:58 +00:00
mos , ok := mmdm [ platformKey ] . ( map [ string ] interface { } )
2023-02-15 18:01:44 +00:00
if ! ok || mos == nil {
return nil
}
cs , ok := mos [ "custom_settings" ]
if ! ok {
// custom settings is not present
return nil
}
csAny , ok := cs . ( [ ] interface { } )
if ! ok || csAny == nil {
// return a non-nil, empty slice instead, so the caller knows that the
// custom_settings key was actually provided.
2024-01-26 16:00:58 +00:00
return [ ] fleet . MDMProfileSpec { }
2023-02-15 18:01:44 +00:00
}
2024-01-26 16:00:58 +00:00
csSpecs := make ( [ ] fleet . MDMProfileSpec , 0 , len ( csAny ) )
2023-02-15 18:01:44 +00:00
for _ , v := range csAny {
2024-01-26 16:00:58 +00:00
if m , ok := v . ( map [ string ] interface { } ) ; ok {
var profSpec fleet . MDMProfileSpec
2023-02-15 18:01:44 +00:00
2024-01-26 16:00:58 +00:00
// extract the Path field
if path , ok := m [ "path" ] . ( string ) ; ok {
profSpec . Path = path
}
2023-11-29 14:32:42 +00:00
2024-01-26 16:00:58 +00:00
// extract the Labels field, labels are cleared if not provided
if labels , ok := m [ "labels" ] . ( [ ] interface { } ) ; ok {
for _ , label := range labels {
if strLabel , ok := label . ( string ) ; ok {
profSpec . Labels = append ( profSpec . Labels , strLabel )
}
}
}
2023-11-29 14:32:42 +00:00
2024-01-26 16:00:58 +00:00
if profSpec . Path != "" {
csSpecs = append ( csSpecs , profSpec )
}
} else if m , ok := v . ( string ) ; ok { // for backwards compatibility with the old way to define profiles
if m != "" {
csSpecs = append ( csSpecs , fleet . MDMProfileSpec { Path : m } )
}
2023-11-29 14:32:42 +00:00
}
}
2024-01-26 16:00:58 +00:00
return csSpecs
}
func extractAppCfgMacOSCustomSettings ( appCfg interface { } ) [ ] fleet . MDMProfileSpec {
return extractAppCfgCustomSettings ( appCfg , "macos_settings" )
}
func extractAppCfgWindowsCustomSettings ( appCfg interface { } ) [ ] fleet . MDMProfileSpec {
return extractAppCfgCustomSettings ( appCfg , "windows_settings" )
2023-11-29 14:32:42 +00:00
}
2023-11-15 21:04:24 +00:00
2023-10-10 22:00:45 +00:00
func extractAppCfgScripts ( appCfg interface { } ) [ ] string {
asMap , ok := appCfg . ( map [ string ] interface { } )
if ! ok {
return nil
}
scripts , ok := asMap [ "scripts" ]
if ! ok {
// scripts is not present
return nil
}
scriptsAny , ok := scripts . ( [ ] interface { } )
if ! ok || scriptsAny == nil {
// return a non-nil, empty slice instead, so the caller knows that the
// scripts key was actually provided.
return [ ] string { }
}
scriptsStrings := make ( [ ] string , 0 , len ( scriptsAny ) )
for _ , v := range scriptsAny {
s , _ := v . ( string )
if s != "" {
scriptsStrings = append ( scriptsStrings , s )
}
}
return scriptsStrings
}
2023-11-15 21:04:24 +00:00
// returns the custom macOS and Windows settings keyed by team name.
2024-01-26 16:00:58 +00:00
func extractTmSpecsMDMCustomSettings ( tmSpecs [ ] json . RawMessage ) map [ string ] [ ] fleet . MDMProfileSpec {
var m map [ string ] [ ] fleet . MDMProfileSpec
2023-02-15 18:01:44 +00:00
for _ , tm := range tmSpecs {
var spec struct {
2023-02-28 20:34:46 +00:00
Name string ` json:"name" `
MDM struct {
MacOSSettings struct {
CustomSettings json . RawMessage ` json:"custom_settings" `
} ` json:"macos_settings" `
2023-11-15 21:04:24 +00:00
WindowsSettings struct {
2023-11-29 14:32:42 +00:00
CustomSettings json . RawMessage ` json:"custom_settings" `
2023-11-15 21:04:24 +00:00
} ` json:"windows_settings" `
2023-02-28 20:34:46 +00:00
} ` json:"mdm" `
2023-02-15 18:01:44 +00:00
}
if err := json . Unmarshal ( tm , & spec ) ; err != nil {
// ignore, this will fail in the call to apply team specs
continue
}
2024-02-27 18:55:05 +00:00
spec . Name = norm . NFC . String ( spec . Name )
2023-11-15 21:04:24 +00:00
if spec . Name != "" {
2024-01-26 16:00:58 +00:00
var macOSSettings [ ] fleet . MDMProfileSpec
var windowsSettings [ ] fleet . MDMProfileSpec
2023-11-15 21:04:24 +00:00
// to keep existing bahavior, if any of the custom
// settings is provided, make the map a non-nil map
if len ( spec . MDM . MacOSSettings . CustomSettings ) > 0 ||
len ( spec . MDM . WindowsSettings . CustomSettings ) > 0 {
if m == nil {
2024-01-26 16:00:58 +00:00
m = make ( map [ string ] [ ] fleet . MDMProfileSpec )
2023-11-15 21:04:24 +00:00
}
}
if len ( spec . MDM . MacOSSettings . CustomSettings ) > 0 {
if err := json . Unmarshal ( spec . MDM . MacOSSettings . CustomSettings , & macOSSettings ) ; err != nil {
// ignore, will fail in apply team specs call
continue
}
if macOSSettings == nil {
// to be consistent with the AppConfig custom settings, set it to an
// empty slice if the provided custom settings are present but empty.
2024-01-26 16:00:58 +00:00
macOSSettings = [ ] fleet . MDMProfileSpec { }
2023-11-15 21:04:24 +00:00
}
2023-02-15 18:01:44 +00:00
}
2023-11-15 21:04:24 +00:00
if len ( spec . MDM . WindowsSettings . CustomSettings ) > 0 {
if err := json . Unmarshal ( spec . MDM . WindowsSettings . CustomSettings , & windowsSettings ) ; err != nil {
// ignore, will fail in apply team specs call
continue
}
if windowsSettings == nil {
// to be consistent with the AppConfig custom settings, set it to an
// empty slice if the provided custom settings are present but empty.
2024-01-26 16:00:58 +00:00
windowsSettings = [ ] fleet . MDMProfileSpec { }
2023-11-15 21:04:24 +00:00
}
2023-02-15 18:01:44 +00:00
}
2023-11-15 21:04:24 +00:00
2024-01-26 16:00:58 +00:00
// TODO: validate equal names here and API?
2023-11-15 21:04:24 +00:00
if macOSSettings != nil || windowsSettings != nil {
m [ spec . Name ] = append ( macOSSettings , windowsSettings ... )
2023-02-15 18:01:44 +00:00
}
}
}
return m
}
2023-04-07 20:31:02 +00:00
2023-10-10 22:00:45 +00:00
func extractTmSpecsScripts ( tmSpecs [ ] json . RawMessage ) map [ string ] [ ] string {
var m map [ string ] [ ] string
for _ , tm := range tmSpecs {
var spec struct {
Name string ` json:"name" `
Scripts json . RawMessage ` json:"scripts" `
}
if err := json . Unmarshal ( tm , & spec ) ; err != nil {
// ignore, this will fail in the call to apply team specs
continue
}
2024-02-27 18:55:05 +00:00
spec . Name = norm . NFC . String ( spec . Name )
2023-10-10 22:00:45 +00:00
if spec . Name != "" && len ( spec . Scripts ) > 0 {
if m == nil {
m = make ( map [ string ] [ ] string )
}
var scripts [ ] string
if err := json . Unmarshal ( spec . Scripts , & scripts ) ; err != nil {
// ignore, will fail in apply team specs call
continue
}
if scripts == nil {
// to be consistent with the AppConfig custom settings, set it to an
// empty slice if the provided custom settings are present but empty.
scripts = [ ] string { }
}
m [ spec . Name ] = scripts
}
}
return m
}
2023-04-07 20:31:02 +00:00
// returns the macos_setup keyed by team name.
func extractTmSpecsMacOSSetup ( tmSpecs [ ] json . RawMessage ) map [ string ] * fleet . MacOSSetup {
var m map [ string ] * fleet . MacOSSetup
for _ , tm := range tmSpecs {
var spec struct {
Name string ` json:"name" `
MDM struct {
MacOSSetup fleet . MacOSSetup ` json:"macos_setup" `
} ` json:"mdm" `
}
if err := json . Unmarshal ( tm , & spec ) ; err != nil {
// ignore, this will fail in the call to apply team specs
continue
}
2024-02-27 18:55:05 +00:00
spec . Name = norm . NFC . String ( spec . Name )
2023-04-07 20:31:02 +00:00
if spec . Name != "" {
if m == nil {
m = make ( map [ string ] * fleet . MacOSSetup )
}
m [ spec . Name ] = & spec . MDM . MacOSSetup
}
}
return m
}
2024-02-09 19:34:57 +00:00
// DoGitOps applies the GitOps config to Fleet.
func ( c * Client ) DoGitOps (
ctx context . Context ,
config * spec . GitOps ,
baseDir string ,
logf func ( format string , args ... interface { } ) ,
dryRun bool ,
2024-03-07 19:20:14 +00:00
appConfig * fleet . EnrichedAppConfig ,
2024-02-09 19:34:57 +00:00
) error {
var err error
logFn := func ( format string , args ... interface { } ) {
if logf != nil {
logf ( format , args ... )
}
}
group := spec . Group { }
scripts := make ( [ ] interface { } , len ( config . Controls . Scripts ) )
for i , script := range config . Controls . Scripts {
scripts [ i ] = * script . Path
}
var mdmAppConfig map [ string ] interface { }
var team map [ string ] interface { }
if config . TeamName == nil {
group . AppConfig = config . OrgSettings
group . EnrollSecret = & fleet . EnrollSecretSpec { Secrets : config . OrgSettings [ "secrets" ] . ( [ ] * fleet . EnrollSecret ) }
group . AppConfig . ( map [ string ] interface { } ) [ "agent_options" ] = config . AgentOptions
delete ( config . OrgSettings , "secrets" ) // secrets are applied separately in Client.ApplyGroup
2024-03-13 15:05:46 +00:00
// Integrations
var integrations interface { }
var ok bool
if integrations , ok = group . AppConfig . ( map [ string ] interface { } ) [ "integrations" ] ; ! ok || integrations == nil {
integrations = map [ string ] interface { } { }
group . AppConfig . ( map [ string ] interface { } ) [ "integrations" ] = integrations
}
if jira , ok := integrations . ( map [ string ] interface { } ) [ "jira" ] ; ! ok || jira == nil {
integrations . ( map [ string ] interface { } ) [ "jira" ] = [ ] interface { } { }
}
if zendesk , ok := integrations . ( map [ string ] interface { } ) [ "zendesk" ] ; ! ok || zendesk == nil {
integrations . ( map [ string ] interface { } ) [ "zendesk" ] = [ ] interface { } { }
2024-02-09 19:34:57 +00:00
}
2024-03-13 15:05:46 +00:00
if googleCal , ok := integrations . ( map [ string ] interface { } ) [ "google_calendar" ] ; ! ok || googleCal == nil {
integrations . ( map [ string ] interface { } ) [ "google_calendar" ] = [ ] interface { } { }
}
2024-02-12 23:27:48 +00:00
// Ensure mdm config exists
mdmConfig , ok := group . AppConfig . ( map [ string ] interface { } ) [ "mdm" ]
if ! ok || mdmConfig == nil {
mdmConfig = map [ string ] interface { } { }
group . AppConfig . ( map [ string ] interface { } ) [ "mdm" ] = mdmConfig
}
mdmAppConfig , ok = mdmConfig . ( map [ string ] interface { } )
if ! ok {
return errors . New ( "org_settings.mdm config is not a map" )
}
2024-03-07 19:20:14 +00:00
// Put in default values for macos_migration
if config . Controls . MacOSMigration != nil {
mdmAppConfig [ "macos_migration" ] = config . Controls . MacOSMigration
} else {
mdmAppConfig [ "macos_migration" ] = map [ string ] interface { } { }
}
macOSMigration := mdmAppConfig [ "macos_migration" ] . ( map [ string ] interface { } )
if enable , ok := macOSMigration [ "enable" ] ; ! ok || enable == nil {
macOSMigration [ "enable" ] = false
}
// Put in default values for windows_enabled_and_configured
2024-02-09 19:34:57 +00:00
mdmAppConfig [ "windows_enabled_and_configured" ] = config . Controls . WindowsEnabledAndConfigured
2024-03-07 19:20:14 +00:00
if config . Controls . WindowsEnabledAndConfigured != nil {
mdmAppConfig [ "windows_enabled_and_configured" ] = config . Controls . WindowsEnabledAndConfigured
} else {
mdmAppConfig [ "windows_enabled_and_configured" ] = false
}
2024-02-09 19:34:57 +00:00
group . AppConfig . ( map [ string ] interface { } ) [ "scripts" ] = scripts
} else {
team = make ( map [ string ] interface { } )
team [ "name" ] = * config . TeamName
team [ "agent_options" ] = config . AgentOptions
if hostExpirySettings , ok := config . TeamSettings [ "host_expiry_settings" ] ; ok {
team [ "host_expiry_settings" ] = hostExpirySettings
}
if features , ok := config . TeamSettings [ "features" ] ; ok {
team [ "features" ] = features
}
team [ "scripts" ] = scripts
team [ "secrets" ] = config . TeamSettings [ "secrets" ]
Enabling setting host status webhook at the team level via REST API and fleetctl apply/gitops. (#17186)
Enabling setting host status webhook at the team level via REST API and
fleetctl apply/gitops.
#14916
Example payload:
```json
{
"data": {
"days_unseen": 3,
"host_ids": [
10724,
10726,
10738,
10739,
10740,
10741,
10742,
10744,
10745,
10746,
10747,
10748,
10749
],
"team_id": 3,
"total_hosts": 15,
"unseen_hosts": 13
},
"text": "More than 86.67% of your hosts have not checked into Fleet for more than 3 days. You've been sent this message because the Host status webhook is enabled in your Fleet instance."
}
```
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
2024-03-04 18:35:27 +00:00
team [ "webhook_settings" ] = map [ string ] interface { } { }
clearHostStatusWebhook := true
if webhookSettings , ok := config . TeamSettings [ "webhook_settings" ] ; ok {
if hostStatusWebhook , ok := webhookSettings . ( map [ string ] interface { } ) [ "host_status_webhook" ] ; ok {
clearHostStatusWebhook = false
team [ "webhook_settings" ] . ( map [ string ] interface { } ) [ "host_status_webhook" ] = hostStatusWebhook
}
}
if clearHostStatusWebhook {
// Clear out any existing host_status_webhook settings
team [ "webhook_settings" ] . ( map [ string ] interface { } ) [ "host_status_webhook" ] = map [ string ] interface { } { }
}
2024-03-13 15:05:46 +00:00
// Integrations
var integrations interface { }
var ok bool
if integrations , ok = config . TeamSettings [ "integrations" ] ; ! ok || integrations == nil {
integrations = map [ string ] interface { } { }
}
team [ "integrations" ] = integrations
_ , ok = integrations . ( map [ string ] interface { } )
if ! ok {
return errors . New ( "team_settings.integrations config is not a map" )
}
2024-03-15 00:00:51 +00:00
if googleCal , ok := integrations . ( map [ string ] interface { } ) [ "google_calendar" ] ; ! ok || googleCal == nil {
integrations . ( map [ string ] interface { } ) [ "google_calendar" ] = map [ string ] interface { } { }
} else {
_ , ok = googleCal . ( map [ string ] interface { } )
2024-03-13 15:05:46 +00:00
if ! ok {
return errors . New ( "team_settings.integrations.google_calendar config is not a map" )
}
}
2024-02-09 19:34:57 +00:00
team [ "mdm" ] = map [ string ] interface { } { }
mdmAppConfig = team [ "mdm" ] . ( map [ string ] interface { } )
}
// Common controls settings between org and team settings
2024-03-07 19:20:14 +00:00
// Put in default values for macos_settings
if config . Controls . MacOSSettings != nil {
mdmAppConfig [ "macos_settings" ] = config . Controls . MacOSSettings
} else {
mdmAppConfig [ "macos_settings" ] = map [ string ] interface { } { }
}
macOSSettings := mdmAppConfig [ "macos_settings" ] . ( map [ string ] interface { } )
if customSettings , ok := macOSSettings [ "custom_settings" ] ; ! ok || customSettings == nil {
macOSSettings [ "custom_settings" ] = [ ] interface { } { }
}
// Put in default values for macos_updates
if config . Controls . MacOSUpdates != nil {
mdmAppConfig [ "macos_updates" ] = config . Controls . MacOSUpdates
} else {
mdmAppConfig [ "macos_updates" ] = map [ string ] interface { } { }
}
macOSUpdates := mdmAppConfig [ "macos_updates" ] . ( map [ string ] interface { } )
if minimumVersion , ok := macOSUpdates [ "minimum_version" ] ; ! ok || minimumVersion == nil {
macOSUpdates [ "minimum_version" ] = ""
}
if deadline , ok := macOSUpdates [ "deadline" ] ; ! ok || deadline == nil {
macOSUpdates [ "deadline" ] = ""
}
// Put in default values for macos_setup
if config . Controls . MacOSSetup != nil {
mdmAppConfig [ "macos_setup" ] = config . Controls . MacOSSetup
} else {
mdmAppConfig [ "macos_setup" ] = map [ string ] interface { } { }
}
macOSSetup := mdmAppConfig [ "macos_setup" ] . ( map [ string ] interface { } )
if bootstrapPackage , ok := macOSSetup [ "bootstrap_package" ] ; ! ok || bootstrapPackage == nil {
macOSSetup [ "bootstrap_package" ] = ""
}
if enableEndUserAuthentication , ok := macOSSetup [ "enable_end_user_authentication" ] ; ! ok || enableEndUserAuthentication == nil {
macOSSetup [ "enable_end_user_authentication" ] = false
}
if macOSSetupAssistant , ok := macOSSetup [ "macos_setup_assistant" ] ; ! ok || macOSSetupAssistant == nil {
macOSSetup [ "macos_setup_assistant" ] = ""
}
// Put in default values for windows_settings
if config . Controls . WindowsSettings != nil {
mdmAppConfig [ "windows_settings" ] = config . Controls . WindowsSettings
} else {
mdmAppConfig [ "windows_settings" ] = map [ string ] interface { } { }
}
windowsSettings := mdmAppConfig [ "windows_settings" ] . ( map [ string ] interface { } )
if customSettings , ok := windowsSettings [ "custom_settings" ] ; ! ok || customSettings == nil {
windowsSettings [ "custom_settings" ] = [ ] interface { } { }
}
// Put in default values for windows_updates
if config . Controls . WindowsUpdates != nil {
mdmAppConfig [ "windows_updates" ] = config . Controls . WindowsUpdates
} else {
mdmAppConfig [ "windows_updates" ] = map [ string ] interface { } { }
}
if appConfig . License . IsPremium ( ) {
windowsUpdates := mdmAppConfig [ "windows_updates" ] . ( map [ string ] interface { } )
if deadlineDays , ok := windowsUpdates [ "deadline_days" ] ; ! ok || deadlineDays == nil {
windowsUpdates [ "deadline_days" ] = 0
}
if gracePeriodDays , ok := windowsUpdates [ "grace_period_days" ] ; ! ok || gracePeriodDays == nil {
windowsUpdates [ "grace_period_days" ] = 0
}
}
// Put in default value for enable_disk_encryption
if config . Controls . EnableDiskEncryption != nil {
mdmAppConfig [ "enable_disk_encryption" ] = config . Controls . EnableDiskEncryption
} else {
mdmAppConfig [ "enable_disk_encryption" ] = false
}
2024-02-09 19:34:57 +00:00
if config . TeamName != nil {
rawTeam , err := json . Marshal ( team )
if err != nil {
return fmt . Errorf ( "error marshalling team spec: %w" , err )
}
group . Teams = [ ] json . RawMessage { rawTeam }
}
// Apply org settings, scripts, enroll secrets, and controls
teamIDsByName , err := c . ApplyGroup ( ctx , & group , baseDir , logf , fleet . ApplySpecOptions { DryRun : dryRun } )
if err != nil {
return err
}
if config . TeamName != nil {
teamID , ok := teamIDsByName [ * config . TeamName ]
if ! ok || teamID == 0 {
if dryRun {
logFn ( "[+] would've added any policies/queries to new team %s\n" , * config . TeamName )
return nil
}
return fmt . Errorf ( "team %s not created" , * config . TeamName )
}
config . TeamID = & teamID
}
err = c . doGitOpsPolicies ( config , logFn , dryRun )
if err != nil {
return err
}
2024-03-13 15:05:46 +00:00
2024-02-09 19:34:57 +00:00
err = c . doGitOpsQueries ( config , logFn , dryRun )
if err != nil {
return err
}
return nil
}
func ( c * Client ) doGitOpsPolicies ( config * spec . GitOps , logFn func ( format string , args ... interface { } ) , dryRun bool ) error {
// Get the ids and names of current policies to figure out which ones to delete
policies , err := c . GetPolicies ( config . TeamID )
if err != nil {
return fmt . Errorf ( "error getting current policies: %w" , err )
}
if len ( config . Policies ) > 0 {
numPolicies := len ( config . Policies )
logFn ( "[+] syncing %d policies\n" , numPolicies )
if ! dryRun {
// Note: We are reusing the spec flow here for adding/updating policies, instead of creating a new flow for GitOps.
if err := c . ApplyPolicies ( config . Policies ) ; err != nil {
return fmt . Errorf ( "error applying policies: %w" , err )
}
logFn ( "[+] synced %d policies\n" , numPolicies )
}
}
var policiesToDelete [ ] uint
for _ , oldItem := range policies {
found := false
for _ , newItem := range config . Policies {
if oldItem . Name == newItem . Name {
found = true
break
}
}
if ! found {
policiesToDelete = append ( policiesToDelete , oldItem . ID )
fmt . Printf ( "[-] deleting policy %s\n" , oldItem . Name )
}
}
if len ( policiesToDelete ) > 0 {
logFn ( "[-] deleting %d policies\n" , len ( policiesToDelete ) )
if ! dryRun {
if err := c . DeletePolicies ( config . TeamID , policiesToDelete ) ; err != nil {
return fmt . Errorf ( "error deleting policies: %w" , err )
}
}
}
return nil
}
func ( c * Client ) doGitOpsQueries ( config * spec . GitOps , logFn func ( format string , args ... interface { } ) , dryRun bool ) error {
// Get the ids and names of current queries to figure out which ones to delete
queries , err := c . GetQueries ( config . TeamID , nil )
if err != nil {
return fmt . Errorf ( "error getting current queries: %w" , err )
}
if len ( config . Queries ) > 0 {
numQueries := len ( config . Queries )
logFn ( "[+] syncing %d queries\n" , numQueries )
if ! dryRun {
// Note: We are reusing the spec flow here for adding/updating queries, instead of creating a new flow for GitOps.
if err := c . ApplyQueries ( config . Queries ) ; err != nil {
return fmt . Errorf ( "error applying queries: %w" , err )
}
logFn ( "[+] synced %d queries\n" , numQueries )
}
}
var queriesToDelete [ ] uint
for _ , oldQuery := range queries {
found := false
for _ , newQuery := range config . Queries {
if oldQuery . Name == newQuery . Name {
found = true
break
}
}
if ! found {
queriesToDelete = append ( queriesToDelete , oldQuery . ID )
fmt . Printf ( "[-] deleting query %s\n" , oldQuery . Name )
}
}
if len ( queriesToDelete ) > 0 {
logFn ( "[-] deleting %d queries\n" , len ( queriesToDelete ) )
if ! dryRun {
if err := c . DeleteQueries ( queriesToDelete ) ; err != nil {
return fmt . Errorf ( "error deleting queries: %w" , err )
}
}
}
return nil
}