2022-01-25 14:34:00 +00:00
package service
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
2022-10-12 21:10:50 +00:00
"fmt"
2022-01-25 14:34:00 +00:00
"io"
"net"
2022-10-12 21:10:50 +00:00
"net/http"
2022-01-25 14:34:00 +00:00
"net/url"
2022-11-22 17:26:36 +00:00
"os"
2022-01-25 14:34:00 +00:00
2022-08-24 12:32:45 +00:00
"github.com/fleetdm/fleet/v4/server/authz"
2022-03-16 14:15:25 +00:00
authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
2022-01-25 14:34:00 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
2022-09-19 17:53:44 +00:00
"github.com/go-kit/kit/log/level"
2022-01-25 14:34:00 +00:00
"github.com/kolide/kit/version"
)
////////////////////////////////////////////////////////////////////////////////
// Get AppConfig
////////////////////////////////////////////////////////////////////////////////
type appConfigResponse struct {
fleet . AppConfig
2022-08-24 21:40:09 +00:00
appConfigResponseFields
}
2022-01-25 14:34:00 +00:00
2022-08-24 21:40:09 +00:00
// appConfigResponseFields are grouped separately to aid with JSON unmarshaling
type appConfigResponseFields struct {
2022-01-25 14:34:00 +00:00
UpdateInterval * fleet . UpdateIntervalConfig ` json:"update_interval" `
Vulnerabilities * fleet . VulnerabilitiesConfig ` json:"vulnerabilities" `
// License is loaded from the service
License * fleet . LicenseInfo ` json:"license,omitempty" `
// Logging is loaded on the fly rather than from the database.
Logging * fleet . Logging ` json:"logging,omitempty" `
2022-07-12 21:21:15 +00:00
// SandboxEnabled is true if fleet serve was ran with server.sandbox_enabled=true
SandboxEnabled bool ` json:"sandbox_enabled,omitempty" `
2022-11-22 17:26:36 +00:00
// MDMEnabled is true if fleet serve was ran with FLEET_DEV_MDM_ENABLED=1
//
// This is used only for UI development, for more details check
// https://github.com/fleetdm/fleet/issues/8751
//
// TODO: remove this flag once the MDM feature is ready for
// release.
MDMEnabled bool ` json:"mdm_enabled,omitempty" `
2022-07-12 21:21:15 +00:00
Err error ` json:"error,omitempty" `
2022-01-25 14:34:00 +00:00
}
2022-08-24 21:40:09 +00:00
// UnmarshalJSON implements the json.Unmarshaler interface to make sure we serialize
// both AppConfig and appConfigResponseFields properly:
//
// - If this function is not defined, AppConfig.UnmarshalJSON gets promoted and
// will be called instead.
// - If we try to unmarshal everything in one go, AppConfig.UnmarshalJSON doesn't get
// called.
func ( r * appConfigResponse ) UnmarshalJSON ( data [ ] byte ) error {
if err := json . Unmarshal ( data , & r . AppConfig ) ; err != nil {
return err
}
if err := json . Unmarshal ( data , & r . appConfigResponseFields ) ; err != nil {
return err
}
return nil
}
2022-01-25 14:34:00 +00:00
func ( r appConfigResponse ) error ( ) error { return r . Err }
2022-12-27 14:26:59 +00:00
func getAppConfigEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( errorer , error ) {
2022-01-25 14:34:00 +00:00
vc , ok := viewer . FromContext ( ctx )
if ! ok {
return nil , errors . New ( "could not fetch user" )
}
config , err := svc . AppConfig ( ctx )
if err != nil {
return nil , err
}
license , err := svc . License ( ctx )
if err != nil {
return nil , err
}
loggingConfig , err := svc . LoggingConfig ( ctx )
if err != nil {
return nil , err
}
updateIntervalConfig , err := svc . UpdateIntervalConfig ( ctx )
if err != nil {
return nil , err
}
vulnConfig , err := svc . VulnerabilitiesConfig ( ctx )
if err != nil {
return nil , err
}
var smtpSettings fleet . SMTPSettings
var ssoSettings fleet . SSOSettings
var hostExpirySettings fleet . HostExpirySettings
var agentOptions * json . RawMessage
// only admin can see smtp, sso, and host expiry settings
if vc . User . GlobalRole != nil && * vc . User . GlobalRole == fleet . RoleAdmin {
smtpSettings = config . SMTPSettings
ssoSettings = config . SSOSettings
hostExpirySettings = config . HostExpirySettings
agentOptions = config . AgentOptions
}
2022-06-10 15:39:02 +00:00
transparencyURL := fleet . DefaultTransparencyURL
// Fleet Premium license is required for custom transparency url
2022-08-10 18:15:35 +00:00
if license . IsPremium ( ) && config . FleetDesktop . TransparencyURL != "" {
2022-06-10 15:39:02 +00:00
transparencyURL = config . FleetDesktop . TransparencyURL
}
fleetDesktop := fleet . FleetDesktopSettings { TransparencyURL : transparencyURL }
2022-08-25 16:41:50 +00:00
features := config . Features
2022-01-25 14:34:00 +00:00
response := appConfigResponse {
AppConfig : fleet . AppConfig {
OrgInfo : config . OrgInfo ,
ServerSettings : config . ServerSettings ,
2022-08-25 16:41:50 +00:00
Features : features ,
2022-01-25 14:34:00 +00:00
VulnerabilitySettings : config . VulnerabilitySettings ,
SMTPSettings : smtpSettings ,
SSOSettings : ssoSettings ,
HostExpirySettings : hostExpirySettings ,
AgentOptions : agentOptions ,
2022-06-10 15:39:02 +00:00
FleetDesktop : fleetDesktop ,
2022-01-25 14:34:00 +00:00
WebhookSettings : config . WebhookSettings ,
2022-03-30 13:10:02 +00:00
Integrations : config . Integrations ,
2023-01-06 20:44:20 +00:00
MDM : config . MDM ,
2022-01-25 14:34:00 +00:00
} ,
2022-08-24 21:40:09 +00:00
appConfigResponseFields : appConfigResponseFields {
UpdateInterval : updateIntervalConfig ,
Vulnerabilities : vulnConfig ,
License : license ,
Logging : loggingConfig ,
SandboxEnabled : svc . SandboxEnabled ( ) ,
2022-11-22 17:26:36 +00:00
// Undocumented feature flag for MDM, used only for UI development, for
// more details check https://github.com/fleetdm/fleet/issues/8751
//
// TODO: remove this flag once the MDM feature is ready for
// release.
MDMEnabled : os . Getenv ( "FLEET_DEV_MDM_ENABLED" ) == "1" ,
2022-08-24 21:40:09 +00:00
} ,
2022-01-25 14:34:00 +00:00
}
return response , nil
}
2022-07-12 21:21:15 +00:00
func ( svc * Service ) SandboxEnabled ( ) bool {
return svc . config . Server . SandboxEnabled
}
2022-01-25 14:34:00 +00:00
func ( svc * Service ) AppConfig ( ctx context . Context ) ( * fleet . AppConfig , error ) {
2022-03-16 14:15:25 +00:00
if ! svc . authz . IsAuthenticatedWith ( ctx , authz_ctx . AuthnDeviceToken ) {
if err := svc . authz . Authorize ( ctx , & fleet . AppConfig { } , fleet . ActionRead ) ; err != nil {
return nil , err
}
2022-01-25 14:34:00 +00:00
}
2022-04-18 20:55:51 +00:00
ac , err := svc . ds . AppConfig ( ctx )
if err != nil {
return nil , err
}
if ac . SMTPSettings . SMTPPassword != "" {
2022-06-06 14:41:51 +00:00
ac . SMTPSettings . SMTPPassword = fleet . MaskedPassword
2022-04-18 20:55:51 +00:00
}
for _ , jiraIntegration := range ac . Integrations . Jira {
2022-06-06 14:41:51 +00:00
jiraIntegration . APIToken = fleet . MaskedPassword
2022-04-18 20:55:51 +00:00
}
2022-05-02 20:58:34 +00:00
for _ , zdIntegration := range ac . Integrations . Zendesk {
2022-06-06 14:41:51 +00:00
zdIntegration . APIToken = fleet . MaskedPassword
2022-05-02 20:58:34 +00:00
}
2022-04-18 20:55:51 +00:00
return ac , nil
2022-01-25 14:34:00 +00:00
}
////////////////////////////////////////////////////////////////////////////////
// Modify AppConfig
////////////////////////////////////////////////////////////////////////////////
type modifyAppConfigRequest struct {
2022-09-19 17:53:44 +00:00
Force bool ` json:"-" query:"force,optional" ` // if true, bypass strict incoming json validation
DryRun bool ` json:"-" query:"dry_run,optional" ` // if true, apply validation but do not save changes
2022-01-25 14:34:00 +00:00
json . RawMessage
}
2022-12-27 14:26:59 +00:00
func modifyAppConfigEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( errorer , error ) {
2022-01-25 14:34:00 +00:00
req := request . ( * modifyAppConfigRequest )
2022-09-19 17:53:44 +00:00
config , err := svc . ModifyAppConfig ( ctx , req . RawMessage , fleet . ApplySpecOptions {
Force : req . Force ,
DryRun : req . DryRun ,
} )
2022-01-25 14:34:00 +00:00
if err != nil {
2022-08-24 21:40:09 +00:00
return appConfigResponse { appConfigResponseFields : appConfigResponseFields { Err : err } } , nil
2022-01-25 14:34:00 +00:00
}
2022-10-17 19:03:49 +00:00
2022-01-25 14:34:00 +00:00
license , err := svc . License ( ctx )
if err != nil {
return nil , err
}
loggingConfig , err := svc . LoggingConfig ( ctx )
if err != nil {
return nil , err
}
response := appConfigResponse {
AppConfig : * config ,
2022-08-24 21:40:09 +00:00
appConfigResponseFields : appConfigResponseFields {
License : license ,
Logging : loggingConfig ,
} ,
2022-01-25 14:34:00 +00:00
}
if response . SMTPSettings . SMTPPassword != "" {
2022-06-06 14:41:51 +00:00
response . SMTPSettings . SMTPPassword = fleet . MaskedPassword
2022-01-25 14:34:00 +00:00
}
2022-06-10 15:39:02 +00:00
if license . Tier != "premium" || response . FleetDesktop . TransparencyURL == "" {
response . FleetDesktop . TransparencyURL = fleet . DefaultTransparencyURL
}
2022-01-25 14:34:00 +00:00
return response , nil
}
2022-09-19 17:53:44 +00:00
func ( svc * Service ) ModifyAppConfig ( ctx context . Context , p [ ] byte , applyOpts fleet . ApplySpecOptions ) ( * fleet . AppConfig , error ) {
2022-01-25 14:34:00 +00:00
if err := svc . authz . Authorize ( ctx , & fleet . AppConfig { } , fleet . ActionWrite ) ; err != nil {
return nil , err
}
2022-05-02 20:58:34 +00:00
// we need the config from the datastore because API tokens are obfuscated at the service layer
// we will retrieve the obfuscated config before we return
appConfig , err := svc . ds . AppConfig ( ctx )
2022-01-25 14:34:00 +00:00
if err != nil {
return nil , err
}
2023-01-03 22:14:18 +00:00
oldAppConfig := appConfig . Copy ( )
2022-01-25 14:34:00 +00:00
2023-01-06 20:44:20 +00:00
// keep this original value, as it cannot be modified via this request.
origAppleBMTerms := oldAppConfig . MDM . AppleBMTermsExpired
2022-06-10 15:39:02 +00:00
license , err := svc . License ( ctx )
if err != nil {
return nil , err
}
2022-02-28 21:28:51 +00:00
oldSmtpSettings := appConfig . SMTPSettings
2022-08-24 12:32:45 +00:00
oldAgentOptions := ""
if appConfig . AgentOptions != nil {
oldAgentOptions = string ( * appConfig . AgentOptions )
}
2022-05-02 20:58:34 +00:00
2022-06-06 14:41:51 +00:00
storedJiraByProjectKey , err := fleet . IndexJiraIntegrations ( appConfig . Integrations . Jira )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "modify AppConfig" )
2022-05-02 20:58:34 +00:00
}
2022-06-06 14:41:51 +00:00
storedZendeskByGroupID , err := fleet . IndexZendeskIntegrations ( appConfig . Integrations . Zendesk )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "modify AppConfig" )
2022-04-06 11:55:25 +00:00
}
2022-01-25 14:34:00 +00:00
// TODO(mna): this ports the validations from the old validationMiddleware
// correctly, but this could be optimized so that we don't unmarshal the
// incoming bytes twice.
invalid := & fleet . InvalidArgumentError { }
var newAppConfig fleet . AppConfig
if err := json . Unmarshal ( p , & newAppConfig ) ; err != nil {
2022-09-19 17:53:44 +00:00
return nil , ctxerr . Wrap ( ctx , & fleet . BadRequestError { Message : err . Error ( ) } )
2022-01-25 14:34:00 +00:00
}
2022-03-30 13:10:02 +00:00
2022-06-13 21:03:51 +00:00
if newAppConfig . FleetDesktop . TransparencyURL != "" {
if license . Tier != "premium" {
invalid . Append ( "transparency_url" , ErrMissingLicense . Error ( ) )
return nil , ctxerr . Wrap ( ctx , invalid )
}
if _ , err := url . Parse ( newAppConfig . FleetDesktop . TransparencyURL ) ; err != nil {
invalid . Append ( "transparency_url" , err . Error ( ) )
return nil , ctxerr . Wrap ( ctx , invalid )
}
}
2022-08-10 18:15:35 +00:00
validateSSOSettings ( newAppConfig , appConfig , invalid , license )
2022-01-25 14:34:00 +00:00
if invalid . HasErrors ( ) {
return nil , ctxerr . Wrap ( ctx , invalid )
}
// We apply the config that is incoming to the old one
2022-08-24 21:40:09 +00:00
appConfig . EnableStrictDecoding ( )
if err := json . Unmarshal ( p , & appConfig ) ; err != nil {
2022-10-12 21:10:50 +00:00
err = fleet . NewUserMessageError ( err , http . StatusBadRequest )
return nil , ctxerr . Wrap ( ctx , err )
2022-09-19 17:53:44 +00:00
}
var legacyUsedWarning error
2022-10-12 21:10:50 +00:00
if legacyKeys := appConfig . DidUnmarshalLegacySettings ( ) ; len ( legacyKeys ) > 0 {
2022-09-19 17:53:44 +00:00
// this "warning" is returned only in dry-run mode, and if no other errors
// were encountered.
legacyUsedWarning = & fleet . BadRequestError {
2022-10-12 21:10:50 +00:00
Message : fmt . Sprintf ( "warning: deprecated settings were used in the configuration: %v; consider updating to the new settings: https://fleetdm.com/docs/using-fleet/configuration-files#settings" , legacyKeys ) ,
2022-09-19 17:53:44 +00:00
}
}
// required fields must be set, ensure they haven't been removed by applying
// the new config
if appConfig . OrgInfo . OrgName == "" {
invalid . Append ( "org_name" , "organization name must be present" )
}
if appConfig . ServerSettings . ServerURL == "" {
invalid . Append ( "server_url" , "Fleet server URL must be present" )
}
if newAppConfig . AgentOptions != nil {
// if there were Agent Options in the new app config, then it replaced the
// agent options in the resulting app config, so validate those.
if err := fleet . ValidateJSONAgentOptions ( * appConfig . AgentOptions ) ; err != nil {
2022-10-12 21:10:50 +00:00
err = fleet . NewUserMessageError ( err , http . StatusBadRequest )
2022-09-19 17:53:44 +00:00
if applyOpts . Force && ! applyOpts . DryRun {
level . Info ( svc . logger ) . Log ( "err" , err , "msg" , "force-apply appConfig agent options with validation errors" )
}
if ! applyOpts . Force {
2022-10-12 21:10:50 +00:00
return nil , ctxerr . Wrap ( ctx , err , "validate agent options" )
2022-09-19 17:53:44 +00:00
}
}
2022-01-25 14:34:00 +00:00
}
2022-06-06 14:41:51 +00:00
fleet . ValidateEnabledVulnerabilitiesIntegrations ( appConfig . WebhookSettings . VulnerabilitiesWebhook , appConfig . Integrations , invalid )
fleet . ValidateEnabledFailingPoliciesIntegrations ( appConfig . WebhookSettings . FailingPoliciesWebhook , appConfig . Integrations , invalid )
2022-09-19 17:53:44 +00:00
fleet . ValidateEnabledHostStatusIntegrations ( appConfig . WebhookSettings . HostStatusWebhook , invalid )
2023-01-03 22:14:18 +00:00
svc . validateMDM ( ctx , license , & oldAppConfig . MDM , & appConfig . MDM , invalid )
2022-03-30 13:10:02 +00:00
if invalid . HasErrors ( ) {
return nil , ctxerr . Wrap ( ctx , invalid )
}
2023-01-06 20:44:20 +00:00
// ignore AppleBMTermsExpired if provided in the modify payload
// we don't return an error in this case because it would prevent
// using the output of fleetctl get config as input to fleetctl apply
// or this endpoint.
appConfig . MDM . AppleBMTermsExpired = origAppleBMTerms
2022-09-19 17:53:44 +00:00
// do not send a test email in dry-run mode, so this is a good place to stop
// (we also delete the removed integrations after that, which we don't want
// to do in dry-run mode).
if applyOpts . DryRun {
if legacyUsedWarning != nil {
return nil , legacyUsedWarning
}
// must reload to get the unchanged app config
return svc . AppConfig ( ctx )
}
2022-02-28 21:28:51 +00:00
// ignore the values for SMTPEnabled and SMTPConfigured
oldSmtpSettings . SMTPEnabled = appConfig . SMTPSettings . SMTPEnabled
oldSmtpSettings . SMTPConfigured = appConfig . SMTPSettings . SMTPConfigured
// if we enable SMTP and the settings have changed, then we send a test email
if appConfig . SMTPSettings . SMTPEnabled {
if oldSmtpSettings != appConfig . SMTPSettings || ! appConfig . SMTPSettings . SMTPConfigured {
if err = svc . sendTestEmail ( ctx , appConfig ) ; err != nil {
2022-04-06 11:55:25 +00:00
return nil , ctxerr . Wrap ( ctx , err )
2022-02-28 21:28:51 +00:00
}
2022-01-25 14:34:00 +00:00
}
appConfig . SMTPSettings . SMTPConfigured = true
2022-05-31 10:19:57 +00:00
} else {
2022-01-25 14:34:00 +00:00
appConfig . SMTPSettings . SMTPConfigured = false
}
2022-06-13 14:04:47 +00:00
delJira , err := fleet . ValidateJiraIntegrations ( ctx , storedJiraByProjectKey , newAppConfig . Integrations . Jira )
if err != nil {
2022-06-06 14:41:51 +00:00
if errors . As ( err , & fleet . IntegrationTestError { } ) {
2022-09-19 17:53:44 +00:00
return nil , ctxerr . Wrap ( ctx , & fleet . BadRequestError { Message : err . Error ( ) } )
2022-04-06 11:55:25 +00:00
}
2022-06-06 14:41:51 +00:00
return nil , ctxerr . Wrap ( ctx , fleet . NewInvalidArgumentError ( "Jira integration" , err . Error ( ) ) )
2022-05-02 20:58:34 +00:00
}
2022-06-06 14:41:51 +00:00
appConfig . Integrations . Jira = newAppConfig . Integrations . Jira
2022-05-02 20:58:34 +00:00
2022-06-13 14:04:47 +00:00
delZendesk , err := fleet . ValidateZendeskIntegrations ( ctx , storedZendeskByGroupID , newAppConfig . Integrations . Zendesk )
if err != nil {
2022-06-06 14:41:51 +00:00
if errors . As ( err , & fleet . IntegrationTestError { } ) {
2022-09-19 17:53:44 +00:00
return nil , ctxerr . Wrap ( ctx , & fleet . BadRequestError { Message : err . Error ( ) } )
2022-05-02 20:58:34 +00:00
}
2022-06-06 14:41:51 +00:00
return nil , ctxerr . Wrap ( ctx , fleet . NewInvalidArgumentError ( "Zendesk integration" , err . Error ( ) ) )
2022-04-06 11:55:25 +00:00
}
2022-06-06 14:41:51 +00:00
appConfig . Integrations . Zendesk = newAppConfig . Integrations . Zendesk
2022-04-06 11:55:25 +00:00
2022-06-13 14:04:47 +00:00
// if any integration was deleted, remove it from any team that uses it
if len ( delJira ) + len ( delZendesk ) > 0 {
if err := svc . ds . DeleteIntegrationsFromTeams ( ctx , fleet . Integrations { Jira : delJira , Zendesk : delZendesk } ) ; err != nil {
return nil , ctxerr . Wrap ( ctx , err , "delete integrations from teams" )
}
}
2023-01-03 22:14:18 +00:00
if license . Tier != "premium" {
// reset transparency url to empty for downgraded licenses
2022-06-13 21:03:51 +00:00
appConfig . FleetDesktop . TransparencyURL = ""
2022-06-10 15:39:02 +00:00
}
2022-01-25 14:34:00 +00:00
if err := svc . ds . SaveAppConfig ( ctx , appConfig ) ; err != nil {
return nil , err
}
2022-05-02 20:58:34 +00:00
// retrieve new app config with obfuscated secrets
obfuscatedConfig , err := svc . AppConfig ( ctx )
if err != nil {
return nil , err
}
2022-08-24 12:32:45 +00:00
// if the agent options changed, create the corresponding activity
newAgentOptions := ""
if obfuscatedConfig . AgentOptions != nil {
newAgentOptions = string ( * obfuscatedConfig . AgentOptions )
}
if oldAgentOptions != newAgentOptions {
if err := svc . ds . NewActivity (
ctx ,
authz . UserFromContext ( ctx ) ,
2022-12-23 16:05:16 +00:00
fleet . ActivityTypeEditedAgentOptions {
Global : true ,
} ,
2022-08-24 12:32:45 +00:00
) ; err != nil {
2022-12-23 22:04:13 +00:00
return nil , ctxerr . Wrap ( ctx , err , "create activity for app config modification" )
2022-08-24 12:32:45 +00:00
}
}
2022-05-02 20:58:34 +00:00
return obfuscatedConfig , nil
2022-01-25 14:34:00 +00:00
}
2023-01-03 22:14:18 +00:00
func ( svc * Service ) validateMDM (
ctx context . Context ,
license * fleet . LicenseInfo ,
oldMdm * fleet . MDM ,
mdm * fleet . MDM ,
invalid * fleet . InvalidArgumentError ,
) {
if name := mdm . AppleBMDefaultTeam ; name != "" && name != oldMdm . AppleBMDefaultTeam {
if ! license . IsPremium ( ) {
invalid . Append ( "mdm.apple_bm_default_team" , ErrMissingLicense . Error ( ) )
return
}
if _ , err := svc . ds . TeamByName ( ctx , name ) ; err != nil {
invalid . Append ( "apple_bm_default_team" , "team name not found" )
}
}
}
2022-08-10 18:15:35 +00:00
func validateSSOSettings ( p fleet . AppConfig , existing * fleet . AppConfig , invalid * fleet . InvalidArgumentError , license * fleet . LicenseInfo ) {
2022-01-25 14:34:00 +00:00
if p . SSOSettings . EnableSSO {
if p . SSOSettings . Metadata == "" && p . SSOSettings . MetadataURL == "" {
if existing . SSOSettings . Metadata == "" && existing . SSOSettings . MetadataURL == "" {
invalid . Append ( "metadata" , "either metadata or metadata_url must be defined" )
}
}
if p . SSOSettings . Metadata != "" && p . SSOSettings . MetadataURL != "" {
invalid . Append ( "metadata" , "both metadata and metadata_url are defined, only one is allowed" )
}
if p . SSOSettings . EntityID == "" {
if existing . SSOSettings . EntityID == "" {
invalid . Append ( "entity_id" , "required" )
}
} else {
if len ( p . SSOSettings . EntityID ) < 5 {
invalid . Append ( "entity_id" , "must be 5 or more characters" )
}
}
if p . SSOSettings . IDPName == "" {
if existing . SSOSettings . IDPName == "" {
invalid . Append ( "idp_name" , "required" )
}
}
2022-08-10 18:15:35 +00:00
if ! license . IsPremium ( ) && p . SSOSettings . EnableJITProvisioning {
invalid . Append ( "enable_jit_provisioning" , ErrMissingLicense . Error ( ) )
}
2022-01-25 14:34:00 +00:00
}
}
////////////////////////////////////////////////////////////////////////////////
// Apply enroll secret spec
////////////////////////////////////////////////////////////////////////////////
type applyEnrollSecretSpecRequest struct {
Spec * fleet . EnrollSecretSpec ` json:"spec" `
}
type applyEnrollSecretSpecResponse struct {
Err error ` json:"error,omitempty" `
}
func ( r applyEnrollSecretSpecResponse ) error ( ) error { return r . Err }
2022-12-27 14:26:59 +00:00
func applyEnrollSecretSpecEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( errorer , error ) {
2022-01-25 14:34:00 +00:00
req := request . ( * applyEnrollSecretSpecRequest )
err := svc . ApplyEnrollSecretSpec ( ctx , req . Spec )
if err != nil {
return applyEnrollSecretSpecResponse { Err : err } , nil
}
return applyEnrollSecretSpecResponse { } , nil
}
func ( svc * Service ) ApplyEnrollSecretSpec ( ctx context . Context , spec * fleet . EnrollSecretSpec ) error {
if err := svc . authz . Authorize ( ctx , & fleet . EnrollSecret { } , fleet . ActionWrite ) ; err != nil {
return err
}
2022-10-05 12:35:36 +00:00
if len ( spec . Secrets ) > fleet . MaxEnrollSecretsCount {
return ctxerr . Wrap ( ctx , fleet . NewInvalidArgumentError ( "secrets" , "too many secrets" ) )
}
2022-01-25 14:34:00 +00:00
for _ , s := range spec . Secrets {
if s . Secret == "" {
return ctxerr . New ( ctx , "enroll secret must not be empty" )
}
}
2022-07-12 22:12:10 +00:00
if svc . config . Packaging . GlobalEnrollSecret != "" {
return ctxerr . New ( ctx , "enroll secret cannot be changed when fleet_packaging.global_enroll_secret is set" )
}
2022-01-25 14:34:00 +00:00
return svc . ds . ApplyEnrollSecrets ( ctx , nil , spec . Secrets )
}
////////////////////////////////////////////////////////////////////////////////
// Get enroll secret spec
////////////////////////////////////////////////////////////////////////////////
type getEnrollSecretSpecResponse struct {
Spec * fleet . EnrollSecretSpec ` json:"spec" `
Err error ` json:"error,omitempty" `
}
func ( r getEnrollSecretSpecResponse ) error ( ) error { return r . Err }
2022-12-27 14:26:59 +00:00
func getEnrollSecretSpecEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( errorer , error ) {
2022-01-25 14:34:00 +00:00
specs , err := svc . GetEnrollSecretSpec ( ctx )
if err != nil {
return getEnrollSecretSpecResponse { Err : err } , nil
}
return getEnrollSecretSpecResponse { Spec : specs } , nil
}
func ( svc * Service ) GetEnrollSecretSpec ( ctx context . Context ) ( * fleet . EnrollSecretSpec , error ) {
if err := svc . authz . Authorize ( ctx , & fleet . EnrollSecret { } , fleet . ActionRead ) ; err != nil {
return nil , err
}
secrets , err := svc . ds . GetEnrollSecrets ( ctx , nil )
if err != nil {
return nil , err
}
return & fleet . EnrollSecretSpec { Secrets : secrets } , nil
}
////////////////////////////////////////////////////////////////////////////////
// Version
////////////////////////////////////////////////////////////////////////////////
type versionResponse struct {
* version . Info
Err error ` json:"error,omitempty" `
}
func ( r versionResponse ) error ( ) error { return r . Err }
2022-12-27 14:26:59 +00:00
func versionEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( errorer , error ) {
2022-01-25 14:34:00 +00:00
info , err := svc . Version ( ctx )
if err != nil {
return versionResponse { Err : err } , nil
}
return versionResponse { Info : info } , nil
}
func ( svc * Service ) Version ( ctx context . Context ) ( * version . Info , error ) {
if err := svc . authz . Authorize ( ctx , & fleet . AppConfig { } , fleet . ActionRead ) ; err != nil {
return nil , err
}
info := version . Version ( )
return & info , nil
}
////////////////////////////////////////////////////////////////////////////////
// Get Certificate Chain
////////////////////////////////////////////////////////////////////////////////
type getCertificateResponse struct {
CertificateChain [ ] byte ` json:"certificate_chain" `
Err error ` json:"error,omitempty" `
}
func ( r getCertificateResponse ) error ( ) error { return r . Err }
2022-12-27 14:26:59 +00:00
func getCertificateEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( errorer , error ) {
2022-01-25 14:34:00 +00:00
chain , err := svc . CertificateChain ( ctx )
if err != nil {
return getCertificateResponse { Err : err } , nil
}
return getCertificateResponse { CertificateChain : chain } , nil
}
// Certificate returns the PEM encoded certificate chain for osqueryd TLS termination.
func ( svc * Service ) CertificateChain ( ctx context . Context ) ( [ ] byte , error ) {
config , err := svc . AppConfig ( ctx )
if err != nil {
return nil , err
}
u , err := url . Parse ( config . ServerSettings . ServerURL )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "parsing serverURL" )
}
conn , err := connectTLS ( ctx , u )
if err != nil {
return nil , err
}
return chain ( ctx , conn . ConnectionState ( ) , u . Hostname ( ) )
}
func connectTLS ( ctx context . Context , serverURL * url . URL ) ( * tls . Conn , error ) {
var hostport string
if serverURL . Port ( ) == "" {
hostport = net . JoinHostPort ( serverURL . Host , "443" )
} else {
hostport = serverURL . Host
}
// attempt dialing twice, first with a secure conn, and then
// if that fails, use insecure
dial := func ( insecure bool ) ( * tls . Conn , error ) {
conn , err := tls . Dial ( "tcp" , hostport , & tls . Config {
2022-02-14 03:35:59 +00:00
InsecureSkipVerify : insecure ,
} )
2022-01-25 14:34:00 +00:00
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "dial tls" )
}
defer conn . Close ( )
return conn , nil
}
var (
conn * tls . Conn
err error
)
conn , err = dial ( false )
if err == nil {
return conn , nil
}
conn , err = dial ( true )
return conn , err
}
// chain builds a PEM encoded certificate chain using the PeerCertificates
// in tls.ConnectionState. chain uses the hostname to omit the Leaf certificate
// from the chain.
func chain ( ctx context . Context , cs tls . ConnectionState , hostname string ) ( [ ] byte , error ) {
buf := bytes . NewBuffer ( [ ] byte ( "" ) )
verifyEncode := func ( chain [ ] * x509 . Certificate ) error {
for _ , cert := range chain {
if len ( chain ) > 1 {
// drop the leaf certificate from the chain. osqueryd does not
// need it to establish a secure connection
if err := cert . VerifyHostname ( hostname ) ; err == nil {
continue
}
}
if err := encodePEMCertificate ( buf , cert ) ; err != nil {
return err
}
}
return nil
}
// use verified chains if available(which adds the root CA), otherwise
// use the certificate chain offered by the server (if terminated with
// self-signed certs)
if len ( cs . VerifiedChains ) != 0 {
for _ , chain := range cs . VerifiedChains {
if err := verifyEncode ( chain ) ; err != nil {
return nil , ctxerr . Wrap ( ctx , err , "encode verified chains pem" )
}
}
} else {
if err := verifyEncode ( cs . PeerCertificates ) ; err != nil {
return nil , ctxerr . Wrap ( ctx , err , "encode peer certificates pem" )
}
}
return buf . Bytes ( ) , nil
}
func encodePEMCertificate ( buf io . Writer , cert * x509 . Certificate ) error {
block := & pem . Block {
Type : "CERTIFICATE" ,
Bytes : cert . Raw ,
}
return pem . Encode ( buf , block )
}
2022-08-30 11:13:09 +00:00
func ( svc * Service ) HostFeatures ( ctx context . Context , host * fleet . Host ) ( * fleet . Features , error ) {
if svc . EnterpriseOverrides != nil {
return svc . EnterpriseOverrides . HostFeatures ( ctx , host )
}
appConfig , err := svc . ds . AppConfig ( ctx )
if err != nil {
return nil , err
}
return & appConfig . Features , nil
}