mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
For #27042. Ready for review, just missing integration tests that I will be writing today. - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. - [X] If database migrations are included, checked table schema to confirm autoupdate - For new Fleet configuration settings - [X] Verified that the setting can be managed via GitOps, or confirmed that the setting is explicitly being excluded from GitOps. If managing via Gitops: - [X] Verified that the setting is exported via `fleetctl generate-gitops` - [X] Added the setting to [the GitOps documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485) - [X] Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional) - [x] Verified that any relevant UI is disabled when GitOps mode is enabled - For database migrations: - [X] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [X] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [X] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Added/updated automated tests - [X] Manual QA for all new/changed functionality --------- Co-authored-by: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
148 lines
5.7 KiB
Go
148 lines
5.7 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
func (ds *Datastore) ConditionalAccessMicrosoftCreateIntegration(
|
|
ctx context.Context, tenantID string, proxyServerSecret string,
|
|
) error {
|
|
return ds.withTx(ctx, func(tx sqlx.ExtContext) error {
|
|
// Currently only one global integration is supported, thus we need to delete the existing
|
|
// one before creating a new one.
|
|
if _, err := tx.ExecContext(ctx,
|
|
`DELETE FROM microsoft_compliance_partner_integrations;`,
|
|
); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "deleting microsoft_compliance_partner_integrations")
|
|
}
|
|
if _, err := tx.ExecContext(ctx,
|
|
`INSERT INTO microsoft_compliance_partner_integrations (tenant_id, proxy_server_secret) VALUES (?, ?);`,
|
|
tenantID, proxyServerSecret,
|
|
); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "inserting new microsoft_compliance_partner_integrations")
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (ds *Datastore) ConditionalAccessMicrosoftMarkSetupDone(ctx context.Context) error {
|
|
// Currently only one global integration is supported.
|
|
if _, err := ds.writer(ctx).ExecContext(ctx,
|
|
`UPDATE microsoft_compliance_partner_integrations SET setup_done = true;`,
|
|
); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "deleting microsoft_compliance_partner_integrations")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ds *Datastore) ConditionalAccessMicrosoftGet(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) {
|
|
return getConditionalAccessMicrosoft(ctx, ds.reader(ctx))
|
|
}
|
|
|
|
func getConditionalAccessMicrosoft(ctx context.Context, q sqlx.QueryerContext) (*fleet.ConditionalAccessMicrosoftIntegration, error) {
|
|
var integration fleet.ConditionalAccessMicrosoftIntegration
|
|
err := sqlx.GetContext(
|
|
ctx, q, &integration,
|
|
// Currently only one global integration is supported.
|
|
`SELECT tenant_id, proxy_server_secret, setup_done FROM microsoft_compliance_partner_integrations;`,
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ctxerr.Wrap(ctx, notFound("MicrosoftCompliancePartnerIntegration"))
|
|
}
|
|
return nil, ctxerr.Wrap(ctx, err, "getting microsoft_compliance_partner_integrations")
|
|
}
|
|
return &integration, nil
|
|
}
|
|
|
|
func (ds *Datastore) ConditionalAccessMicrosoftDelete(ctx context.Context) error {
|
|
return ds.withTx(ctx, func(tx sqlx.ExtContext) error {
|
|
// Currently only one global integration is supported.
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM microsoft_compliance_partner_integrations;`); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "deleting microsoft_compliance_partner_integrations")
|
|
}
|
|
// Remove all last reported statuses.
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM microsoft_compliance_partner_host_statuses;`); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "deleting microsoft_compliance_partner_host_statuses")
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (ds *Datastore) LoadHostConditionalAccessStatus(ctx context.Context, hostID uint) (*fleet.HostConditionalAccessStatus, error) {
|
|
var hostConditionalAccessStatus fleet.HostConditionalAccessStatus
|
|
if err := sqlx.GetContext(ctx,
|
|
ds.reader(ctx),
|
|
&hostConditionalAccessStatus,
|
|
`SELECT
|
|
mcphs.host_id, mcphs.device_id, mcphs.user_principal_name, mcphs.compliant, mcphs.created_at, mcphs.updated_at, mcphs.managed,
|
|
h.os_version, hdn.display_name
|
|
FROM microsoft_compliance_partner_host_statuses mcphs
|
|
JOIN host_display_names hdn ON hdn.host_id=mcphs.host_id
|
|
JOIN hosts h ON h.id=mcphs.host_id
|
|
WHERE mcphs.host_id = ?`,
|
|
hostID,
|
|
); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ctxerr.Wrap(ctx, notFound("HostConditionalAccessStatus").WithID(hostID))
|
|
}
|
|
}
|
|
hostConditionalAccessStatus.OSVersion = strings.TrimPrefix(hostConditionalAccessStatus.OSVersion, "macOS ")
|
|
return &hostConditionalAccessStatus, nil
|
|
}
|
|
|
|
func (ds *Datastore) CreateHostConditionalAccessStatus(ctx context.Context, hostID uint, deviceID string, userPrincipalName string) error {
|
|
// Most of the time this information won't change, so use the reader first.
|
|
var hostConditionalAccessStatus fleet.HostConditionalAccessStatus
|
|
err := sqlx.GetContext(ctx,
|
|
ds.reader(ctx),
|
|
&hostConditionalAccessStatus,
|
|
`SELECT device_id, user_principal_name FROM microsoft_compliance_partner_host_statuses WHERE host_id = ?`,
|
|
hostID,
|
|
)
|
|
switch {
|
|
case err == nil:
|
|
if deviceID == hostConditionalAccessStatus.DeviceID && userPrincipalName == hostConditionalAccessStatus.UserPrincipalName {
|
|
// Nothing to do, the Entra account on the device is still the same.
|
|
return nil
|
|
}
|
|
// If we got here it means the host's Entra data has changed, so we will override the host's status row.
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
// OK, let's create one.
|
|
default:
|
|
return ctxerr.Wrap(ctx, err, "failed to get microsoft_compliance_partner_host_statuses")
|
|
}
|
|
|
|
// Create or override existing row for the host.
|
|
if _, err := ds.writer(ctx).ExecContext(ctx,
|
|
`INSERT INTO microsoft_compliance_partner_host_statuses
|
|
(host_id, device_id, user_principal_name)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
device_id = VALUES(device_id),
|
|
user_principal_name = VALUES(user_principal_name),
|
|
managed = NULL,
|
|
compliant = NULL`,
|
|
hostID, deviceID, userPrincipalName,
|
|
); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "create host conditional access status")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ds *Datastore) SetHostConditionalAccessStatus(ctx context.Context, hostID uint, managed, compliant bool) error {
|
|
if _, err := ds.writer(ctx).ExecContext(ctx,
|
|
`UPDATE microsoft_compliance_partner_host_statuses SET managed = ?, compliant = ? WHERE host_id = ?;`,
|
|
managed, compliant, hostID,
|
|
); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "update host conditional access status")
|
|
}
|
|
return nil
|
|
}
|