Merge branch 'main' into feat-config-profile-excluding-labels

This commit is contained in:
Martin Angers 2024-06-17 10:35:34 -04:00
commit 97af4e66a1
455 changed files with 5963 additions and 3330 deletions

View file

@ -99,6 +99,14 @@ Smoke tests are limited to core functionality and serve as a pre-release final r
3. Verify able to run MDM commands on both macOS and Windows hosts from the CLI.
</td><td>pass/fail</td></tr>
<tr><td>MDM migration flow</td><td>Verify MDM migration for ADE and non-ADE hosts</td><td>
1. Turn off MDM on an ADE-eligible macOS host and verify that the native, "Device Enrollment" macOS notification appears.
2. On the My device page, follow the "Turn on MDM" instructions and verify that MDM is turned on.
3. Turn off MDM on a non ADE-eligible macOS host.
4. On the My device page, follow the "Turn on MDM" instructions and verify that MDM is turned on.
</td><td>pass/fail</td></tr>
<tr><td>Scripts</td><td>Verify script library and execution</td><td>
1. Verify able to run a script on all host types from CLI.

View file

@ -35,6 +35,7 @@ What else should contributors [keep in mind](https://fleetdm.com/handbook/compan
- [ ] UI changes: TODO <!-- Insert the link to the relevant Figma cover page. Remove this checkbox if there are no changes to the user interface. -->
- [ ] CLI usage changes: TODO <!-- Insert the link to the relevant Figma cover page. Remove this checkbox if there are no changes to the CLI. -->
- [ ] REST API changes: TODO <!-- Specify changes as a draft PR to the REST API doc page. Remove this checkbox if there are no changes necessary. Move this item to the engineering list below if engineering will design the API changes. -->
- [ ] Fleet's agent (fleetd) changes: TODO <!-- Specify changes to fleetd. If the change requires a new Fleet (server) version, consider specifying to only enable this change in new Fleet versions. Remove this checkbox if there are no changes necessary. -->
- [ ] Permissions changes: TODO <!-- Specify changes as a draft PR to the Manage access doc page. If doc changes aren't necessary, explicitly mention no changes to the doc page. Remove this checkbox if there are no permissions changes. -->
- [ ] Outdated documentation changes: TODO <!-- Specify required documentation changes (public-facing fleetdm.com/docs or contributors) & redirects to add to /website/config/routes.js. -->
- [ ] Changes to paid features or tiers: TODO <!-- Specify "Fleet Free" or "Fleet Premium". If only certain parts of the user story involve paid features, specify which parts. Implementation of paid features should live in the `ee/` directory. -->

View file

@ -31,6 +31,7 @@ env:
TF_VAR_elastic_url: ${{ secrets.ELASTIC_APM_SERVER_URL }}
TF_VAR_elastic_token: ${{ secrets.ELASTIC_APM_SECRET_TOKEN }}
TF_VAR_geolite2_license: ${{ secrets.MAXMIND_LICENSE }}
TF_VAR_dogfood_sidecar_enroll_secret: ${{ secrets.DOGFOOD_SERVERS_CANARY_ENROLL_SECRET }}
permissions:
id-token: write

View file

@ -24,7 +24,7 @@ defaults:
shell: bash
env:
FLEET_DESKTOP_VERSION: 1.25.0
FLEET_DESKTOP_VERSION: 1.26.0
permissions:
contents: read

View file

@ -1,3 +1,10 @@
## Fleet 4.51.1 (Jun 12, 2024)
### Bug fixes
* Added S3 config variables with a `carves` and `software_installers` prefix, which were used to configure buckets for those features. The existing non-prefixed variables were kept for backwards compatibility.
* Fixed a bug that prevented unused script contents to be periodically cleaned up from the database.
## Fleet 4.51.0 (Jun 10, 2024)
### Endpoint Operations

View file

@ -0,0 +1,346 @@
# Deploy Fleet on Ubuntu with Elastic
![Deploy Fleet on Ubuntu with Elastic](../website/assets/images/articles/deploy-fleet-on-ubuntu-with-elastic-1600x900@2x.png)
[<img src="../website/assets/images/articles/deploy-fleet-on-ubuntu-with-elastic-internews_logo-256x237@2x.png" width="128" align="right"/>](https://internews.org/)_Today we wanted to feature [Josh](https://defensivedepth.com/), a member of our community. His work was sponsored by [Internews](https://internews.org/). If you are interested in contributing to the Fleet blog, feel free to [contact us](https://fleetdm.com/company/contact) or reach out to [@jdstrong](https://osquery.slack.com/team/U04MTPBAHQS) on the osquery slack._
This guide provides a detailed walkthrough for setting up a small production environment of Fleet alongside Elastic components (Elasticsearch, Kibana, Filebeat). The setup integrates Filebeat to collect scheduled query results from Fleet and feed them into Elasticsearch, while Kibana will be utilized for data visualization and the creation of detections. Additionally, Nginx will serve as a reverse proxy for the Kibana and Fleet web interfaces and will segregate the web administration and agent data+control planes of Fleet for more fine-grained access control.
The installation and configuration will begin with the Elastic stack components, followed by Fleet and its dependencies. For this guide, they will all be installed on a single server; however, for larger deployments or requirements of higher availability and scalability, a more distributed approach across multiple servers and geographical regions is recommended.
### Network, server & DNS setup
This guide is based on Ubuntu 22.04 LTS, although the installation procedures for the components remain consistent across newer versions of the operating system.
For this guide, subdomain `fleet.localhost.invalid` is pointed to the server's public IP. Replace this subdomain with a valid one configured as such.
Ports needed, inbound to server:
- `TCP/80` (Only used for the initial Let's Encrypt setup)
- `TCP/443` (Used initially for the Let's Encrypt setup, and then longterm for Fleet distributed agents to checking for data and control)
- `TCP/8443` (Used for Kibana web interface)
- `TCP/9443` (Used for Fleet web interface)
Set up access control where it makes sense - perimeter firewall or on the server itself. Set the ports for the Kibana (`TCP/8443`) and Fleet (`TCP/9443`) web interfaces to only be accessible from a known-trusted IP space. Also set rules for `TCP/443`, which is used for the deployed osquery agents to check in with Fleet. A common configuration is for the web interface ports to be accessible to a single IP or small set of IPs, and for the osquery check in port to be accessible anywhere.
Be aware that if you are using a proxy like Cloudflare, you will need to confirm that the ports in this guide will work as expected.
### Update OS
Let's start by updating the system's packages and creating a workspace directory:
```sh
sudo apt-get update && sudo apt-get dist-upgrade -y
mkdir workspace && cd workspace
```
### Install & configure Certbot
Next up is to install Certbot to create and manage our free Let's Encrypt SSL certificate. This certificate will be used by for all components.
```sh
sudo apt-get install certbot -y
sudo certbot certonly --standalone
```
Select option 1 to spin up a temporary web server. Enter the domain that you have pointed to your public IP. You will need TCP/80 & TCP/443 open to the server.
By default, the certificate and key are saved at:
- Certificate: `/etc/letsencrypt/live/fleet.localhost.invalid/fullchain.pem`
- Key: `/etc/letsencrypt/live/fleet.localhost.invalid/privkey.pem`
### Install & configure Nginx
Let's install Nginx and configure it as a reverse proxy for Fleet and Kibana.
```sh
sudo apt-get install nginx
nano /etc/nginx/sites-available/fleet # use the below config, remember to update the path to the certificate files
sudo ln -s /etc/nginx/sites-available/fleet /etc/nginx/sites-enabled/ # symlink the config file to enable it
nginx -t # Test the config to make sure there are no syntax errors
sudo systemctl reload nginx # Reload nginx to make the config active
sudo systemctl status nginx # Check the reload to confirm that there are no errors
```
Nginx Config file:
```sh
# Define SSL configuration
ssl_certificate /etc/letsencrypt/live/fleet.localhost.invalid/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fleet.localhost.invalid/privkey.pem;
# Common proxy settings
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Server block for Kibana on port 8443
server {
listen 8443 ssl default_server;
location / {
proxy_pass http://localhost:5601;
}
}
# Server block for Fleet on port 9443 with WebSocket support
server {
listen 9443 ssl;
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob: wss:; frame-ancestors 'self'";
location / {
proxy_pass https://localhost:4443/;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
# Server block for specific Orbit osquery paths on port 443
server {
listen 443 ssl;
location ~* ^/api/(osquery|fleet/orbit/(config|ping)|v1/osquery) {
proxy_pass https://localhost:4443;
}
}
```
### Install & configure Elasticsearch
In case the below does not work, consult Debian package installation instructions at https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html
Let's download and install Elasticsearch via an Ubuntu package.
One-time prep needed to add the Elastic APT repository:
```sh
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-8.x.list
sudo apt-get update
```
Install the Elasticsearch package (this will install the latest stable version):
```sh
sudo apt-get install elasticsearch
```
The post-install message will contain a password generated for the Elasticsearch built-in superuser (`elastic`). Make note of it as we will need it later.
Enable and start the Elasticsearch service:
```sh
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch.service
```
## Install & configure Kibana
Onto Kibana. Let's download, install and do the initial configuration.
```sh
sudo apt-get install kibana
```
Before we start Kibana, we need to edit the configuration file:
```sh
nano /etc/kibana/kibana.yml
```
Set the server host and public base URL by uncommenting and editing the below lines:
```yaml
server.host: "0.0.0.0" # Sets Kibana to listen on all interfaces
server.publicBaseUrl: "https://fleetmd.localhost.invalid:8443" # This should be set to your custom subdomain/port
```
Enable and start the Kibana service:
```sh
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable --now kibana.service
```
### Initial configuration
Access Kibana at `https://fleet.localhost.invalid:8443`. If you get stuck at this step, you may not have opened ports 8443 and 9443, as needed in this walkthrough. Generate and enter the initial setup token and the verification code:
```sh
/usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
/usr/share/kibana/bin/kibana-verification-code
```
From there, log in with the username `elastic` and the password that was generated previously, and choose `Explore on my own`. Navigate to `Management` -> `Stack Monitoring` and set up self-monitoring with `set up with self monitoring` and `Turn on monitoring`. This will give you a nice overview of Elasticsearch, Kibana and eventually Filebeat.
## Install & configure Filebeat
The final Elastic component to install is Filebeat. Let's download and configure it to pick up our osquery logs.
```sh
sudo apt-get install filebeat
```
Edit the Filebeat configuration to set up where to send its logs (Elasticsearch). We disable ssl.verification because the connection from Filebeat to Elasticsearch is local (from Filebeat on the server to Elasticsearch on the same system).
Filebeat has built-in support for osquery logs. Let's configure and then enable that filebeat module and then start the Filebeat service:
```sh
sudo nano /etc/filebeat/modules.d/osquery.yml.disabled # Use the following config
```
```yaml
# Module: osquery
- module: osquery
result:
enabled: true
# Set custom paths for the log files. If left empty,
# Filebeat will choose the paths depending on your OS.
var.paths: ["/tmp/osquery_result"]
# If true, all fields created by this module are prefixed with
# `osquery.result`. Set to false to copy the fields in the root
# of the document. The default is true.
#var.use_namespace: true
```
```sh
sudo filebeat modules enable osquery # Enable the Filebeat osquery module
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable --now filebeat.service
```
## Install & configure MySQL
With the Elastic components installed, we can move on to Fleet. First up is installing MySQL and creating the Fleet user and database.
```sh
sudo apt-get install mysql-server -y
mysql -uroot
create database fleet; # This is the database that will be used by Fleet
create user fleet@'localhost' identified by 'FleetDMPW!'; # Create the mysql user for the Fleet database and set a strong password.
grant all privileges on fleet.* to fleet@'localhost'; # Grant the new user the necessary privileges to the Fleet database.
exit
```
## Install & configure Redis
Redis is used for the Live Query functionality. Let's get it installed.
```sh
sudo apt-get install redis-server -y
```
## Install & configure Fleet
Finally, the linchpin - Fleet. Let's download the latest version. You can find the latest version here: https://github.com/fleetdm/fleet/releases/latest - make sure you download the main Fleet package and not `fleetctl` at this time.
```sh
wget https://github.com/fleetdm/fleet/releases/download/fleet-$VERSION/fleet_$VERSION_linux.tar.gz
tar -xf fleet_v*_linux.tar.gz # Extract the Fleet binary
sudo cp fleet_v*_linux/fleet /usr/bin/ # Copy the the Fleet binary to /usr/bin
fleet version # Sanity check to make sure it runs as expected
```
Next we will create the directory that will contain the config and installers, and create the config itself.
```sh
mkdir /etc/fleet
nano /etc/fleet/fleet.config
```
Use the following as a baseline for your Fleet config:
```yaml
mysql:
address: 127.0.0.1:3306
database: fleet
username: fleet
password: FleetPW!
redis:
address: 127.0.0.1:6379
server:
address: 0.0.0.0:4443
cert: /etc/letsencrypt/live/fleet.localhost.invalid/fullchain.pem
key: /etc/letsencrypt/live/fleet.localhost.invalid/privkey.pem
websockets_allow_unsafe_origin: true # This is needed for Live Query functionality to work with the nginx reverse proxy we are using
```
Next, let's run the `prepare db` command to complete the necessary database prep.
```sh
fleet prepare db --config /etc/fleet/fleet.config
```
### Setup systemd unit file
Now that we are ready to run Fleet, let's create a `systemd` unit file to manage Fleet as a service, and then go ahead and start the service:
```sh
sudo nano /etc/systemd/system/fleet.service # Use the example unit file below
sudo systemctl enable --now fleet.service
sudo systemctl status fleet.service
```
```sh
[Unit]
Description=fleet
After=network.target
[Service]
ExecStart=/usr/bin/fleet serve -c /etc/fleet/fleet.config
[Install]
WantedBy=multi-user.target
```
Finally, complete the Fleet setup via the web interface at https://fleet.localhost.invalid:9443
## fleetctl
fleetctl is a utility from Fleet that is used to manage Fleet from the command line. Let's download it and get it logged into our instance of Fleet. You can find the latest version here: https://github.com/fleetdm/fleet/releases/latest
```sh
wget https://github.com/fleetdm/fleet/releases/download/fleet-$VERSION/fleetctl_$VERSION_linux.tar.gz
tar -xf fleetctl_*_linux.tar.gz# Extract the fleetct binary
sudo cp fleetctl_v*_linux/fleetctl /usr/bin/ # Copy the the fleetctl binary to /usr/bin
/usr/bin/fleetctl --version # Sanity check to make sure it runs as expected
```
Next, we need to configure it to work with our local instance of Fleet and login to it.
```sh
fleetctl config set --address https://fleet.localhost.invalid::4443
fleetctl login
```
## Generate agents
Fleet ships with support for Orbit, a wrapper around osquery. Orbit makes configuration of osquery much simpler, offers auto-update functionality of osquery as well as additional tables developed by Fleet. In order to install an Orbit/osquery agent, you will need to generate an installer.
You can start the process of generating Orbit agent packages from the Fleet interface - click on the `Add Hosts` button. You can generate the packages anywhere that you have `fleetctl`, including on the server itself. Be sure to install the Docker engine if you need to generate installers for Windows.
## Load Fleet standard query library
Fleet has a library of queries that are useful in many different situations - https://fleetdm.com/docs/using-fleet/standard-query-library
Let's go ahead and load them - once this is complete, you can find them in the web interface under Queries.
```sh
git clone https://github.com/fleetdm/fleet.git
cd fleet
fleetctl apply -f docs/01-Using-Fleet/standard-query-library/standard-query-library.yml
```
<meta name="articleTitle" value="Deploy Fleet on Ubuntu">
<meta name="authorGitHubUsername" value="defensivedepth">
<meta name="authorFullName" value="Josh Brower">
<meta name="publishedOn" value="2024-06-12">
<meta name="category" value="guides">
<meta name="description" value="A guide to deploy Fleet and Elastic on Ubuntu.">
<meta name="articleImageUrl" value="../website/assets/images/articles/deploy-fleet-on-ubuntu-with-elastic-1600x900@2x.png">

View file

@ -0,0 +1,132 @@
# Sysadmin diaries: restoring `fleetd`
![Sysadmin diaries: restoring fleetd](../website/assets/images/articles/sysadmin-diaries-1600x900@2x.png)
As a sysadmin, unexpected challenges are part of the job. In our last diary installment, we discussed the methods of [device enrollment](https://fleetdm.com/guides/sysadmin-diaries-device-enrollment). Today, we tackle a new challenge: a surly employee has deleted the `fleetd` files from their device. What happens next? Can we restore the `fleetd` agent using Mobile Device Management (MDM) commands? In this post, well explore various methods to tackle this situation and ensure your fleet of devices remains secure and compliant.
### What is `fleetd` and why it matters
`Fleetd` is a suite of agents Fleet provides to collect and manage information about your devices. It includes osquery, Orbit, Fleet Desktop, and the `fleetd` Chrome extension. These tools help you maintain visibility and control over your device fleet.
### Scenario: the surly employee deletion
Imagine a disgruntled employee deleting the `fleetd` files from their device. This disruptive act can hinder your ability to manage the device and potentially compromise security. Fortunately, you can reinstall the `fleetd` agent and restore order with the right MDM commands. It's important to note that ADE (Automated Device Enrollment) enrollment ensures we can maintain control of the laptop and still send MDM commands to the host, such as remote lock or wipe.
### Solutions and commands
There are several approaches to reinstall the `fleetd` agent using MDM commands:
#### 1. Resending the `fleetd` configuration profile
One potential solution is to resend the `fleetd` configuration profile. The new feature for [resending profiles](https://fleetdm.com/docs/rest-api/rest-api#resend-hosts-configuration-profile) makes this easy to accomplish through the MDM interface.
#### 2. Wipe the device
A more extreme method is wiping the device, which performs an Erase All Contents and Settings (EACS). This wipes and resets the laptop by erasing the user-data volume, returning the device to an "out-of-box" experience. This process avoids reinstalling macOS, making it a quick and efficient solution but probably an aggressive action.
#### 3. Sending the install command
By default, the install profile is not sent after the first enrollment. However, you can manually send a command to reinstall `fleetd`. Here is the XML command for macOS:
```xml
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Command</key>
<dict>
<key>ManifestURL</key>
<string>https://download.fleetdm.com/fleetd-base-manifest.plist</string>
<key>RequestType</key>
<string>InstallEnterpriseApplication</string>
</dict>
<key>CommandUUID</key>
<string>adc1bc23-abec-4499-b57f-c8755c7ffe3c</string>
</dict>
</plist>
```
To run this command, use the following `fleetctl` command:
```sh
fleetctl mdm run-command --hosts=HOST_IDENTIFIER --payload=path/to/file.xml
```
For Windows, the process involves two steps. First, [add the profile](https://fleetdm.com/docs/using-fleet/mdm-custom-os-settings) using gitops or the UI:
```xml
<Add>
<CmdID>addCommandUUID</CmdID>
<Item>
<Target>
<LocURI>./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7BA427C0AA-E2D5-40DF-ACE8-0D726A6BE096%7D/DownloadInstall</LocURI>
</Target>
</Item>
</Add>
```
Then, execute the command using `fleetctl`:
```xml
<Exec>
<CmdID>execCommandUUID</CmdID>
<Item>
<Target>
<LocURI>./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7BA427C0AA-E2D5-40DF-ACE8-0D726A6BE096%7D/DownloadInstall</LocURI>
</Target>
<Data>
<MsiInstallJob id="{A427C0AA-E2D5-40DF-ACE8-0D726A6BE096}">
<Product Version="1.0.0.0">
<Download>
<ContentURLList>
<ContentURL>https://download.fleetdm.com/fleetd-base.msi</ContentURL>
</ContentURLList>
</Download>
<Validation>
<FileHash>9F89C57D1B34800480B38BD96186106EB6418A82B137A0D56694BF6FFA4DDF1A</FileHash>
</Validation>
<Enforcement>
<CommandLine>/quiet FLEET_URL="REPLACE_WITH_FLEET_URL_HERE" FLEET_SECRET="REPLACE_WITH_FLEET_SECRET_HERE"</CommandLine>
<TimeOut>10</TimeOut>
<RetryCount>1</RetryCount>
<RetryInterval>5</RetryInterval>
</Enforcement>
</Product>
</MsiInstallJob>
</Data>
<Meta>
<Type xmlns="syncml:metinf">text/plain</Type>
<Format xmlns="syncml:metinf">xml</Format>
</Meta>
</Item>
</Exec>
```
### Success story and experiment results
Recently, we conducted an experiment to test these methods. After executing the commands, we observed the device coming back online, confirming the effectiveness of these solutions. This successful experiment highlights the practicality of using MDM commands to restore the `fleetd` agent.
### Conclusion
Dealing with the deletion of `fleetd` files by a surly employee can be a challenge. However, using MDM commands to resend configuration profiles, utilize the EACS, or manually send the install command can efficiently restore functionality and ensure device security. Documenting these processes further strengthens your device management capabilities and prepares you for any future disruptions.
<meta name="articleTitle" value="Sysadmin diaries: restoring fleetd">
<meta name="authorFullName" value="JD Strong">
<meta name="authorGitHubUsername" value="spokanemac">
<meta name="category" value="guides">
<meta name="publishedOn" value="2024-06-14">
<meta name="articleImageUrl" value="../website/assets/images/articles/sysadmin-diaries-1600x900@2x.png">
<meta name="description" value="In this sysadmin diary, we explore restoring fleetd deleted by a surly employee.">

View file

@ -0,0 +1 @@
- Fleet now matches vulnerabilies for applications that include an OS scope [example](https://nvd.nist.gov/vuln/detail/CVE-2023-0400)

View file

@ -0,0 +1,2 @@
- Endpoint `/api/latest/fleet/users/admin` to return API token when creating API-only (non-SSO) users.
- Added API-token of the created API-only (non-SSO) user to the output of `fleetctl user create --api-only`.

View file

@ -0,0 +1 @@
- Fixed issue where Windows-specific error message was displayed when failing to parse macOS configuration profiles.

View file

@ -0,0 +1 @@
* Use a "soft-delete" approach when deleting a host so that its script execution details are still available for the activities feed.

View file

@ -0,0 +1 @@
* Fixed the `/mdm/apple/mdm` endpoint so that it returns status code 408 (request timeout) instead of 500 (internal server error) when encountering a timeout reading the request body.

1
changes/18427-cert-names Normal file
View file

@ -0,0 +1 @@
* Use Fleet instead of FleetDM in MDM certificates

View file

@ -0,0 +1 @@
removed vscode false positive vulnerabilities

View file

@ -0,0 +1 @@
- Fixed UI bug where Zoom icon was displayed for ZoomInfo.

View file

@ -0,0 +1 @@
- Cleanup count rendering fixing clientside flashing counts

View file

@ -0,0 +1,2 @@
- Fixed UI bug where error detail was overflowing the table in "OS settings" modal in "My device"
page UI.

View file

@ -0,0 +1 @@
- Clean up software empty states in the UI

View file

@ -0,0 +1 @@
* Fixed an issue with the Windows-specific `windows-remove-fleetd.ps1` script provided in the Fleet repository where running the script did remove `fleetd` but made it impossible to reinstall the agent.

View file

@ -0,0 +1 @@
* Fixed a code linter issue where a slice was created non-empty and appended-to, instead of empty with the required capacity.

View file

@ -0,0 +1 @@
* Fixed a panic (API returning code 500) when the software installer exists in the database but the installer does not exist in the storage.

View file

@ -0,0 +1,2 @@
* Enabled `fleetctl gitops` to create teams with no enroll secrets, or clear enroll secrets for an existing team. This is done by setting `team_settings.secrets` to nothing or to null or to an empty array ( `[]` ) in YAML.
* Enabled `fleetctl apply` to create teams with no enroll secrets, or clear enroll secrets for an existing team. This is done by setting `team.secrets` to an empty array in YAML.

View file

@ -0,0 +1,3 @@
Fixed host details page and device details page not showing the latest software.
Added `exclude_software` query parameter to the `/api/latest/fleet/hosts/:id` endpoint to exclude software from the response.

View file

@ -0,0 +1 @@
* Extended the timeout for the endpoint to upload a software installer (`POST /fleet/software/package`), and improved handling of the maximum size.

View file

@ -0,0 +1 @@
* Fixed a bug that prevented unused script contents to be periodically cleaned up from the database.

View file

@ -0,0 +1 @@
- Fixed bug where MDM migration failed when attempting to renew enrollment profiles on macOS Sonoma devices.

2
changes/19545-unlock-pin Normal file
View file

@ -0,0 +1,2 @@
* /api/latest/fleet/hosts/:id/lock returns `unlock_pin` for Apple hosts
* UI no longer uses unlock pending state for Apple hosts

View file

@ -0,0 +1 @@
* Fixed the Linux unlock script to support passwordless users.

View file

@ -0,0 +1 @@
* Added a server setting to configure the query repory cap size, `server_settings.query_report_cap` (default is 1000).

1
changes/19612-idp-ingest Normal file
View file

@ -0,0 +1 @@
- Fixes issue where the MDM ingestion flow would fail if an invalid enrollment reference was passed.

View file

@ -0,0 +1 @@
* Improved the logic used by Fleet to detect if a host is currently MDM-managed.

View file

@ -0,0 +1 @@
- Host policy table can be sortable by response and View all host link preserves the team

View file

@ -0,0 +1 @@
* Added support to wipe iOS/iPadOS devices.

View file

@ -0,0 +1 @@
- Fixes an issue with backwards compatibility with the deprecated `FLEET_S3_*` environment variables.

View file

@ -0,0 +1 @@
- Improved db usage when sending statistics

View file

@ -8,7 +8,7 @@ version: v6.0.2
home: https://github.com/fleetdm/fleet
sources:
- https://github.com/fleetdm/fleet.git
appVersion: v4.51.0
appVersion: v4.51.1
dependencies:
- name: mysql
condition: mysql.enabled

View file

@ -267,7 +267,7 @@ spec:
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
## APEND ENVIRONMENT VARIABLES FROM SECRETS/CMs
## APPEND ENVIRONMENT VARIABLES FROM SECRETS/CMs
{{- range .Values.envsFrom }}
- name: {{ .name }}
valueFrom:

View file

@ -2,7 +2,7 @@
# All settings related to how Fleet is deployed in Kubernetes
hostName: fleet.localhost
replicas: 3 # The number of Fleet instances to deploy
imageTag: v4.51.0 # Version of Fleet to deploy
imageTag: v4.51.1 # Version of Fleet to deploy
podAnnotations: {} # Additional annotations to add to the Fleet pod
serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account
resources:

View file

@ -7,8 +7,8 @@ import (
"time"
"github.com/fleetdm/fleet/v4/server/config"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
_ "github.com/go-sql-driver/mysql"
"github.com/spf13/cobra"
)

View file

@ -54,10 +54,10 @@ import (
"github.com/fleetdm/fleet/v4/server/sso"
"github.com/fleetdm/fleet/v4/server/version"
"github.com/getsentry/sentry-go"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/go-kit/log"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/google/uuid"
"github.com/ngrok/sqlmw"
"github.com/prometheus/client_golang/prometheus"
@ -74,6 +74,8 @@ import (
var allowedURLPrefixRegexp = regexp.MustCompile("^(?:/[a-zA-Z0-9_.~-]+)+$")
const softwareInstallerUploadTimeout = 2 * time.Minute
type initializer interface {
// Initialize is used to populate a datastore with
// preloaded data
@ -196,7 +198,7 @@ the way that the Fleet server works.
}
ds = mds
if config.S3.CarvesBucket != "" {
if config.S3.CarvesBucket != "" || config.S3.Bucket != "" {
carveStore, err = s3.NewCarveStore(config.S3, ds)
if err != nil {
initFatal(err, "initializing S3 carvestore")
@ -1027,7 +1029,7 @@ the way that the Fleet server works.
}
}
// We must wrap the Handler here to set special per-endpoint Write
// We must wrap the Handler here to set special per-endpoint Read/Write
// timeouts, so that we have access to the raw http.ResponseWriter.
// Otherwise, the handler is wrapped by the promhttp response delegator,
// which does not support the Unwrap call needed to work with
@ -1038,6 +1040,9 @@ the way that the Fleet server works.
// does not implement.
rootMux.HandleFunc("/api/", func(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/fleet/scripts/run/sync") {
// when running a script synchronously, we wait a while for a script
// execution result, so the write timeout (to write the response)
// must be extended.
rc := http.NewResponseController(rw)
// add an additional 30 seconds to prevent race conditions where the
// request is terminated early.
@ -1045,6 +1050,25 @@ the way that the Fleet server works.
level.Error(logger).Log("msg", "http middleware failed to override endpoint write timeout", "err", err)
}
}
if req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/fleet/software/package") {
// when uploading a software installer, the file might be large so
// the read timeout (to read the full request body) must be extended.
rc := http.NewResponseController(rw)
// the frontend times out waiting for the upload after 2 minutes, so
// use that same timeout:
// https://www.figma.com/design/oQl2oQUG0iRkUy0YOxc307/%2314921-Deploy-security-agents-to-macOS%2C-Windows%2C-and-Linux-hosts?node-id=773-18032&t=QjEU6tc73tddNSqn-0
if err := rc.SetReadDeadline(time.Now().Add(softwareInstallerUploadTimeout)); err != nil {
level.Error(logger).Log("msg", "http middleware failed to override endpoint read timeout", "err", err)
}
// the write timeout should be extended as well to give the server time to
// write a response body with the right error, otherwise the connection is
// terminated abruptly.
if err := rc.SetWriteDeadline(time.Now().Add(softwareInstallerUploadTimeout + 30*time.Second)); err != nil {
level.Error(logger).Log("msg", "http middleware failed to override endpoint write timeout", "err", err)
}
req.Body = http.MaxBytesReader(rw, req.Body, service.MaxSoftwareInstallerSize)
}
apiHandler.ServeHTTP(rw, req)
})
rootMux.Handle("/", frontendHandler)

View file

@ -26,9 +26,9 @@ import (
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/fleetdm/fleet/v4/server/service/schedule"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/go-kit/log"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mozilla.org/pkcs7"

View file

@ -13,8 +13,8 @@ import (
"github.com/fleetdm/fleet/v4/server/config"
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
"github.com/fleetdm/fleet/v4/server/fleet"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/spf13/cobra"
)

View file

@ -843,9 +843,7 @@ func getHostsCommand() *cli.Command {
}
if c.Bool("mdm") {
// hosts enrolled (automatic or manual) in Fleet's MDM server
query.Set("mdm_name", fleet.WellKnownMDMFleet)
query.Set("mdm_enrollment_status", string(fleet.MDMEnrollStatusEnrolled))
query.Set("connected_to_fleet", "true")
}
if c.Bool("mdm-pending") {
// hosts pending enrollment in Fleet's MDM server

View file

@ -181,22 +181,29 @@ func TestBasicTeamGitOps(t *testing.T) {
CreatedAt: time.Now(),
Name: teamName,
}
var savedTeam *fleet.Team
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
if name == teamName {
return team, nil
if name == teamName && savedTeam != nil {
return savedTeam, nil
}
return nil, nil
return nil, &notFoundError{}
}
ds.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) {
if tid == team.ID {
return team, nil
return savedTeam, nil
}
return nil, nil
}
var enrolledTeamSecrets []*fleet.EnrollSecret
ds.NewTeamFunc = func(ctx context.Context, newTeam *fleet.Team) (*fleet.Team, error) {
newTeam.ID = team.ID
savedTeam = newTeam
enrolledTeamSecrets = newTeam.Secrets
return newTeam, nil
}
ds.IsEnrollSecretAvailableFunc = func(ctx context.Context, secret string, new bool, teamID *uint) (bool, error) {
return true, nil
}
var savedTeam *fleet.Team
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
savedTeam = team
return team, nil
@ -205,10 +212,6 @@ func TestBasicTeamGitOps(t *testing.T) {
require.ElementsMatch(t, labels, []string{fleet.BuiltinLabelMacOS14Plus})
return map[string]uint{fleet.BuiltinLabelMacOS14Plus: 1}, nil
}
ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
declaration.DeclarationUUID = uuid.NewString()
return declaration, nil
}
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
@ -216,16 +219,15 @@ func TestBasicTeamGitOps(t *testing.T) {
return nil
}
var enrolledSecrets []*fleet.EnrollSecret
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
enrolledSecrets = secrets
enrolledTeamSecrets = secrets
return nil
}
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
t.Setenv("TEST_SECRET", secret)
t.Setenv("TEST_SECRET", "")
_, err = tmpFile.WriteString(
`
@ -235,7 +237,7 @@ policies:
agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"}]
secrets: ${TEST_SECRET}
`,
)
require.NoError(t, err)
@ -255,8 +257,17 @@ team_settings:
_ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name()})
require.NotNil(t, savedTeam)
assert.Equal(t, teamName, savedTeam.Name)
require.Len(t, enrolledSecrets, 1)
assert.Equal(t, secret, enrolledSecrets[0].Secret)
assert.Empty(t, enrolledTeamSecrets)
// The previous run created the team, so let's rerun with an existing team
_ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name()})
assert.Empty(t, enrolledTeamSecrets)
// Add a secret
t.Setenv("TEST_SECRET", fmt.Sprintf("[{\"secret\":\"%s\"}]", secret))
_ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name()})
require.Len(t, enrolledTeamSecrets, 1)
assert.Equal(t, secret, enrolledTeamSecrets[0].Secret)
}
func TestFullGlobalGitOps(t *testing.T) {
@ -407,6 +418,7 @@ func TestFullGlobalGitOps(t *testing.T) {
assert.Equal(t, orgName, savedAppConfig.OrgInfo.OrgName)
assert.Equal(t, fleetServerURL, savedAppConfig.ServerSettings.ServerURL)
assert.Contains(t, string(*savedAppConfig.AgentOptions), "distributed_denylist_duration")
assert.Equal(t, 2000, savedAppConfig.ServerSettings.QueryReportCap)
assert.Len(t, enrolledSecrets, 2)
assert.True(t, policyDeleted)
assert.Len(t, appliedPolicySpecs, 5)
@ -912,7 +924,6 @@ team_settings:
_ = runAppForTest(t, []string{"gitops", "-f", globalFile.Name(), "-f", teamFile.Name(), "--delete-other-teams"})
assert.True(t, ds.ListTeamsFuncInvoked)
assert.True(t, ds.DeleteTeamFuncInvoked)
}
func TestFullGlobalAndTeamGitOps(t *testing.T) {
@ -1048,7 +1059,6 @@ func TestTeamSofwareInstallersGitOps(t *testing.T) {
}
})
}
}
func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig, **fleet.Team) {

View file

@ -6,7 +6,6 @@ import (
"net/http"
"os"
"slices"
"strings"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/service"
@ -116,10 +115,7 @@ func mdmRunCommand() *cli.Command {
}
mdmPlatform = mdmHostPlatform
// TODO(mna): this "On" check is brittle, but looks like it's the only
// enrollment indication we have right now...
if host.MDM.EnrollmentStatus == nil || !strings.HasPrefix(*host.MDM.EnrollmentStatus, "On") ||
host.MDM.Name != fleet.WellKnownMDMFleet {
if host.MDM.ConnectedToFleet == nil || !*host.MDM.ConnectedToFleet {
return errors.New(`Can't run the MDM command because one or more hosts have MDM turned off. Run the following command to see a list of hosts with MDM on: fleetctl get hosts --mdm.`)
}
@ -316,7 +312,6 @@ func hostMdmActionSetup(c *cli.Context, hostIdent string, actionType string) (cl
if err != nil {
var nfe service.NotFoundErr
if errors.As(err, &nfe) {
fmt.Println(hostIdent)
return nil, nil, errors.New("The host doesn't exist. Please provide a valid host identifier.")
}
@ -331,8 +326,7 @@ func hostMdmActionSetup(c *cli.Context, hostIdent string, actionType string) (cl
// check mdm is on for the host
if fleet.MDMSupported(host.Platform) {
if host.MDM.EnrollmentStatus == nil || !strings.HasPrefix(*host.MDM.EnrollmentStatus, "On") ||
host.MDM.Name != fleet.WellKnownMDMFleet {
if host.MDM.ConnectedToFleet == nil || !*host.MDM.ConnectedToFleet {
return nil, nil, fmt.Errorf("Can't %s the host because it doesn't have MDM turned on.", actionType)
}
}

View file

@ -2,6 +2,7 @@ package main
import (
"context"
"errors"
"fmt"
"os"
"slices"
@ -36,14 +37,14 @@ func TestMDMRunCommand(t *testing.T) {
UUID: "mac-enrolled",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolled := &fleet.Host{
ID: 2,
UUID: "win-enrolled",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macUnenrolled := &fleet.Host{
ID: 3,
@ -65,42 +66,42 @@ func TestMDMRunCommand(t *testing.T) {
UUID: "mac-enrolled-2",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolled2 := &fleet.Host{
ID: 7,
UUID: "win-enrolled-2",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macNonFleetEnrolled := &fleet.Host{
ID: 8,
UUID: "mac-non-fleet-enrolled",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMJamf},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMJamf, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMJamf, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(false)},
}
winNonFleetEnrolled := &fleet.Host{
ID: 9,
UUID: "win-non-fleet-enrolled",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMIntune},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMIntune, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMIntune, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(false)},
}
macPending := &fleet.Host{
ID: 10,
UUID: "mac-pending",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winPending := &fleet.Host{
ID: 11,
UUID: "win-pending",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
hostByUUID := make(map[string]*fleet.Host)
hostByID := make(map[uint]*fleet.Host)
@ -233,6 +234,13 @@ func TestMDMRunCommand(t *testing.T) {
}
return h.MDMInfo, nil
}
ds.AreHostsConnectedToFleetMDMFunc = func(ctx context.Context, hosts []*fleet.Host) (map[string]bool, error) {
res := make(map[string]bool, len(hosts))
for _, h := range hosts {
res[h.UUID] = h.MDM.ConnectedToFleet != nil && *h.MDM.ConnectedToFleet
}
return res, nil
}
enqueuer.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.Command) (map[string]error, error) {
return map[string]error{}, nil
@ -315,14 +323,14 @@ func TestMDMLockCommand(t *testing.T) {
UUID: "mac-enrolled",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolled := &fleet.Host{
ID: 2,
UUID: "win-enrolled",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
linuxEnrolled := &fleet.Host{
@ -345,57 +353,49 @@ func TestMDMLockCommand(t *testing.T) {
UUID: "mac-pending",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winPending := &fleet.Host{
ID: 7,
UUID: "win-pending",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winEnrolledUP := &fleet.Host{
ID: 8,
UUID: "win-enrolled-up",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledUP := &fleet.Host{
ID: 9,
UUID: "mac-enrolled-up",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
}
winEnrolledLP := &fleet.Host{
ID: 10,
UUID: "win-enrolled-lp",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledLP := &fleet.Host{
ID: 11,
UUID: "mac-enrolled-lp",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledWP := &fleet.Host{
ID: 12,
UUID: "win-enrolled-wp",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledWP := &fleet.Host{
ID: 13,
UUID: "mac-enrolled-wp",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
hostByUUID := make(map[string]*fleet.Host)
@ -409,7 +409,6 @@ func TestMDMLockCommand(t *testing.T) {
macPending,
winPending,
winEnrolledUP,
macEnrolledUP,
winEnrolledLP,
macEnrolledLP,
winEnrolledWP,
@ -421,7 +420,6 @@ func TestMDMLockCommand(t *testing.T) {
unlockPending := map[uint]*fleet.Host{
winEnrolledUP.ID: winEnrolledUP,
macEnrolledUP.ID: macEnrolledUP,
}
lockPending := map[uint]*fleet.Host{
@ -446,9 +444,7 @@ func TestMDMLockCommand(t *testing.T) {
if _, ok := unlockPending[host.ID]; ok {
if fleetPlatform == "darwin" {
status.UnlockPIN = "1234"
status.UnlockRequestedAt = time.Now()
return &status, nil
return nil, errors.New("apple devices do not have an unlock pending state")
}
status.UnlockScript = &fleet.HostScriptResult{}
@ -504,6 +500,10 @@ func TestMDMLockCommand(t *testing.T) {
}
}
ds.IsHostConnectedToFleetMDMFunc = func(ctx context.Context, host *fleet.Host) (bool, error) {
return host.MDMInfo != nil && host.MDMInfo.Enrolled == true && host.MDMInfo.Name == fleet.WellKnownMDMFleet, nil
}
appCfgAllMDM, appCfgWinMDM, appCfgMacMDM, appCfgNoMDM := setupAppConigs()
successfulOutput := func(ident string) string {
@ -542,7 +542,6 @@ fleetctl mdm unlock --host=%s
{appCfgWinMDM, "valid windows but pending ", []string{"--host", winPending.UUID}, `Can't lock the host because it doesn't have MDM turned on.`},
{appCfgMacMDM, "valid macos but pending", []string{"--host", macPending.UUID}, `Can't lock the host because it doesn't have MDM turned on.`},
{appCfgAllMDM, "valid windows but pending unlock", []string{"--host", winEnrolledUP.UUID}, "Host has pending unlock request."},
{appCfgAllMDM, "valid macos but pending unlock", []string{"--host", macEnrolledUP.UUID}, "Host has pending unlock request."},
{appCfgAllMDM, "valid windows but pending lock", []string{"--host", winEnrolledLP.UUID}, "Host has pending lock request."},
{appCfgAllMDM, "valid macos but pending lock", []string{"--host", macEnrolledLP.UUID}, "Host has pending lock request."},
{appCfgAllMDM, "valid windows but pending wipe", []string{"--host", winEnrolledWP.UUID}, "Host has pending wipe request."},
@ -558,14 +557,14 @@ func TestMDMUnlockCommand(t *testing.T) {
UUID: "mac-enrolled",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolled := &fleet.Host{
ID: 2,
UUID: "win-enrolled",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
linuxEnrolled := &fleet.Host{
ID: 3,
@ -587,56 +586,49 @@ func TestMDMUnlockCommand(t *testing.T) {
UUID: "mac-pending",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winPending := &fleet.Host{
ID: 7,
UUID: "win-pending",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winEnrolledUP := &fleet.Host{
ID: 8,
UUID: "win-enrolled-up",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
}
macEnrolledUP := &fleet.Host{
ID: 9,
UUID: "mac-enrolled-up",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledLP := &fleet.Host{
ID: 10,
UUID: "win-enrolled-lp",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledLP := &fleet.Host{
ID: 11,
UUID: "mac-enrolled-lp",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledWP := &fleet.Host{
ID: 12,
UUID: "win-enrolled-wp",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledWP := &fleet.Host{
ID: 13,
UUID: "mac-enrolled-wp",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
hostByUUID := make(map[string]*fleet.Host)
@ -650,7 +642,6 @@ func TestMDMUnlockCommand(t *testing.T) {
macPending,
winPending,
winEnrolledUP,
macEnrolledUP,
winEnrolledLP,
macEnrolledLP,
winEnrolledWP,
@ -667,7 +658,6 @@ func TestMDMUnlockCommand(t *testing.T) {
unlockPending := map[uint]*fleet.Host{
winEnrolledUP.ID: winEnrolledUP,
macEnrolledUP.ID: macEnrolledUP,
}
lockPending := map[uint]*fleet.Host{
@ -701,9 +691,7 @@ func TestMDMUnlockCommand(t *testing.T) {
if _, ok := unlockPending[host.ID]; ok {
if fleetPlatform == "darwin" {
status.UnlockPIN = "1234"
status.UnlockRequestedAt = time.Now()
return &status, nil
return nil, errors.New("apple devices do not have an unlock pending state")
}
status.UnlockScript = &fleet.HostScriptResult{}
@ -761,6 +749,9 @@ func TestMDMUnlockCommand(t *testing.T) {
return nil, nil
}
}
ds.IsHostConnectedToFleetMDMFunc = func(ctx context.Context, host *fleet.Host) (bool, error) {
return host.MDM.ConnectedToFleet != nil && *host.MDM.ConnectedToFleet, nil
}
appCfgAllMDM, appCfgWinMDM, appCfgMacMDM, appCfgNoMDM := setupAppConigs()
@ -800,7 +791,6 @@ fleetctl get host %s
{appCfgWinMDM, "valid windows but pending mdm enroll", []string{"--host", winPending.UUID}, `Can't unlock the host because it doesn't have MDM turned on.`},
{appCfgMacMDM, "valid macos but pending mdm enroll", []string{"--host", macPending.UUID}, `Can't unlock the host because it doesn't have MDM turned on.`},
{appCfgAllMDM, "valid windows but pending unlock", []string{"--host", winEnrolledUP.UUID}, "Host has pending unlock request."},
{appCfgAllMDM, "valid macos but pending unlock", []string{"--host", macEnrolledUP.UUID}, ""},
{appCfgAllMDM, "valid windows but pending lock", []string{"--host", winEnrolledLP.UUID}, "Host has pending lock request."},
{appCfgAllMDM, "valid macos but pending lock", []string{"--host", macEnrolledLP.UUID}, "Host has pending lock request."},
{appCfgAllMDM, "valid windows but pending wipe", []string{"--host", winEnrolledWP.UUID}, "Host has pending wipe request."},
@ -816,14 +806,14 @@ func TestMDMWipeCommand(t *testing.T) {
UUID: "mac-enrolled",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolled := &fleet.Host{
ID: 2,
UUID: "win-enrolled",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winNotEnrolled := &fleet.Host{
ID: 4,
@ -840,84 +830,77 @@ func TestMDMWipeCommand(t *testing.T) {
UUID: "mac-pending",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winPending := &fleet.Host{
ID: 7,
UUID: "win-pending",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: false, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("Pending"), ConnectedToFleet: ptr.Bool(false)},
}
winEnrolledUP := &fleet.Host{
ID: 8,
UUID: "win-enrolled-up",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
}
macEnrolledUP := &fleet.Host{
ID: 9,
UUID: "mac-enrolled-up",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledLP := &fleet.Host{
ID: 10,
UUID: "win-enrolled-lp",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledLP := &fleet.Host{
ID: 11,
UUID: "mac-enrolled-lp",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledWP := &fleet.Host{
ID: 12,
UUID: "win-enrolled-wp",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledWP := &fleet.Host{
ID: 13,
UUID: "mac-enrolled-wp",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledWiped := &fleet.Host{
ID: 14,
UUID: "win-enrolled-wiped",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledWiped := &fleet.Host{
ID: 15,
UUID: "mac-enrolled-wiped",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)"), ConnectedToFleet: ptr.Bool(true)},
}
winEnrolledLocked := &fleet.Host{
ID: 16,
UUID: "win-enrolled-locked",
Platform: "windows",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual"), ConnectedToFleet: ptr.Bool(true)},
}
macEnrolledLocked := &fleet.Host{
ID: 17,
UUID: "mac-enrolled-locked",
Platform: "darwin",
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual")},
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual"), ConnectedToFleet: ptr.Bool(true)},
}
linuxEnrolled := &fleet.Host{
ID: 18,
@ -950,7 +933,6 @@ func TestMDMWipeCommand(t *testing.T) {
macPending,
winPending,
winEnrolledUP,
macEnrolledUP,
winEnrolledLP,
macEnrolledLP,
winEnrolledWP,
@ -971,7 +953,6 @@ func TestMDMWipeCommand(t *testing.T) {
unlockPending := map[uint]*fleet.Host{
winEnrolledUP.ID: winEnrolledUP,
macEnrolledUP.ID: macEnrolledUP,
}
lockPending := map[uint]*fleet.Host{
@ -1010,9 +991,7 @@ func TestMDMWipeCommand(t *testing.T) {
if _, ok := unlockPending[host.ID]; ok {
if fleetPlatform == "darwin" {
status.UnlockPIN = "1234"
status.UnlockRequestedAt = time.Now()
return &status, nil
return nil, errors.New("apple devices do not have an unlock pending state")
}
status.UnlockScript = &fleet.HostScriptResult{}
@ -1104,6 +1083,9 @@ func TestMDMWipeCommand(t *testing.T) {
return nil, nil
}
}
ds.IsHostConnectedToFleetMDMFunc = func(ctx context.Context, host *fleet.Host) (bool, error) {
return host.MDM.ConnectedToFleet != nil && *host.MDM.ConnectedToFleet, nil
}
appCfgAllMDM, appCfgWinMDM, appCfgMacMDM, appCfgNoMDM := setupAppConigs()
appCfgScriptsDisabled := &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ScriptsDisabled: true}}
@ -1129,7 +1111,6 @@ func TestMDMWipeCommand(t *testing.T) {
{appCfgWinMDM, "valid windows but pending mdm enroll", []string{"--host", winPending.UUID}, `Can't wipe the host because it doesn't have MDM turned on.`},
{appCfgMacMDM, "valid macos but pending mdm enroll", []string{"--host", macPending.UUID}, `Can't wipe the host because it doesn't have MDM turned on.`},
{appCfgAllMDM, "valid windows but pending unlock", []string{"--host", winEnrolledUP.UUID}, "Host has pending unlock request."},
{appCfgAllMDM, "valid macos but pending unlock", []string{"--host", macEnrolledUP.UUID}, "Host has pending unlock request."},
{appCfgAllMDM, "valid windows but pending lock", []string{"--host", winEnrolledLP.UUID}, "Host has pending lock request."},
{appCfgAllMDM, "valid macos but pending lock", []string{"--host", macEnrolledLP.UUID}, "Host has pending lock request."},
{appCfgAllMDM, "valid windows but pending wipe", []string{"--host", winEnrolledWP.UUID}, "Host has pending wipe request."},

View file

@ -11,8 +11,8 @@ import (
"github.com/fleetdm/fleet/v4/server/live_query/live_query_mock"
"github.com/fleetdm/fleet/v4/server/pubsub"
"github.com/fleetdm/fleet/v4/server/service"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View file

@ -11,6 +11,7 @@
"server_settings": {
"server_url": "",
"live_query_disabled": false,
"query_report_cap": 0,
"query_reports_disabled": false,
"enable_analytics": false,
"deferred_save_host": false,

View file

@ -59,6 +59,7 @@ spec:
deferred_save_host: false
enable_analytics: false
live_query_disabled: false
query_report_cap: 0
query_reports_disabled: false
server_url: ""
scripts_disabled: false

View file

@ -11,6 +11,7 @@
"server_settings": {
"server_url": "",
"live_query_disabled": false,
"query_report_cap": 0,
"query_reports_disabled": false,
"enable_analytics": false,
"deferred_save_host": false,

View file

@ -98,6 +98,7 @@ spec:
deferred_save_host: false
enable_analytics: false
live_query_disabled: false
query_report_cap: 0
query_reports_disabled: false
server_url: ""
scripts_disabled: false

View file

@ -29,6 +29,7 @@ spec:
enable_release_device_manually: false
macos_setup_assistant:
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null

View file

@ -50,7 +50,8 @@
"enrollment_status": null,
"name": "",
"pending_action": "",
"server_url": null
"server_url": null,
"connected_to_fleet": null
},
"team_id": null,
"pack_stats": null,

View file

@ -40,6 +40,7 @@ spec:
name: ""
pending_action: ""
server_url: null
connected_to_fleet: null
memory: 0
orbit_version: null
os_version: ""

View file

@ -49,7 +49,8 @@
"encryption_key_available": false,
"enrollment_status": null,
"name": "",
"server_url": null
"server_url": null,
"connected_to_fleet": null
},
"team_id": null,
"pack_stats": null,
@ -124,7 +125,8 @@
"encryption_key_available": false,
"enrollment_status": null,
"name": "",
"server_url": null
"server_url": null,
"connected_to_fleet": null
},
"team_id": null,
"pack_stats": null,

View file

@ -50,7 +50,8 @@
"encryption_key_available": false,
"enrollment_status": null,
"name": "",
"server_url": null
"server_url": null,
"connected_to_fleet": null
},
"team_id": null,
"pack_stats": null,
@ -125,7 +126,8 @@
"encryption_key_available": false,
"enrollment_status": null,
"name": "",
"server_url": null
"server_url": null,
"connected_to_fleet": null
},
"team_id": null,
"pack_stats": null,

View file

@ -42,6 +42,7 @@ spec:
enrollment_status: null
name: ""
server_url: null
connected_to_fleet: null
memory: 0
orbit_version: null
os_version: ""
@ -101,6 +102,7 @@ spec:
encryption_key_available: false
enrollment_status: null
server_url: null
connected_to_fleet: null
memory: 0
os_version: ""
osquery_version: ""

View file

@ -101,6 +101,7 @@ org_settings:
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 2000
query_reports_disabled: false
scripts_disabled: false
server_url: $FLEET_SERVER_URL

View file

@ -59,6 +59,7 @@ spec:
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 0
query_reports_disabled: false
server_url: https://example.org
scripts_disabled: false

View file

@ -59,6 +59,7 @@ spec:
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 0
query_reports_disabled: false
server_url: https://example.org
scripts_disabled: false

View file

@ -29,6 +29,7 @@ spec:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null
@ -63,6 +64,7 @@ spec:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null

View file

@ -29,6 +29,7 @@ spec:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null
@ -63,6 +64,7 @@ spec:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null

View file

@ -29,6 +29,7 @@ spec:
windows_settings:
custom_settings: null
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null

View file

@ -28,6 +28,7 @@ spec:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
software: null
webhook_settings:
host_status_webhook: null

View file

@ -12,7 +12,7 @@ import (
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/fleetdm/fleet/v4/server/service/schedule"
kitlog "github.com/go-kit/kit/log"
kitlog "github.com/go-kit/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View file

@ -160,7 +160,7 @@ func createUserCommand() *cli.Command {
force_reset := !sso && !apiOnly
// password requirements are validated as part of `CreateUser`
err = client.CreateUser(fleet.UserPayload{
sessionKey, err := client.CreateUser(fleet.UserPayload{
Password: &password,
Email: &email,
Name: &name,
@ -174,6 +174,10 @@ func createUserCommand() *cli.Command {
return fmt.Errorf("Failed to create user: %w", err)
}
if apiOnly && sessionKey != nil && *sessionKey != "" {
fmt.Fprintf(c.App.Writer, "Success! The API token for your new user is: %s\n", *sessionKey)
}
return nil
},
}
@ -208,7 +212,6 @@ func createBulkUsersCommand() *cli.Command {
}
defer csvFile.Close()
csvLines, err := csv.NewReader(csvFile).ReadAll()
if err != nil {
return err
}
@ -278,7 +281,7 @@ func createBulkUsersCommand() *cli.Command {
}
for _, user := range users {
err = client.CreateUser(user)
_, err = client.CreateUser(user)
if err != nil {
return fmt.Errorf("Failed to create user: %w", err)
}
@ -351,7 +354,6 @@ func deleteBulkUsersCommand() *cli.Command {
}
defer csvFile.Close()
csvLines, err := csv.NewReader(csvFile).ReadAll()
if err != nil {
return err
}
@ -362,10 +364,10 @@ func deleteBulkUsersCommand() *cli.Command {
}
}
return nil
},
}
}
func generateRandomPassword() (string, error) {
password, err := password.Generate(20, 2, 2, false, true)
if err != nil {

View file

@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"encoding/csv"
"fmt"
"math/big"
"os"
"strings"
@ -73,31 +74,57 @@ func TestUserCreateForcePasswordReset(t *testing.T) {
) error {
return nil
}
ds.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) {
if email == "bar@example.com" {
apiOnlyUser := &fleet.User{
ID: 1,
Email: email,
}
err := apiOnlyUser.SetPassword(pwd, 24, 10)
require.NoError(t, err)
return apiOnlyUser, nil
}
return nil, &notFoundError{}
}
var apiOnlyUserSessionKey string
ds.NewSessionFunc = func(ctx context.Context, userID uint, sessionKey string) (*fleet.Session, error) {
apiOnlyUserSessionKey = sessionKey
return &fleet.Session{
ID: 2,
UserID: userID,
Key: sessionKey,
}, nil
}
for _, tc := range []struct {
name string
args []string
expectedAdminForcePasswordReset bool
displaysToken bool
}{
{
name: "sso",
args: []string{"--email", "foo@example.com", "--name", "foo", "--sso"},
expectedAdminForcePasswordReset: false,
displaysToken: false,
},
{
name: "api-only",
args: []string{"--email", "bar@example.com", "--password", pwd, "--name", "bar", "--api-only"},
expectedAdminForcePasswordReset: false,
displaysToken: true,
},
{
name: "api-only-sso",
args: []string{"--email", "baz@example.com", "--name", "baz", "--api-only", "--sso"},
expectedAdminForcePasswordReset: false,
displaysToken: false,
},
{
name: "non-sso-non-api-only",
args: []string{"--email", "zoo@example.com", "--password", pwd, "--name", "zoo"},
expectedAdminForcePasswordReset: true,
displaysToken: false,
},
} {
ds.NewUserFuncInvoked = false
@ -106,10 +133,15 @@ func TestUserCreateForcePasswordReset(t *testing.T) {
return user, nil
}
require.Equal(t, "", runAppForTest(t, append(
stdout := runAppForTest(t, append(
[]string{"user", "create"},
tc.args...,
)))
))
if tc.displaysToken {
require.Equal(t, stdout, fmt.Sprintf("Success! The API token for your new user is: %s\n", apiOnlyUserSessionKey))
} else {
require.Empty(t, stdout)
}
require.True(t, ds.NewUserFuncInvoked)
}
}

View file

@ -144,7 +144,7 @@ You can verify that these flags have taken effect on the hosts by running a quer
> If you revoked an old enroll secret, this feature won't update for hosts that were added to Fleet using this old enroll secret. This is because Fleetd uses the enroll secret to receive new flags from Fleet. For these hosts, all existing features will work as expected.
For further documentation on how to rotate enroll secrets, please see [this guide](#rotating-enroll-secrets).
For further documentation on how to rotate enroll secrets, please see [this guide](https://fleetdm.com/docs/configuration/configuration-files#rotating-enroll-secrets).
If you prefer to deploy a new package with the updated enroll secret:

View file

@ -2807,7 +2807,7 @@ packaging:
> The [`server_private_key` configuration option](#server_private_key) is required for macOS MDM features.
> The Apple Push Notification service (APNs), Simple Certificate Enrollment Protocol (SCEP), and Apple Business Manager (ABM) [certificate and key configuration](https://github.com/fleetdm/fleet/fleet-v4.51.0/main/docs/Contributing/Configuration-for-contributors.md#mobile-device-management-mdm) are deprecated as of Fleet 4.51. They are maintained for backwards compatibility. Please upload your APNs certificate and ABM token. Learn how [here](../Using%20Fleet/MDM-setup.md).
> The Apple Push Notification service (APNs), Simple Certificate Enrollment Protocol (SCEP), and Apple Business Manager (ABM) [certificate and key configuration](https://github.com/fleetdm/fleet/blob/fleet-v4.51.0/docs/Contributing/Configuration-for-contributors.md#mobile-device-management-mdm) are deprecated as of Fleet 4.51. They are maintained for backwards compatibility. Please upload your APNs certificate and ABM token. Learn how [here](https://fleetdm.com/docs/using-fleet/mdm-setup).
##### mdm.apple_scep_signer_validity_days

View file

@ -505,95 +505,24 @@ To run your local server with the MDM features enabled, you need to get certific
### ABM setup
To enable the [DEP](https://github.com/fleetdm/fleet/blob/main/tools/mdm/apple/glossary-and-protocols.md#dep-device-enrollment-program) enrollment flow, the Fleet server needs three things:
1. A private key.
1. A certificate.
1. An encrypted token generated by Apple.
#### Private key, certificate, and encrypted token
To enable the [DEP](https://github.com/fleetdm/fleet/blob/main/tools/mdm/apple/glossary-and-protocols.md#dep-device-enrollment-program) enrollment flow, the Fleet server needs an encrypted token generated by Apple.
First ask @lukeheath to create an account for you in [ABM](https://github.com/fleetdm/fleet/blob/main/tools/mdm/apple/glossary-and-protocols.md#abm-apple-business-manager). You'll need an account to generate an encrypted token.
Once you have access to ABM, follow [these guided instructions](https://fleetdm.com/docs/using-fleet/mdm-setup#apple-business-manager-abm) in the user facing docs to generate the private key, certificate, and encrypted token.
Once you have access to ABM, follow [these guided instructions](https://fleetdm.com/docs/using-fleet/mdm-setup#apple-business-manager-abm) to get and upload the encrypted token.
### APNs and SCEP setup
The server also needs a private key + certificate to identify with Apple's [APNs](https://github.com/fleetdm/fleet/blob/main/tools/mdm/apple/glossary-and-protocols.md#apns-apple-push-notification-service) servers, and another for [SCEP](https://github.com/fleetdm/fleet/blob/main/tools/mdm/apple/glossary-and-protocols.md#scep-simple-certificate-enrollment-protocol).
The server also needs a certificate to identify with Apple's [APNs](https://github.com/fleetdm/fleet/blob/main/tools/mdm/apple/glossary-and-protocols.md#apns-apple-push-notification-service) servers.
To generate both, follow [these guided instructions](https://fleetdm.com/docs/using-fleet/mdm-macos-setup#apple-push-notification-service-apns).
To get a certificate and upload it, [these guided instructions](https://fleetdm.com/docs/using-fleet/mdm-macos-setup#apple-push-notification-service-apns).
Note that:
1. Fleet must be running to generate the certificates and keys.
1. Fleet must be running to generate the token and certificate.
2. You must be logged in to Fleet as a global admin. See [Building Fleet](./Building-Fleet.md) for details on getting Fleet setup locally.
3. To login into https://identity.apple.com/pushcert you can use your ABM account generated in the previous step.
4. Save all the certificates and keys in a safe place.
Internally, the certificates are generated using this flow. Note that the fleet sails API base url can be changed using the `TEST_FLEETDM_API_URL` environment variable.
```mermaid
sequenceDiagram
participant user as user email
participant fleetctl as fleetctl
participant server as fleet server
participant fleetdm as fleetdm.com sails app
participant apple as identity.apple.com
link apple: PushCert @ https://identity.apple.com/pushcert
note over fleetctl: fleetctl login
fleetctl->>+server: login
server-->>-fleetctl: token
note over fleetctl: fleetctl generate mdm_apple
fleetctl->>+server: generate certificates
server->>server: generate self-signed SCEP cert & key
server->>server: generate APNs key
server->>server: generate APNs CSR
server-)+fleetdm: request vendor signature on APNs CSR
server-->>-fleetctl: SCEP cert, SCEP key, APNs key
note over fleetdm: calls /ee/tools/mdm/cert
fleetdm--)-user: vendor-signed APNs CSR
user->>+apple: vendor-signed APNs CSR
note right of apple: managed through web ui
apple-->>-user: Apple-signed APNs certificate
```
Another option, if for some reason, generating the certificates and keys fails or you don't have a supported email address handy is to use `openssl` to generate your SCEP key pair:
```sh
$ openssl genrsa -out fleet-mdm-apple-scep.key 4096
$ openssl req -x509 -new -nodes -key fleet-mdm-apple-scep.key -sha256 -days 1826 -out fleet-mdm-apple-scep.crt -subj '/CN=Fleet Root CA/C=US/O=Fleet DM.'
```
### Running the server
Try to store all the certificates and tokens you generated in the earlier steps together in a safe place outside of the repo, then start the server with:
```sh
FLEET_MDM_APPLE_SCEP_CHALLENGE=scepchallenge \
FLEET_MDM_APPLE_SCEP_CERT=/path/to/fleet-mdm-apple-scep.crt \
FLEET_MDM_APPLE_SCEP_KEY=/path/to/fleet-mdm-apple-scep.key \
FLEET_MDM_APPLE_BM_SERVER_TOKEN=/path/to/dep_encrypted_token.p7m \
FLEET_MDM_APPLE_BM_CERT=/path/to/fleet-apple-mdm-bm-public-key.crt \
FLEET_MDM_APPLE_BM_KEY=/path/to/fleet-apple-mdm-bm-private.key \
FLEET_MDM_APPLE_APNS_CERT=/path/to/mdmcert.download.push.pem \
FLEET_MDM_APPLE_APNS_KEY=/path/to/mdmcert.download.push.key \
./build/fleet serve --dev --dev_license --logging_debug
```
Note: if you need to enroll VMs using MDM, the server needs to run behind TLS with a valid certificate. In a separate terminal window/tab, create a local tunnel to your server using `ngrok` (`brew install ngrok/ngrok/ngrok` if you don't have it.)
```sh
ngrok http https://localhost:8080
```
> NOTE: If this is your first time using ngrok this command will fail and you will see a message
> about signing up. Open the sign up link and complete the sign up flow. You can rerun the same command
> and ngrok should work this time. After this open the forwarding link, you will be asked to confirm that you'd like
> to be forwarded to your local server and should accept.
Don't forget to edit your Fleet server settings (through the UI or `fleetctl`) to use the URL `ngrok` provides to you. You need to do this whenever you restart `ngrok`.
4. Save the token and certificate in a safe place.
### Testing MDM

View file

@ -1,4 +1,4 @@
# REST API
# REST API
- [Authentication](#authentication)
- [Activities](#activities)
@ -880,6 +880,7 @@ None.
"apple_bm_terms_expired": false,
"enabled_and_configured": true,
"windows_enabled_and_configured": true,
"enable_disk_encryption": true,
"macos_updates": {
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
@ -889,11 +890,20 @@ None.
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": true
"custom_settings": [
{
"path": "path/to/profile1.mobileconfig",
"labels": ["Label 1", "Label 2"]
}
]
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
"custom_settings": [
{
"path": "path/to/profile2.xml",
"labels": ["Label 3", "Label 4"]
}
],
},
"scripts": ["path/to/script.sh"],
"end_user_authentication": {
@ -983,6 +993,10 @@ None.
"enable_vulnerabilities_webhook":true,
"destination_url": "https://server.com",
"host_batch_size": 1000
},
"activities_webhook":{
"enable_activities_webhook":true,
"destination_url": "https://server.com"
}
},
"integrations": {
@ -1098,6 +1112,8 @@ Modifies the Fleet's configuration with the supplied information.
| enable_vulnerabilities_webhook | boolean | body | _webhook_settings.vulnerabilities_webhook settings_. Whether or not the vulnerabilities webhook is enabled. |
| destination_url | string | body | _webhook_settings.vulnerabilities_webhook settings_. The URL to deliver the webhook requests to. |
| host_batch_size | integer | body | _webhook_settings.vulnerabilities_webhook settings_. Maximum number of hosts to batch on vulnerabilities webhook requests. The default, 0, means no batching (all vulnerable hosts are sent on one request). |
| enable_activities_webhook | boolean | body | _webhook_settings.activities_webhook settings_. Whether or not the activity feed webhook is enabled. |
| destination_url | string | body | _webhook_settings.activities_webhook settings_. The URL to deliver the webhook requests to. |
| enable_software_vulnerabilities | boolean | body | _integrations.jira[] settings_. Whether or not Jira integration is enabled for software vulnerabilities. Only one vulnerability automation can be enabled at a given time (enable_vulnerabilities_webhook and enable_software_vulnerabilities). |
| enable_failing_policies | boolean | body | _integrations.jira[] settings_. Whether or not Jira integration is enabled for failing policies. Only one failing policy automation can be enabled at a given time (enable_failing_policies_webhook and enable_failing_policies). |
| url | string | body | _integrations.jira[] settings_. The URL of the Jira server to integrate with. |
@ -1216,6 +1232,7 @@ Note that when making changes to the `integrations` object, all integrations mus
"apple_bm_enabled_and_configured": false,
"enabled_and_configured": false,
"windows_enabled_and_configured": false,
"enable_disk_encryption": true,
"macos_updates": {
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
@ -1225,21 +1242,24 @@ Note that when making changes to the `integrations` object, all integrations mus
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": {
"path": "path/to/profile1.mobileconfig",
"labels": ["Label 1", "Label 2"]
},
{
"path": "path/to/profile2.json",
"labels": ["Label 3", "Label 4"]
},
"enable_disk_encryption": true
"custom_settings": [
{
"path": "path/to/profile1.mobileconfig",
"labels": ["Label 1", "Label 2"]
},
{
"path": "path/to/profile2.json",
"labels": ["Label 3", "Label 4"]
},
]
},
"windows_settings": {
"custom_settings": {
"path": "path/to/profile3.xml",
"labels": ["Label 1", "Label 2"]
}
"custom_settings": [
{
"path": "path/to/profile3.xml",
"labels": ["Label 1", "Label 2"]
}
]
},
"end_user_authentication": {
"entity_id": "",
@ -1300,6 +1320,10 @@ Note that when making changes to the `integrations` object, all integrations mus
"enable_vulnerabilities_webhook":true,
"destination_url": "https://server.com",
"host_batch_size": 1000
},
"activities_webhook":{
"enable_activities_webhook":true,
"destination_url": "https://server.com"
}
},
"integrations": {
@ -5318,7 +5342,7 @@ Deletes the custom MDM setup enrollment profile assigned to a team or no team.
### Get manual enrollment profile
Retrieves the manual enrollment profile for macOS hosts. Install this profile on macOS hosts to turn on MDM features manually.
Retrieves an unsigned manual enrollment profile for macOS hosts. Install this profile on macOS hosts to turn on MDM features manually.
`GET /api/v1/fleet/enrollment_profiles/manual`
@ -6012,29 +6036,19 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
### Add policy
There are two ways of adding a policy:
1. Preferred: By setting `name`, `query`, and `description`.
2. Legacy: By setting `query_id` to reuse the data of an existing query. If `query_id` is set,
then `query` must not be set, and `name` and `description` are ignored.
An error is returned if both `query` and `query_id` are set on the request.
`POST /api/v1/fleet/global/policies`
#### Parameters
| Name | Type | In | Description |
| ---------- | ------- | ---- | ------------------------------------ |
| name | string | body | The query's name. |
| query | string | body | The query in SQL. |
| description | string | body | The query's description. |
| name | string | body | The policy's name. |
| query | string | body | The policy's query in SQL. |
| description | string | body | The policy's description. |
| resolution | string | body | The resolution steps for the policy. |
| query_id | integer | body | An existing query's ID (legacy). |
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
| critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. |
Either `query` or `query_id` must be provided.
#### Example (preferred)
`POST /api/v1/fleet/global/policies`
@ -6079,47 +6093,6 @@ Either `query` or `query_id` must be provided.
}
```
#### Example (legacy)
`POST /api/v1/fleet/global/policies`
#### Request body
```json
{
"query_id": 12
}
```
Where `query_id` references an existing `query`.
##### Default response
`Status: 200`
```json
{
"policy": {
"id": 43,
"name": "Gatekeeper enabled",
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
"description": "Checks if gatekeeper is enabled on macOS devices",
"critical": true,
"author_id": 42,
"author_name": "John",
"author_email": "john@example.com",
"team_id": null,
"resolution": "Resolution steps",
"platform": "darwin",
"created_at": "2022-03-17T20:15:55Z",
"updated_at": "2022-03-17T20:15:55Z",
"passing_host_count": 0,
"failing_host_count": 0,
"host_count_updated_at": null
}
}
```
### Remove policies
`POST /api/v1/fleet/global/policies/delete`
@ -6500,11 +6473,10 @@ The semantics for creating a team policy are the same as for global policies, se
| Name | Type | In | Description |
| ---------- | ------- | ---- | ------------------------------------ |
| id | integer | path | Defines what team ID to operate on. |
| name | string | body | The query's name. |
| query | string | body | The query in SQL. |
| description | string | body | The query's description. |
| name | string | body | The policy's name. |
| query | string | body | The policy's query in SQL. |
| description | string | body | The policy's description. |
| resolution | string | body | The resolution steps for the policy. |
| query_id | integer | body | An existing query's ID (legacy). |
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
| critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. |
@ -8164,7 +8136,7 @@ Download a software package.
| ---- | ------- | ---- | -------------------------------------------- |
| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
| team_id | integer | form | **Required**. The team ID. Downloads a software package added to the specified team. |
| alt | integer | path | **Required**. If specified and set to "media", downloads the specified software package. |
| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
#### Example
@ -9009,6 +8981,7 @@ _Available in Fleet Premium_
}
},
"mdm": {
"enable_disk_encryption": true,
"macos_updates": {
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
@ -9018,11 +8991,20 @@ _Available in Fleet Premium_
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": false
"custom_settings": [
{
"path": "path/to/profile1.mobileconfig",
"labels": ["Label 1", "Label 2"]
}
]
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
"custom_settings": [
{
"path": "path/to/profile2.xml",
"labels": ["Label 3", "Label 4"]
}
],
},
"macos_setup": {
"bootstrap_package": "",
@ -9293,6 +9275,7 @@ _Available in Fleet Premium_
}
},
"mdm": {
"enable_disk_encryption": true,
"macos_updates": {
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
@ -9302,11 +9285,20 @@ _Available in Fleet Premium_
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": false
"custom_settings": [
{
"path": "path/to/profile1.mobileconfig",
"labels": ["Label 1", "Label 2"]
}
]
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
"custom_settings": [
{
"path": "path/to/profile2.xml",
"labels": ["Label 3", "Label 4"]
}
],
},
"macos_setup": {
"bootstrap_package": "",

View file

@ -1,6 +1,6 @@
# Commands
In Fleet you can run MDM commands to take action on your macOS and Windows hosts, like restarting the host, remotely.
In Fleet you can run MDM commands to take action on your macOS, iOS, iPadOS, and Windows hosts, like restarting the host, remotely.
## Custom commands
@ -85,7 +85,7 @@ You can view a list of the 1,000 latest commands:
The command ID can be used to view command results as documented in [step 4 of the previous section](#step-4-view-the-commands-results).
The possible statuses for macOS hosts are the following:
The possible statuses for macOS, iOS, and iPadOS hosts are the following:
* Pending: the command has yet to run on the host. The host will run the command the next time it comes online.
* NotNow: the host responded with "NotNow" status via the MDM protocol: the host received the command, but couldnt execute it. The host will try to run the command the next time it comes online.

View file

@ -1,14 +1,14 @@
# Setup
To turn on macOS MDM features, follow the instructions on this page to connect Fleet to Apple Push Notification service (APNs).
To turn on macOS, iOS, and iPadOS MDM features, follow the instructions on this page to connect Fleet to Apple Push Notification service (APNs).
To use automatic enrollment (aka zero-touch) features on macOS, follow instructions to connect Fleet with Apple Business Manager (ABM).
To use automatic enrollment (aka zero-touch) features on macOS, iOS, and iPadOS, follow instructions to connect Fleet with Apple Business Manager (ABM).
To turn on Windows MDM features, head to this [Windows MDM setup article](https://fleetdm.com/guides/windows-mdm-setup).
## Apple Push Notification service (APNs)
Apple uses APNs to authenticate and manage interactions between Fleet and the host.
Apple uses APNs to authenticate and manage interactions between Fleet and hosts.
To connect Fleet to APNs or renew APNs, head to the **Settings > Integrations > Mobile device management (MDM)** page.
@ -30,9 +30,9 @@ After connecting Fleet to ABM, set Fleet to be the MDM for all Macs:
4. Click **MDM Server Assignment** and click **Edit** next to **Default Server Assignment**.
5. Switch **Mac** to Fleet.
New or wiped macOS hosts that are in ABM, before they've been set up, appear in Fleet with **MDM status** set to "Pending".
New or wiped macOS, iOS, and iPadOS hosts that are in ABM, before they've been set up, appear in Fleet with **MDM status** set to "Pending".
All hosts that automatically enroll will be assigned to the default team. If no default team is set, then the host will be placed in "No team".
All macOS hosts that automatically enroll will be assigned to the default team. If no default team is set, then the host will be placed in "No team".
> A host can be transferred to a new (not default) team before it enrolls. In the Fleet UI, you can do this under **Settings** > **Teams**.

View file

@ -1,46 +1,36 @@
# Segment hosts
`Applies only to Fleet Premium`
_Available in Fleet Premium_
```
In Fleet 4.0, Teams were introduced.
```
In Fleet, you can group hosts together in a "team" in Fleet. This way, you can apply queries, policies, scripts, and more that are tailored to the hosts' risk/compliance needs.
- [Overview](#overview)
- [Best practice](#best-practice)
- [Transfer hosts to a team](#transfer-hosts-to-a-team)
A host can only belong to one team.
## Overview
You can give users access to only some teams.
In Fleet, you can group hosts together in a team.
Then, you can give users access to only some teams.
This means you manage permissions so that some users can only run queries and manage hosts on the teams these users have access to.
You can manage teams in the Fleet UI by selecting **Settings** > **Teams** in the top navigation. From there, you can add or remove teams, manage user access to teams, transfer hosts, or modify team settings.
You can manage teams by selecting your avatar in the top navigation and then **Settings > Teams**.
## Best practice
The best practice is to create these teams: `Workstations`, `Workstations (canary)`, `Servers`, and `Servers (canary)`.
Fleet's best practice teams:
- `Workstations`: End user's production work computers (macOS, Windows, and Linux)
- `Workstations (canary)`: IT team's test work computers. Sometimes, for demos or testing, includes end user's work computers. Used for [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) a new workflow or feature that may or may not be rolled out to the "Workstations" team.
- `Servers`: Security team's production servers.
- `Servers (canary)`: Security team's test servers.
- `Compliance exclusions`: All contributors' test work computers or virtual machines (VMs). Used for validating workflows for Fleet customers or reproducing bugs in the Fleet product.
- `iPhones`: All contributors' test iOS hosts. Used to dogfood Fleet's iOS features (coming soon).
If some of your hosts don't fall under the above teams, what are these hosts for? The answer determines the the hosts' risk/compliance needs, and thus their security basline, and thus their "team" in Fleet. If the hosts' have a different compliance needs, and thus different security baseline, then it's time to create a new team in Fleet.
## Adding hosts to a team
Hosts can only belong to one team in Fleet.
You can add hosts to a new team in Fleet by either enrolling the host with a team's enroll secret or by transferring the host via the Fleet UI after the host has been enrolled to Fleet.
To automatically add hosts to a team in Fleet, check out the [**Adding hosts** documentation](https://fleetdm.com/docs/using-fleet/adding-hosts#automatically-adding-hosts-to-a-team).
> If a host was previously enrolled using a global enroll secret, changing the host's osquery enroll
> secret will not cause the host to be transferred to the desired team. You must delete the
> `osquery/osquery.db` file on the host, which forces the host to re-enroll
> using the new team enroll secret. Alternatively, you can transfer the host via the Fleet UI, the
> fleetctl CLI using `fleetctl hosts transfer`, or the [transfer host API endpoint](https://fleetdm.com/docs/using-fleet/rest-api#transfer-hosts-to-a-team).
## Advanced
You can automatically enroll hosts to a specific team in Fleet by installing a fleetd with a team enroll secret. Learn more [here](./enroll-hosts.md#enroll-host-to-a-specific-team).
Changing the host's enroll secret after enrollment will not cause the host to be transferred to a different team.
<meta name="pageOrderInSection" value="1000">
<meta name="description" value="Learn how to group hosts in Fleet to apply specific queries, policies, and agent options using teams.">

View file

@ -1,6 +1,6 @@
# Windows 10 Enterprise benchmarks
Fleet's policies have been written against v2.0.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version.
Fleet's policies have been written against v3.0.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version.
For requirements and usage details, see the [CIS Benchmarks](https://fleetdm.com/docs/using-fleet/cis-benchmarks) documentation.
@ -12,4 +12,4 @@ For requirements and usage details, see the [CIS Benchmarks](https://fleetdm.com
### Checks that require a Group Policy template
Several items require Group Policy templates in place in order to audit them.
These items are tagged with the label `CIS_group_policy_template_required` in the YAML file, and details about the required Group Policy templates can be found in each item's `resolution`.
These items are tagged with the label `CIS_group_policy_template_required` in the YAML file, and details about the required Group Policy templates can be found in each item's `resolution`.

View file

@ -1,6 +1,6 @@
# Windows 11 Enterprise benchmarks
Fleet's policies have been written against v2.0.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version.
Fleet's policies have been written against v3.0.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version.
For requirements and usage details, see the [CIS Benchmarks](https://fleetdm.com/docs/using-fleet/cis-benchmarks) documentation.
@ -12,4 +12,4 @@ For requirements and usage details, see the [CIS Benchmarks](https://fleetdm.com
### Checks that require a Group Policy template
Several items require Group Policy templates in place in order to audit them.
These items are tagged with the label `CIS_group_policy_template_required` in the YAML file, and details about the required Group Policy templates can be found in each item's `resolution`.
These items are tagged with the label `CIS_group_policy_template_required` in the YAML file, and details about the required Group Policy templates can be found in each item's `resolution`.

View file

@ -2256,12 +2256,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -3606,9 +3606,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"

View file

@ -3,7 +3,7 @@ package calendar
import (
"context"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/kit/log"
"github.com/go-kit/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/calendar/v3"

View file

@ -8,7 +8,7 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/kit/log/level"
"github.com/go-kit/log/level"
)
func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
@ -46,13 +46,18 @@ func (svc *Service) TriggerMigrateMDMDevice(ctx context.Context, host *fleet.Hos
return nil
}
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
return ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
var bre fleet.BadRequestError
switch {
case !ac.MDM.MacOSMigration.Enable:
bre.InternalErr = ctxerr.New(ctx, "macOS migration not enabled")
case ac.MDM.MacOSMigration.WebhookURL == "":
bre.InternalErr = ctxerr.New(ctx, "macOS migration webhook URL not configured")
case !host.IsEligibleForDEPMigration():
case !host.IsEligibleForDEPMigration(connected):
bre.InternalErr = ctxerr.New(ctx, "host not eligible for macOS migration")
}
if bre.InternalErr != nil {
@ -106,11 +111,16 @@ func (svc *Service) GetFleetDesktopSummary(ctx context.Context) (fleet.DesktopSu
}
if appCfg.MDM.EnabledAndConfigured && appCfg.MDM.MacOSMigration.Enable {
if host.NeedsDEPEnrollment() {
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
return sum, ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if host.NeedsDEPEnrollment(connected) {
sum.Notifications.RenewEnrollmentProfile = true
}
if host.IsEligibleForDEPMigration() {
if host.IsEligibleForDEPMigration(connected) {
sum.Notifications.NeedsMDMMigration = true
}
}

View file

@ -6,6 +6,16 @@ do
echo "$user"
if [ "$user" != "root" ]; then
echo "Unlocking password for $user"
passwd -u $user
STDERR=$(passwd -u "$user" 2>&1 >/dev/null)
if [ $? -eq 3 ]; then
# possibly due to the user not having a password
# use this convoluted case approach to avoid bashisms (POSIX portable)
case "$STDERR" in
*"unlocking the password would result in a passwordless account"* )
# unlock and delete password to set it back to empty
passwd -ud "$user"
;;
esac
fi
fi
done

View file

@ -38,44 +38,47 @@ func (svc *Service) OSVersion(ctx context.Context, osID uint, teamID *uint, incl
return svc.Service.OSVersion(ctx, osID, teamID, true)
}
func (svc *Service) LockHost(ctx context.Context, hostID uint) error {
func (svc *Service) LockHost(ctx context.Context, hostID uint) (unlockPIN string, err error) {
// First ensure the user has access to list hosts, then check the specific
// host once team_id is loaded.
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return err
return "", err
}
host, err := svc.ds.HostLite(ctx, hostID)
if err != nil {
return ctxerr.Wrap(ctx, err, "get host lite")
return "", ctxerr.Wrap(ctx, err, "get host lite")
}
// Authorize again with team loaded now that we have the host's team_id.
// Authorize as "execute mdm_command", which is the correct access
// requirement and is what happens for macOS platforms.
if err := svc.authz.Authorize(ctx, fleet.MDMCommandAuthz{TeamID: host.TeamID}, fleet.ActionWrite); err != nil {
return err
return "", err
}
// locking validations are based on the platform of the host
switch host.FleetPlatform() {
case "ios", "ipados":
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock iOS or iPadOS hosts. Use wipe instead."))
case "darwin":
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
return "", ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
}
// on macOS, the lock command requires the host to be MDM-enrolled in Fleet
hostMDM, err := svc.ds.GetHostMDM(ctx, host.ID)
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
if fleet.IsNotFound(err) {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock the host because it doesn't have MDM turned on."))
}
return ctxerr.Wrap(ctx, err, "get host MDM information")
return "", ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if !hostMDM.IsFleetEnrolled() {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock the host because it doesn't have MDM turned on."))
if !connected {
if fleet.IsNotFound(err) {
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock the host because it doesn't have MDM turned on."),
)
}
}
case "windows", "linux":
@ -84,27 +87,30 @@ func (svc *Service) LockHost(ctx context.Context, hostID uint) error {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.WindowsMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return ctxerr.Wrap(ctx, err, "check windows MDM enabled")
return "", ctxerr.Wrap(ctx, err, "check windows MDM enabled")
}
}
// on windows and linux, a script is used to lock the host so scripts must
// be enabled
appCfg, err := svc.ds.AppConfig(ctx)
if err != nil {
return ctxerr.Wrap(ctx, err, "get app config")
return "", ctxerr.Wrap(ctx, err, "get app config")
}
if appCfg.ServerSettings.ScriptsDisabled {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock host because running scripts is disabled in organization settings."))
return "", ctxerr.Wrap(
ctx,
fleet.NewInvalidArgumentError("host_id", "Can't lock host because running scripts is disabled in organization settings."),
)
}
hostOrbitInfo, err := svc.ds.GetHostOrbitInfo(ctx, host.ID)
switch {
case err != nil:
// If not found, then do nothing. We do not know if this host has scripts enabled or not
if !fleet.IsNotFound(err) {
return ctxerr.Wrap(ctx, err, "get host orbit info")
return "", ctxerr.Wrap(ctx, err, "get host orbit info")
}
case hostOrbitInfo.ScriptsEnabled != nil && !*hostOrbitInfo.ScriptsEnabled:
return ctxerr.Wrap(
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError(
"host_id", "Couldn't lock host. To lock, deploy the fleetd agent with --enable-scripts and refetch host vitals.",
),
@ -112,26 +118,37 @@ func (svc *Service) LockHost(ctx context.Context, hostID uint) error {
}
default:
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", fmt.Sprintf("Unsupported host platform: %s", host.Platform)))
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", fmt.Sprintf("Unsupported host platform: %s", host.Platform)))
}
// if there's a lock, unlock or wipe action pending, do not accept the lock
// request.
lockWipe, err := svc.ds.GetHostLockWipeStatus(ctx, host)
if err != nil {
return ctxerr.Wrap(ctx, err, "get host lock/wipe status")
return "", ctxerr.Wrap(ctx, err, "get host lock/wipe status")
}
switch {
case lockWipe.IsPendingLock():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending lock request. The host will lock when it comes online."))
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending lock request. The host will lock when it comes online."),
)
case lockWipe.IsPendingUnlock():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending unlock request. Host cannot be locked again until unlock is complete."))
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError(
"host_id", "Host has pending unlock request. Host cannot be locked again until unlock is complete.",
),
)
case lockWipe.IsPendingWipe():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. Cannot process lock requests once host is wiped."))
return "", ctxerr.Wrap(
ctx,
fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. Cannot process lock requests once host is wiped."),
)
case lockWipe.IsWiped():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is wiped. Cannot process lock requests once host is wiped."))
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError("host_id", "Host is wiped. Cannot process lock requests once host is wiped."),
)
case lockWipe.IsLocked():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already locked.").WithStatus(http.StatusConflict))
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already locked.").WithStatus(http.StatusConflict))
}
// all good, go ahead with queuing the lock request.
@ -158,7 +175,7 @@ func (svc *Service) UnlockHost(ctx context.Context, hostID uint) (string, error)
// locking validations are based on the platform of the host
switch host.FleetPlatform() {
case "darwin":
case "darwin", "ios", "ipados":
// all good, no need to check if MDM enrolled, will validate later that it
// is currently locked.
@ -249,7 +266,7 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error {
// uses scripts, not MDM.
var requireMDM bool
switch host.FleetPlatform() {
case "darwin":
case "darwin", "ios", "ipados":
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
@ -297,14 +314,11 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error {
if requireMDM {
// the wipe command requires the host to be MDM-enrolled in Fleet
hostMDM, err := svc.ds.GetHostMDM(ctx, host.ID)
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
if fleet.IsNotFound(err) {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't wipe the host because it doesn't have MDM turned on."))
}
return ctxerr.Wrap(ctx, err, "get host MDM information")
return ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if !hostMDM.IsFleetEnrolled() {
if !connected {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't wipe the host because it doesn't have MDM turned on."))
}
}
@ -331,19 +345,21 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error {
return svc.enqueueWipeHostRequest(ctx, host, lockWipe)
}
func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus) error {
func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus) (
unlockPIN string, err error,
) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return fleet.ErrNoContext
return "", fleet.ErrNoContext
}
if lockStatus.HostFleetPlatform == "darwin" {
lockCommandUUID := uuid.NewString()
if err := svc.mdmAppleCommander.DeviceLock(ctx, host, lockCommandUUID); err != nil {
return ctxerr.Wrap(ctx, err, "enqueuing lock request for darwin")
if unlockPIN, err = svc.mdmAppleCommander.DeviceLock(ctx, host, lockCommandUUID); err != nil {
return "", ctxerr.Wrap(ctx, err, "enqueuing lock request for darwin")
}
if err := svc.NewActivity(
if err = svc.NewActivity(
ctx,
vc.User,
fleet.ActivityTypeLockedHost{
@ -351,10 +367,10 @@ func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host
HostDisplayName: host.DisplayName(),
},
); err != nil {
return ctxerr.Wrap(ctx, err, "create activity for darwin lock host request")
return "", ctxerr.Wrap(ctx, err, "create activity for darwin lock host request")
}
return nil
return unlockPIN, nil
}
script := windowsLockScript
@ -374,7 +390,7 @@ func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host
UserID: &vc.User.ID,
SyncRequest: false,
}, host.FleetPlatform()); err != nil {
return err
return "", err
}
if err := svc.NewActivity(
@ -385,10 +401,10 @@ func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host
HostDisplayName: host.DisplayName(),
},
); err != nil {
return ctxerr.Wrap(ctx, err, "create activity for lock host request")
return "", ctxerr.Wrap(ctx, err, "create activity for lock host request")
}
return nil
return "", nil
}
func (svc *Service) enqueueUnlockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus) (string, error) {
@ -399,7 +415,9 @@ func (svc *Service) enqueueUnlockHostRequest(ctx context.Context, host *fleet.Ho
var unlockPIN string
if lockStatus.HostFleetPlatform == "darwin" {
// record the unlock request if it was not already recorded
// Record the unlock request time if it was not already recorded.
// It should be always recorded, since the UnlockRequestedAt time is created after the lock command is acknowledged.
// This code is left here to catch potential issues.
if lockStatus.UnlockRequestedAt.IsZero() {
if err := svc.ds.UnlockHostManually(ctx, host.ID, host.FleetPlatform(), time.Now().UTC()); err != nil {
return "", err
@ -449,7 +467,7 @@ func (svc *Service) enqueueWipeHostRequest(ctx context.Context, host *fleet.Host
}
switch wipeStatus.HostFleetPlatform {
case "darwin":
case "darwin", "ios", "ipados":
wipeCommandUUID := uuid.NewString()
if err := svc.mdmAppleCommander.EraseDevice(ctx, host, wipeCommandUUID); err != nil {
return ctxerr.Wrap(ctx, err, "enqueuing wipe request for darwin")

View file

@ -30,7 +30,7 @@ import (
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/storage"
"github.com/fleetdm/fleet/v4/server/sso"
"github.com/fleetdm/fleet/v4/server/worker"
kitlog "github.com/go-kit/kit/log"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/google/uuid"
)
@ -135,7 +135,7 @@ func (svc *Service) MDMAppleDeviceLock(ctx context.Context, hostID uint) error {
return err
}
err = svc.mdmAppleCommander.DeviceLock(ctx, host, uuid.New().String())
_, err = svc.mdmAppleCommander.DeviceLock(ctx, host, uuid.New().String())
if err != nil {
return err
}
@ -892,16 +892,16 @@ func (svc *Service) MDMAppleMatchPreassignment(ctx context.Context, externalHost
return err // will return a not found error if host does not exist
}
hostMDM, err := svc.ds.GetHostMDM(ctx, host.ID)
if err != nil || !hostMDM.IsFleetEnrolled() {
if err == nil || fleet.IsNotFound(err) {
err = errors.New("host is not enrolled in Fleet MDM")
return ctxerr.Wrap(ctx, &fleet.BadRequestError{
Message: err.Error(),
InternalErr: err,
})
}
return err
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
return ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if !connected {
err = errors.New("host is not enrolled in Fleet MDM")
return ctxerr.Wrap(ctx, &fleet.BadRequestError{
Message: err.Error(),
InternalErr: err,
})
}
// Collect the profiles' groups in case we need to create a new team,

View file

@ -25,7 +25,7 @@ import (
"github.com/fleetdm/fleet/v4/server/service"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/fleetdm/fleet/v4/server/worker"
kitlog "github.com/go-kit/kit/log"
kitlog "github.com/go-kit/log"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)

View file

@ -10,7 +10,7 @@ import (
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/storage"
"github.com/fleetdm/fleet/v4/server/sso"
kitlog "github.com/go-kit/kit/log"
kitlog "github.com/go-kit/log"
)
// Service wraps a free Service and implements additional premium functionality on top of it.

View file

@ -181,7 +181,7 @@ func (svc *Service) getSoftwareInstallerBinary(ctx context.Context, storageID st
return nil, ctxerr.Wrap(ctx, err, "checking if installer exists")
}
if !exists {
return nil, ctxerr.Wrap(ctx, err, "does not exist in software installer store")
return nil, ctxerr.Wrap(ctx, notFoundError{}, "does not exist in software installer store")
}
// get the installer from the store

View file

@ -19,7 +19,7 @@ import (
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/worker"
"github.com/go-kit/kit/log/level"
"github.com/go-kit/log/level"
)
func obfuscateSecrets(user *fleet.User, teams []*fleet.Team) error {
@ -537,10 +537,7 @@ func (svc *Service) DeleteTeam(ctx context.Context, teamID uint) error {
mdmHostSerials := make([]string, 0, len(hosts))
for _, host := range hosts {
hostIDs = append(hostIDs, host.ID)
// FIXME: These checks don't work here because host.MDMInfo is not being populated by
// ds.ListHosts call (it populates host.MDM instead). This may be happening in other
// places too.
if host.MDMInfo.IsPendingDEPFleetEnrollment() || host.MDMInfo.IsDEPFleetEnrolled() {
if host.IsDEPAssignedToFleet() {
mdmHostSerials = append(mdmHostSerials, host.HardwareSerial)
}
}
@ -773,10 +770,17 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
for _, spec := range specs {
var secrets []*fleet.EnrollSecret
for _, secret := range spec.Secrets {
secrets = append(secrets, &fleet.EnrollSecret{
Secret: secret.Secret,
})
// When secrets slice is empty, all secrets are removed.
// When secrets slice is nil, existing secrets are kept.
if spec.Secrets != nil {
secrets = make([]*fleet.EnrollSecret, 0, len(*spec.Secrets))
for _, secret := range *spec.Secrets {
secrets = append(
secrets, &fleet.EnrollSecret{
Secret: secret.Secret,
},
)
}
}
var create bool
@ -804,7 +808,7 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
}
}
}
if len(spec.Secrets) > fleet.MaxEnrollSecretsCount {
if len(secrets) > fleet.MaxEnrollSecretsCount {
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("secrets", "too many secrets"), "validate secrets")
}
if err := spec.MDM.MacOSUpdates.Validate(); err != nil {
@ -816,8 +820,9 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
if create {
// create a new team enroll secret if none is provided for a new team.
if len(secrets) == 0 {
// create a new team enroll secret if none is provided for a new team,
// unless the user explicitly passed in an empty array
if secrets == nil {
secret, err := server.GenerateRandomText(fleet.EnrollSecretDefaultLength)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "generate enroll secret string")
@ -1125,7 +1130,7 @@ func (svc *Service) editTeamFromSpec(
team.Config.Software = spec.Software
}
if len(secrets) > 0 {
if secrets != nil {
team.Secrets = secrets
}
@ -1179,8 +1184,8 @@ func (svc *Service) editTeamFromSpec(
return err
}
// only replace enroll secrets if at least one is provided (#6774)
if len(secrets) > 0 {
// If no secrets are provided and user did not explicitly specify an empty list, do not replace secrets. (#6774)
if secrets != nil {
if err := svc.ds.ApplyEnrollSecrets(ctx, ptr.Uint(team.ID), secrets); err != nil {
return err
}

View file

@ -12,6 +12,7 @@ services:
sails_datastores__default__adapter: sails-postgresql
sails_sockets__url: redis://redis:6379
sails_session__url: redis://redis:6379
sails_models__migrate: safe
sails_custom__fleetBaseUrl: '' #Add the base url of your Fleet instance: ex: https://fleet.example.com
sails_custom__fleetApiToken: '' # Add the API token of an API-only user [?] Here's how you get one: https://fleetdm.com/docs/using-fleet/fleetctl-cli#get-the-api-token-of-an-api-only-user
sails_custom__fleetApiOptionalCookie: '' # If your fleet instance requires optional cookies, use this to interact with the APIs

View file

@ -320,6 +320,7 @@ const DEFAULT_QUERY_REPORT_MOCK: IQueryReport = {
},
},
],
report_clipped: false,
};
const createMockQueryReport = (

View file

@ -360,7 +360,7 @@ const PlatformWrapper = ({
</InfoBanner>
</div>
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__installer-input ${baseClass}__chromeos-extension-id`}
name="Extension ID"
label={renderChromeOSLabel(
@ -370,7 +370,7 @@ const PlatformWrapper = ({
value={CHROME_OS_INFO.extensionId}
/>
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__installer-input ${baseClass}__chromeos-url`}
name="Installation URL"
label={renderChromeOSLabel(
@ -380,7 +380,7 @@ const PlatformWrapper = ({
value={CHROME_OS_INFO.installationUrl}
/>
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__installer-input ${baseClass}__chromeos-policy-for-extension`}
name="Policy for extension"
label={renderChromeOSLabel(
@ -399,7 +399,7 @@ const PlatformWrapper = ({
{renderFleetCertificateBlock("tooltip")}
<div className={`${baseClass}__advanced--installer`}>
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__installer-input ${baseClass}__installer-input-${packageType}`}
name="installer"
label={renderLabel(
@ -495,7 +495,7 @@ const PlatformWrapper = ({
require sudo or Run as Administrator privileges):
</p>
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__run-osquery-input`}
name="run-osquery"
label={renderLabel(
@ -533,7 +533,7 @@ const PlatformWrapper = ({
</Checkbox>
)}
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__installer-input ${baseClass}__installer-input-${packageType}`}
name="installer"
label={renderLabel(packageType, renderInstallerString(packageType))}

View file

@ -26,8 +26,7 @@
min-height: 88px;
}
&__textarea,
&--disabled {
&__textarea {
font-family: "SourceCodePro", $monospace;
font-weight: $bold;
font-size: 13px;

View file

@ -119,7 +119,7 @@ const EnrollSecretRow = ({
>
{/* TODO: replace with InputFieldHiddenContent component */}
<InputField
disabled
readOnly
inputWrapperClass={`${baseClass}__secret-input`}
name={`osqueryd-secret-${uniqueId()}`}
type={showSecret ? "text" : "password"}

View file

@ -76,7 +76,6 @@ const TargetsInput = ({
columnConfigs={searchResultsTableConfig}
data={dropdownHosts}
isLoading={isTargetsLoading}
resultsTitle=""
emptyComponent={() => (
<div className="empty-search">
<div className="empty-search__inner">
@ -107,7 +106,6 @@ const TargetsInput = ({
columnConfigs={selectedHostsTableConifg}
data={targetedHosts}
isLoading={false}
resultsTitle=""
showMarkAllPages={false}
isAllPagesSelected={false}
disableCount

View file

@ -43,7 +43,7 @@ interface IDataTableProps {
showMarkAllPages: boolean;
isAllPagesSelected: boolean; // TODO: make dependent on showMarkAllPages
toggleAllPagesSelected?: any; // TODO: an event type and make it dependent on showMarkAllPages
resultsTitle: string;
resultsTitle?: string;
defaultPageSize: number;
defaultPageIndex?: number;
primarySelectAction?: IActionButtonProps;
@ -85,7 +85,7 @@ const DataTable = ({
showMarkAllPages,
isAllPagesSelected,
toggleAllPagesSelected,
resultsTitle,
resultsTitle = "results",
defaultPageSize,
defaultPageIndex,
primarySelectAction,

View file

@ -12,7 +12,6 @@ import Icon from "components/Icon/Icon";
import { COLORS } from "styles/var/colors";
import DataTable from "./DataTable/DataTable";
import TableContainerUtils from "./utilities/TableContainerUtils";
import { IActionButtonProps } from "./DataTable/ActionButton/ActionButton";
export interface ITableQueryData {
@ -44,7 +43,8 @@ interface ITableContainerProps<T = any> {
inputPlaceHolder?: string;
disableActionButton?: boolean;
disableMultiRowSelect?: boolean;
resultsTitle: string;
/** resultsTitle used in DataTable for matching results text */
resultsTitle?: string;
resultsHtml?: JSX.Element;
additionalQueries?: string;
emptyComponent: React.ElementType;
@ -64,10 +64,6 @@ interface ITableContainerProps<T = any> {
primarySelectAction?: IActionButtonProps;
/** Secondary button/s after selecting a row */
secondarySelectActions?: IActionButtonProps[]; // TODO: Combine with primarySelectAction as these are all rendered in the same spot
/**
* @deprecated please use renderCount instead
* */
filteredCount?: number;
searchToolTipText?: string;
// TODO - consolidate this functionality within `filters`
searchQueryColumn?: string;
@ -103,7 +99,6 @@ interface ITableContainerProps<T = any> {
* bar and API call so TableContainer will reset its page state to 0 */
resetPageIndex?: boolean;
disableTableHeader?: boolean;
show0Count?: boolean;
}
const baseClass = "table-container";
@ -140,7 +135,6 @@ const TableContainer = <T,>({
disableCount,
primarySelectAction,
secondarySelectActions,
filteredCount,
searchToolTipText,
isClientSidePagination,
onClientSidePaginationChange,
@ -160,7 +154,6 @@ const TableContainer = <T,>({
setExportRows,
resetPageIndex,
disableTableHeader,
show0Count,
}: ITableContainerProps<T>) => {
const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
const [sortHeader, setSortHeader] = useState(defaultSortHeader || "");
@ -252,16 +245,6 @@ const TableContainer = <T,>({
additionalQueries,
]);
// TODO: refactor existing components relying on displayCount to use renderCount pattern
const displayCount = useCallback((): any => {
if (typeof filteredCount === "number") {
return filteredCount;
} else if (typeof clientFilterCount === "number") {
return clientFilterCount;
}
return data?.length || 0;
}, [filteredCount, clientFilterCount, data]);
const renderPagination = useCallback(() => {
if (disablePagination || isClientSidePagination) {
return null;
@ -309,37 +292,16 @@ const TableContainer = <T,>({
stackControls ? "stack-table-controls" : ""
}`}
>
<span className="results-count">
{renderCount && (
<div
className={`${baseClass}__results-count ${
stackControls ? "stack-table-controls" : ""
}`}
style={opacity}
>
{renderCount()}
</div>
)}
{!renderCount &&
!disableCount &&
(isMultiColumnFilter || displayCount() || show0Count) ? (
<div
className={`${baseClass}__results-count ${
stackControls ? "stack-table-controls" : ""
}`}
style={opacity}
>
{TableContainerUtils.generateResultsCountText(
resultsTitle,
displayCount(),
show0Count
)}
{resultsHtml}
</div>
) : (
<div />
)}
</span>
{renderCount && !disableCount && (
<div
className={`${baseClass}__results-count ${
stackControls ? "stack-table-controls" : ""
}`}
style={opacity}
>
{renderCount()}
</div>
)}
<span className="controls">
{actionButton && !actionButton.hideButton && (
<Button

View file

@ -0,0 +1,14 @@
import React from "react";
import { generateResultsCountText } from "../utilities/TableContainerUtils";
interface ITableCountProps {
name: string;
count?: number;
}
const TableCount = ({ name, count }: ITableCountProps): JSX.Element => {
return <span>{generateResultsCountText(name, count)}</span>;
};
export default TableCount;

View file

@ -0,0 +1 @@
export { default } from "./TableCount";

View file

@ -54,9 +54,6 @@
height: 38px; // Fixes overlap with .Select outline
}
}
.results-count {
height: 40px; // Match height of search/filters
}
&__results-count {
display: flex;
@ -66,6 +63,7 @@
color: $core-fleet-black;
margin: 0;
height: 40px;
gap: 12px;
.count-error {
color: $ui-error;

View file

@ -1,11 +1,10 @@
const DEFAULT_RESULTS_NAME = "results";
const generateResultsCountText = (
export const generateResultsCountText = (
name: string = DEFAULT_RESULTS_NAME,
resultsCount: number,
show0Count = false
resultsCount?: number
): string => {
if (resultsCount === 0 && !show0Count) return `No ${name}`;
if (!resultsCount || resultsCount === 0) return `0 ${name}`;
// If there is 1 result and the last 3 letters in the result
// name are "ies," we remove the "ies" and add "y"
// to make the name singular

View file

@ -55,7 +55,7 @@ class UserSettingsForm extends Component {
autofocus
label="Email (required)"
helpText={renderEmailHelpText()}
disabled={!smtpConfigured}
readOnly={!smtpConfigured}
tooltip={
<>
Editing your email address requires that SMTP or SES is

Some files were not shown because too many files have changed in this diff Show more