Merge branch 'main' into feat-labels-scoped-software

This commit is contained in:
Gabriel Hernandez 2024-12-17 10:58:04 -06:00
commit eb41e7ce8e
26 changed files with 708 additions and 238 deletions

View file

@ -9,8 +9,6 @@ We've been using Fleet for a few years and we couldn't be happier. The fact that
## Challenge
Scaling organizations face a common challenge: managing every device efficiently across varying teams and locations. Here, we take a deeper look at the impact that Fleet has made at a leading financial technology company.
The leading financial company looked to simplify how they manage devices and reduce tool overlap without sacrificing control over their infrastructure. The use of multiple proprietary device management tools was creating operational silos, and it required specialized expertise for different legacy systems, leading to inefficiencies.
## Solution

View file

@ -2,7 +2,7 @@
<div purpose="attribution-quote">
After several fast-paced weeks of planning, testing, and collaboration, our corporate engineering team at Foursquare has officially completed the migration to Fleet Device Management as our new device management platform. This move represents a big step forward for us.
After several fast-paced weeks of planning, testing, and collaboration, our corporate engineering team at Foursquare has officially completed the migration to Fleet Device Management as our new device management platform. This move represents a big step forward for us.
**— Mike Meyer, Manager, Corporate Engineering at Foursquare**
</div>

View file

@ -0,0 +1,79 @@
# Global social media platform switches to Fleet for workstation telemetry
<div purpose="attribution-quote">
Context is king for device data, and Fleet provides a way to surface that information to our other teams and partners.
**- Systems and Infrastructure Manager**
</div>
## Challenge
One of the largest social media platforms sought to enhance its telemetry capabilities to maintain strict compliance and security without compromising data accessibility and operational efficiency. Managing thousands of devices across multiple platforms had led to fragmented visibility and reliance on manual data handling processes, which were time-consuming and error-prone. Additionally, the existing solution failed to provide actionable insights without significant customization, hindering proactive operations and complicating compliance with ongoing [ISO27002](https://www.iso.org/standard/75652.html) and [SOC audit](https://en.wikipedia.org/wiki/System_and_Organization_Controls) requirements.
## Solution
The social media platform transitioned to Fleet, consolidating under a single, [multi-platform](https://fleetdm.com/orchestration) system that supports macOS, Linux, and Windows. Fleet's compliance features made it easier to meet regulatory standards and protect sensitive data. Leveraging Fleet's real-time reporting and flexible data management capabilities eliminated the need for manual workflows and extensive customizations. Additionally, deploying [osquery](https://osquery.io/) independently from their existing EDR enhanced data accuracy and reliability, providing more accurate measurement against other benchmarks like [CIS](https://www.cisecurity.org/cis-benchmarks), and standardized osquery operations across their entire fleet.
## Results
<div purpose="checklist">
Verifiable compliance
Cross-team data accessibility
Real-time insights
Standardized processes
</div>
By switching to Fleet, they were able to institute more stringent compliance policies, verify security posture, gather real-time insight into ongoing operations, and standardize these processes across their growing fleet of diverse devices and teams.
## Their story
This social media platform is one of the largest globally, connecting thousands of communities and millions of users. With a vast user base and a significant global presence, effective visibility is essential to maintaining their required performance, security, and compliance standards.
The decision to switch to Fleet was driven by a few key factors. Strict adherence to compliance standards, balancing proactive and reactive security measures to protect sensitive data, and streamlining device data accessibility. By making data easily accessible and parsable, Fleet eliminated the inefficiencies of manual workflows and addressed communication gaps across teams, enabling better, faster decision-making.
With Fleet, they achieved this through:
- Eliminating tool overlap
- Definitive data for compliance and real-time reporting
- Robust API and webhook support
- A Unified reporting language
### Eliminate tool overlap
Fleets centralized platform enabled the combination of device operations across macOS, Windows, and Linux with a [unified reporting language](https://fleetdm.com/docs/deploy/reference-architectures#mysql) that provides flexibility and contextualized data. By adhering to standard data shapes and formats, Fleet makes sure that data is easily interpretable and usable across various teams and applications while serving as the central hub for security data.
### Definitive data for compliance
Fleets live query engine streams easily accessible and parsable [data](https://fleetdm.com/tables/account_policy_data), eliminating the need for manual exports and data consolidation from multiple tools. With accurate visibility and access across teams, remediations based on information directly from each device lead to fewer infrastructure failures and auditing errors.
### Robust API and webhook support
Fleets API facilitates real-time [compliance](https://fleetdm.com/queries) auditing and reporting, allowing the team to respond promptly to potential issues by combining data from different tools. The [API](https://fleetdm.com/docs/rest-api/rest-api) and webhook features enable automation and integration with existing systems, eliminating the need for extra middleware and reducing reliance on manual configurations.
### Unified reporting language
Fleet's straightforward deployment of the osquery agent across their devices as an independent element ensured data accuracy and reliability while standardizing its operations across macOS, Windows, and Linux. This allowed the social media platform to inspect, collect, fix, install, patch, and program just about anything, every minute of the day, on any computer in their infrastructure, with an unnoticeable performance impact
## Conclusion
Transitioning to Fleet provided the platform with a strategic solution that addressed its critical needs for compliance, security, data accessibility, and operational efficiency. Fleet's cross-platform support and open-source transparency set it apart from competitors, providing a single source of truth for all devices.
<call-to-action></call-to-action>
<meta name="category" value="announcements">
<meta name="authorGitHubUsername" value="Drew-P-drawers">
<meta name="authorFullName" value="Andrew Baker">
<meta name="publishedOn" value="2024-12-16">
<meta name="articleTitle" value="Global social media platform migrates to Fleet">
<meta name="description" value="Global social media platform migrates to Fleet">

View file

@ -1,5 +1,7 @@
# Seamless macOS MDM migration
> NOTE: Please contact Fleet here https://fleetdm.com/contact or reach out to the Fleet Customer Success team if you are a current Fleet customer for consultation when considering this migration path. We'd love to help!
![Seamless macOS MDM migrations to Fleet](../website/assets/images/articles/seamless-mdm-migration-1600x900@2x.png)
Migrating macOS devices between Mobile Device Management (MDM) solutions is often fraught with challenges, including potential gaps in device management, user disruption, and compliance issues. Traditional MDM migrations typically require end-user interaction and leave devices unmanaged for a period, leading to problems like Wi-Fi disconnections due to certificate profile removal and incomplete migrations. These challenges can force organizations to stay with outdated MDM solutions that no longer meet their needs. But theres a better way.

View file

@ -0,0 +1 @@
* Aliased EAP versions of JetBrains IDEs to "last release version plus all fixes" (e.g. 2024.3 EAP -> 2024.2.99) to avoid vulnerability false positives

View file

@ -0,0 +1 @@
* Added Mastodon icon and URL to server email templates.

View file

@ -0,0 +1 @@
- add helpful tooltip for the install software setup experience page

View file

@ -309,7 +309,7 @@ func checkWinVulnerabilities(
"found new", len(r))
results = append(results, r...)
if err != nil {
errHandler(ctx, logger, "analyzing hosts for Windows vulnerabilities", err)
errHandler(ctx, kitlog.With(logger, "os name", o.Name, "display version", o.DisplayVersion), "analyzing hosts for Windows vulnerabilities", err)
}
}
}

View file

@ -94,7 +94,7 @@ If a table is not available for your host, Fleet will generally handle things be
Fleet Desktop is supported on Ubuntu and Fedora.
Fedora requires a [gnome extension](https://extensions.gnome.org/extension/615/appindicator-support/) and Google Chrome for Fleet Desktop.
Fedora and some flavors of Ubuntu (e.g. Kubuntu) require a [gnome extension](https://extensions.gnome.org/extension/615/appindicator-support/) and Google Chrome for Fleet Desktop.
Fleet's default (un)install scripts use `apt-get` for Debian-based distributions, and `dnf` for Red Hat-based distributions. To install packages on CentOS versions prior to 8, either add `dnf` or edit install and uninstall scripts to use the `yum` or `rpm` command.

View file

@ -60,7 +60,10 @@ describe("AddInstallSoftware", () => {
);
expect(
screen.getByText(/2 software will be installed during setup/)
screen.getByText(
(_, element) =>
element?.textContent === "2 software will be installed during setup."
)
).toBeVisible();
expect(
screen.getByRole("button", { name: "Show selected software" })

View file

@ -6,6 +6,7 @@ import Button from "components/buttons/Button";
import CustomLink from "components/CustomLink";
import { ISoftwareTitle } from "interfaces/software";
import LinkWithContext from "components/LinkWithContext";
import TooltipWrapper from "components/TooltipWrapper";
const baseClass = "add-install-software";
@ -45,9 +46,17 @@ const AddInstallSoftware = ({
software.app_store_app?.install_during_setup
).length;
return installDuringSetupCount === 0
? "No software added."
: `${installDuringSetupCount} software will be installed during setup.`;
return installDuringSetupCount === 0 ? (
"No software added."
) : (
<>
{installDuringSetupCount} software will be{" "}
<TooltipWrapper position="top" tipContent="Software order will vary.">
installed during setup
</TooltipWrapper>
.
</>
);
};
const getButtonText = () => {

View file

@ -132,6 +132,10 @@ resource "aws_ecs_task_definition" "backend" {
}
]
environment = concat([
{
name = "FLEET_LOGGING_JSON"
value = "true"
},
{
name = "FLEET_MYSQL_USERNAME"
value = module.aurora_mysql.cluster_master_username

View file

@ -143,6 +143,13 @@
src="{{.AssetURL}}/fleet-mark-color-40x40@2x.png"
/>
</a>
<a href="https://discuss.systems/@Fleet" target="_blank">
<img
alt="Mastodon logo"
style="height: 20px; width: 20px; padding-right: 20px"
src="{{.AssetURL}}/mastodon-logo-50x40@2x.png"
/>
</a>
<a href="https://twitter.com/fleetctl" target="_blank">
<img
alt="X logo"

View file

@ -176,6 +176,13 @@
src="{{.AssetURL}}/fleet-mark-color-40x40@2x.png"
/>
</a>
<a href="https://discuss.systems/@Fleet" target="_blank">
<img
alt="Mastodon logo"
style="height: 20px; width: 20px; padding-right: 20px"
src="{{.AssetURL}}/mastodon-logo-50x40@2x.png"
/>
</a>
<a href="https://twitter.com/fleetctl" target="_blank">
<img
alt="X logo"

View file

@ -148,6 +148,13 @@
src="{{.AssetURL}}/fleet-mark-color-40x40@2x.png"
/>
</a>
<a href="https://discuss.systems/@Fleet" target="_blank">
<img
alt="Mastodon logo"
style="height: 20px; width: 20px; padding-right: 20px"
src="{{.AssetURL}}/mastodon-logo-50x40@2x.png"
/>
</a>
<a href="https://twitter.com/fleetctl" target="_blank">
<img
alt="X logo"

View file

@ -148,6 +148,13 @@
src="{{.AssetURL}}/fleet-mark-color-40x40@2x.png"
/>
</a>
<a href="https://discuss.systems/@Fleet" target="_blank">
<img
alt="Mastodon logo"
style="height: 20px; width: 20px; padding-right: 20px"
src="{{.AssetURL}}/mastodon-logo-50x40@2x.png"
/>
</a>
<a href="https://twitter.com/fleetctl" target="_blank">
<img
alt="X logo"

View file

@ -122,6 +122,13 @@
src="{{.AssetURL}}/fleet-mark-color-40x40@2x.png"
/>
</a>
<a href="https://discuss.systems/@Fleet" target="_blank">
<img
alt="Mastodon logo"
style="height: 20px; width: 20px; padding-right: 20px"
src="{{.AssetURL}}/mastodon-logo-50x40@2x.png"
/>
</a>
<a href="https://twitter.com/fleetctl" target="_blank">
<img
alt="X logo"

View file

@ -1599,15 +1599,9 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho
var (
macOSMSTeamsVersion = regexp.MustCompile(`(\d).00.(\d)(\d+)`)
citrixName = regexp.MustCompile(`Citrix Workspace [0-9]+`)
)
// sanitizeSoftware performs any sanitization required to the ingested software fields.
//
// Some fields are reported with known incorrect values and we need to fix them before using them.
func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
softwareSanitizers := []struct {
softwareSanitizers = []struct {
checkSoftware func(*fleet.Host, *fleet.Software) bool
mutateSoftware func(*fleet.Software)
mutateSoftware func(*fleet.Software, log.Logger)
}{
// "Microsoft Teams" on macOS defines the `bundle_short_version` (CFBundleShortVersionString) in a different
// unexpected version format. Thus here we transform the version string to the expected format
@ -1623,7 +1617,7 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
checkSoftware: func(h *fleet.Host, s *fleet.Software) bool {
return h.Platform == "darwin" && (s.Name == "Microsoft Teams.app" || s.Name == "Microsoft Teams classic.app")
},
mutateSoftware: func(s *fleet.Software) {
mutateSoftware: func(s *fleet.Software, logger log.Logger) {
if matches := macOSMSTeamsVersion.FindStringSubmatch(s.Version); len(matches) > 0 {
s.Version = fmt.Sprintf("%s.%s.00.%s", matches[1], matches[2], matches[3])
}
@ -1635,7 +1629,7 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
checkSoftware: func(h *fleet.Host, s *fleet.Software) bool {
return h.Platform == "windows" && s.Name == "Cloudflare WARP" && s.Source == "programs"
},
mutateSoftware: func(s *fleet.Software) {
mutateSoftware: func(s *fleet.Software, logger log.Logger) {
// Perform some sanity check on the version before mutating it.
parts := strings.Split(s.Version, ".")
if len(parts) <= 1 {
@ -1658,7 +1652,7 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
checkSoftware: func(h *fleet.Host, s *fleet.Software) bool {
return citrixName.Match([]byte(s.Name)) || s.Name == "Citrix Workspace.app"
},
mutateSoftware: func(s *fleet.Software) {
mutateSoftware: func(s *fleet.Software, logger log.Logger) {
parts := strings.Split(s.Version, ".")
if len(parts) <= 1 {
level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version)
@ -1694,7 +1688,7 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
checkSoftware: func(h *fleet.Host, s *fleet.Software) bool {
return s.Name == "minio" && strings.Contains(s.Version, "RELEASE.")
},
mutateSoftware: func(s *fleet.Software) {
mutateSoftware: func(s *fleet.Software, logger log.Logger) {
s.Version = strings.TrimPrefix(s.Version, "RELEASE.")
},
},
@ -1705,7 +1699,7 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
return s.Name == "minio" && regex.MatchString(s.Version)
},
mutateSoftware: func(s *fleet.Software) {
mutateSoftware: func(s *fleet.Software, logger log.Logger) {
timestamp, err := time.Parse("20060102150405", s.Version)
if err != nil {
level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version, "err", err)
@ -1714,11 +1708,54 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
s.Version = timestamp.Format("2006-01-02T15-04-05Z")
},
},
}
{
// JetBrains EAP version numbers aren't what are used in CPEs; this handles the translation for Mac versions.
// See #22723 for background. Bundle identifier for EAPs also ends with "-EAP" but checking version makes it
// a bit easier to add other platforms later. EAP version numbers are e.g. EAP GO-243.21565.42, and checking
// here for the dash ensures that string splitting in the mutator always works without a bounds check.
checkSoftware: func(h *fleet.Host, s *fleet.Software) bool {
return s.BundleIdentifier != "" && strings.HasPrefix(s.BundleIdentifier, "com.jetbrains.") &&
strings.HasPrefix(s.Version, "EAP ") && strings.Contains(s.Version, "-")
},
mutateSoftware: func(s *fleet.Software, logger log.Logger) {
// 243 -> 2024.3
eapMajorVersion := strings.Split(strings.Split(s.Version, "-")[1], ".")[0]
yearBasedMajorVersion, err := strconv.Atoi("20" + eapMajorVersion[:2])
if err != nil {
level.Debug(logger).Log("msg", "failed to parse JetBrains EAP major version", "version", s.Version, "err", err)
return
}
yearBasedMinorVersion, err := strconv.Atoi(eapMajorVersion[2:])
if err != nil {
level.Debug(logger).Log("msg", "failed to parse JetBrains EAP minor version", "version", s.Version, "err", err)
return
}
// EAPs are treated as having all fixes from the previous year-based release, but no fixes from the
// year-based release they're an EAP of. The exception to this would be CVE-2024-37051, which was fixed
// in a second/third EAP depending on product, but at this point all vulnerable EAPs force exit on
// startup due to being expired, so that CVE can't be exploited.
yearBasedMinorVersion -= 1
if yearBasedMinorVersion <= 0 { // wrap e.g. 2024.1 to 2023.4 (not a real version, but has all 2023.3 fixes)
yearBasedMajorVersion -= 1
yearBasedMinorVersion = 4
}
// pass through minor and patch version for EAP to tell different EAP builds apart
eapMinorAndPatchVersion := strings.Join(strings.Split(strings.Split(s.Version, "-")[1], ".")[1:], ".")
s.Version = fmt.Sprintf("%d.%d.%s.%s", yearBasedMajorVersion, yearBasedMinorVersion, "99", eapMinorAndPatchVersion)
},
},
}
)
// sanitizeSoftware performs any sanitization required to the ingested software fields.
//
// Some fields are reported with known incorrect values and we need to fix them before using them.
func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) {
for _, softwareSanitizer := range softwareSanitizers {
if softwareSanitizer.checkSoftware(h, s) {
softwareSanitizer.mutateSoftware(s)
softwareSanitizer.mutateSoftware(s, logger)
return
}
}

View file

@ -1920,6 +1920,48 @@ func TestSanitizeSoftware(t *testing.T) {
Version: "2020-03-10T00-00-00Z",
},
},
{
name: "JetBrains non-EAP",
h: &fleet.Host{},
s: &fleet.Software{
Name: "GoLand.app",
Version: "2024.3.1",
BundleIdentifier: "com.jetbrains.goland",
},
sanitized: &fleet.Software{
Name: "GoLand.app",
Version: "2024.3.1",
BundleIdentifier: "com.jetbrains.goland",
},
},
{
name: "JetBrains EAP",
h: &fleet.Host{},
s: &fleet.Software{
Name: "GoLand.app",
Version: "EAP GO-243.21565.42",
BundleIdentifier: "com.jetbrains.goland-EAP",
},
sanitized: &fleet.Software{
Name: "GoLand.app",
Version: "2024.2.99.21565.42",
BundleIdentifier: "com.jetbrains.goland-EAP",
},
},
{
name: "JetBrains year-wrapped EAP",
h: &fleet.Host{},
s: &fleet.Software{
Name: "IntelliJ IDEA CE",
Version: "EAP IC-241.12345.67",
BundleIdentifier: "com.jetbrains.intellij-EAP",
},
sanitized: &fleet.Software{
Name: "IntelliJ IDEA CE",
Version: "2023.4.99.12345.67",
BundleIdentifier: "com.jetbrains.intellij-EAP",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
sanitizeSoftware(tc.h, tc.s, log.NewNopLogger())

View file

@ -66,7 +66,53 @@ func (p Products) GetMatchForOS(ctx context.Context, os fleet.OperatingSystem) (
}
func NewProductFromFullName(fullName string) Product {
return Product(fullName)
// If the full name includes a version, return it as-is.
p := Product(fullName)
if p.HasDisplayVersion() {
return p
}
// Several Windows products listed in MSRC bulletins don't include the OS version number.
// We need this to match the product with a host's OS, so we'll add them here.
versionString := ""
switch {
case strings.Contains(fullName, "Windows Server 2022"):
versionString = "21H2"
case strings.Contains(fullName, "Windows Server 2016"):
versionString = "1607"
case strings.Contains(fullName, "Windows Server 2019"):
versionString = "1809"
case strings.Contains(fullName, "Windows 8.1"):
versionString = "6.3 / NT 6.3"
case strings.Contains(fullName, "Windows RT 8.1"):
versionString = "6.3 / NT 6.3"
case strings.Contains(fullName, "Windows Server 2012 R2"):
versionString = "6.3 / NT 6.3"
case strings.Contains(fullName, "Windows Server 2012"):
versionString = "6.2 / NT 6.2"
case strings.Contains(fullName, "Windows Server 2008 R2"):
versionString = "6.1 / NT 6.1"
case strings.Contains(fullName, "Windows 7"):
versionString = "6.1 / NT 6.1"
case strings.Contains(fullName, "Windows Server 2008"):
versionString = "6.0 / NT 6.0"
}
finalName := fullName
if versionString != "" {
finalName += (" Version " + versionString)
}
return Product(finalName)
}
func NewProductFromOS(os fleet.OperatingSystem) Product {

View file

@ -67,354 +67,424 @@ func TestMatches(t *testing.T) {
func TestFullProductName(t *testing.T) {
testCases := []struct {
fullName string
arch string
prodName string
fullName string
arch string
prodName string
finalName string
}{
{
fullName: "Windows 10 Version 1809 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1809 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1809 for 32-bit Systems",
},
{
fullName: "Windows 10 Version 1809 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1809 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1809 for x64-based Systems",
},
{
fullName: "Windows 10 Version 1809 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
fullName: "Windows 10 Version 1809 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
finalName: "Windows 10 Version 1809 for ARM64-based Systems",
},
{
fullName: "Windows Server 2019",
arch: "all",
prodName: "Windows Server 2019",
fullName: "Windows Server 2019",
arch: "all",
prodName: "Windows Server 2019",
finalName: "Windows Server 2019 Version 1809",
},
{
fullName: "Windows Server 2019 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2019",
fullName: "Windows Server 2019 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2019",
finalName: "Windows Server 2019 (Server Core installation) Version 1809",
},
{
fullName: "Windows 10 Version 1909 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1909 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1909 for 32-bit Systems",
},
{
fullName: "Windows 10 Version 1909 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1909 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1909 for x64-based Systems",
},
{
fullName: "Windows 10 Version 1909 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
fullName: "Windows 10 Version 1909 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
finalName: "Windows 10 Version 1909 for ARM64-based Systems",
},
{
fullName: "Windows 10 Version 21H1 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 21H1 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 21H1 for x64-based Systems",
},
{
fullName: "Windows 10 Version 21H1 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
fullName: "Windows 10 Version 21H1 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
finalName: "Windows 10 Version 21H1 for ARM64-based Systems",
},
{
fullName: "Windows 10 Version 21H1 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 21H1 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 21H1 for 32-bit Systems",
},
{
fullName: "Windows Server 2022",
arch: "all",
prodName: "Windows Server 2022",
fullName: "Windows Server 2022",
arch: "all",
prodName: "Windows Server 2022",
finalName: "Windows Server 2022 Version 21H2",
},
{
fullName: "Windows Server 2022 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2022",
fullName: "Windows Server 2022 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2022",
finalName: "Windows Server 2022 (Server Core installation) Version 21H2",
},
{
fullName: "Windows 10 Version 20H2 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 20H2 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 20H2 for x64-based Systems",
},
{
fullName: "Windows 10 Version 20H2 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 20H2 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 20H2 for 32-bit Systems",
},
{
fullName: "Windows 10 Version 20H2 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
fullName: "Windows 10 Version 20H2 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
finalName: "Windows 10 Version 20H2 for ARM64-based Systems",
},
{
fullName: "Windows Server, version 20H2 (Server Core Installation)",
arch: "all",
prodName: "Windows Server",
fullName: "Windows Server, version 20H2 (Server Core Installation)",
arch: "all",
prodName: "Windows Server",
finalName: "Windows Server, version 20H2 (Server Core Installation)",
},
{
fullName: "Windows 11 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 11",
fullName: "Windows 11 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 11",
finalName: "Windows 11 for x64-based Systems",
},
{
fullName: "Windows 11 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 11",
fullName: "Windows 11 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 11",
finalName: "Windows 11 for ARM64-based Systems",
},
{
fullName: "Windows 10 Version 21H2 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 21H2 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 21H2 for 32-bit Systems",
},
{
fullName: "Windows 10 Version 21H2 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
fullName: "Windows 10 Version 21H2 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
finalName: "Windows 10 Version 21H2 for ARM64-based Systems",
},
{
fullName: "Windows 10 Version 21H2 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 21H2 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 21H2 for x64-based Systems",
},
{
fullName: "Windows 10 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 for 32-bit Systems",
},
{
fullName: "Windows 10 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 for x64-based Systems",
},
{
fullName: "Windows 10 Version 1607 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1607 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1607 for 32-bit Systems",
},
{
fullName: "Windows 10 Version 1607 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1607 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1607 for x64-based Systems",
},
{
fullName: "Windows Server 2016",
arch: "all",
prodName: "Windows Server 2016",
fullName: "Windows Server 2016",
arch: "all",
prodName: "Windows Server 2016",
finalName: "Windows Server 2016 Version 1607",
},
{
fullName: "Windows Server 2016 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2016",
fullName: "Windows Server 2016 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2016",
finalName: "Windows Server 2016 (Server Core installation) Version 1607",
},
{
fullName: "Windows 8.1 for 32-bit systems",
arch: "32-bit",
prodName: "Windows 8.1",
fullName: "Windows 8.1 for 32-bit systems",
arch: "32-bit",
prodName: "Windows 8.1",
finalName: "Windows 8.1 for 32-bit systems Version 6.3 / NT 6.3",
},
{
fullName: "Windows 8.1 for x64-based systems",
arch: "64-bit",
prodName: "Windows 8.1",
fullName: "Windows 8.1 for x64-based systems",
arch: "64-bit",
prodName: "Windows 8.1",
finalName: "Windows 8.1 for x64-based systems Version 6.3 / NT 6.3",
},
{
fullName: "Windows RT 8.1",
arch: "all",
prodName: "Windows RT 8.1",
fullName: "Windows RT 8.1",
arch: "all",
prodName: "Windows RT 8.1",
finalName: "Windows RT 8.1 Version 6.3 / NT 6.3",
},
{
fullName: "Windows Server 2012",
arch: "all",
prodName: "Windows Server 2012",
fullName: "Windows Server 2012",
arch: "all",
prodName: "Windows Server 2012",
finalName: "Windows Server 2012 Version 6.2 / NT 6.2",
},
{
fullName: "Windows Server 2012 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012",
fullName: "Windows Server 2012 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012",
finalName: "Windows Server 2012 (Server Core installation) Version 6.2 / NT 6.2",
},
{
fullName: "Windows Server 2012 R2",
arch: "all",
prodName: "Windows Server 2012 R2",
fullName: "Windows Server 2012 R2",
arch: "all",
prodName: "Windows Server 2012 R2",
finalName: "Windows Server 2012 R2 Version 6.3 / NT 6.3",
},
{
fullName: "Windows Server 2012 R2 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012 R2",
fullName: "Windows Server 2012 R2 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012 R2",
finalName: "Windows Server 2012 R2 (Server Core installation) Version 6.3 / NT 6.3",
},
{
fullName: "Windows 7 for 32-bit Systems Service Pack 1",
arch: "32-bit",
prodName: "Windows 7",
fullName: "Windows 7 for 32-bit Systems Service Pack 1",
arch: "32-bit",
prodName: "Windows 7",
finalName: "Windows 7 for 32-bit Systems Service Pack 1 Version 6.1 / NT 6.1",
},
{
fullName: "Windows 7 for x64-based Systems Service Pack 1",
arch: "64-bit",
prodName: "Windows 7",
fullName: "Windows 7 for x64-based Systems Service Pack 1",
arch: "64-bit",
prodName: "Windows 7",
finalName: "Windows 7 for x64-based Systems Service Pack 1 Version 6.1 / NT 6.1",
},
{
fullName: "Windows Server 2008 for 32-bit Systems Service Pack 2",
arch: "32-bit",
prodName: "Windows Server 2008",
fullName: "Windows Server 2008 for 32-bit Systems Service Pack 2",
arch: "32-bit",
prodName: "Windows Server 2008",
finalName: "Windows Server 2008 for 32-bit Systems Service Pack 2 Version 6.0 / NT 6.0",
},
{
fullName: "Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation)",
arch: "32-bit",
prodName: "Windows Server 2008",
fullName: "Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation)",
arch: "32-bit",
prodName: "Windows Server 2008",
finalName: "Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation) Version 6.0 / NT 6.0",
},
{
fullName: "Windows Server 2008 for x64-based Systems Service Pack 2",
arch: "64-bit",
prodName: "Windows Server 2008",
fullName: "Windows Server 2008 for x64-based Systems Service Pack 2",
arch: "64-bit",
prodName: "Windows Server 2008",
finalName: "Windows Server 2008 for x64-based Systems Service Pack 2 Version 6.0 / NT 6.0",
},
{
fullName: "Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core installation)",
arch: "64-bit",
prodName: "Windows Server 2008",
fullName: "Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core installation)",
arch: "64-bit",
prodName: "Windows Server 2008",
finalName: "Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core installation) Version 6.0 / NT 6.0",
},
{
fullName: "Windows Server 2008 R2 for x64-based Systems Service Pack 1",
arch: "64-bit",
prodName: "Windows Server 2008 R2",
fullName: "Windows Server 2008 R2 for x64-based Systems Service Pack 1",
arch: "64-bit",
prodName: "Windows Server 2008 R2",
finalName: "Windows Server 2008 R2 for x64-based Systems Service Pack 1 Version 6.1 / NT 6.1",
},
{
fullName: "Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation)",
arch: "64-bit",
prodName: "Windows Server 2008 R2",
fullName: "Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation)",
arch: "64-bit",
prodName: "Windows Server 2008 R2",
finalName: "Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation) Version 6.1 / NT 6.1",
},
{
fullName: "Windows 10 Version 1803 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1803 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1803 for x64-based Systems",
},
{
fullName: "Windows Server, version 1803 (Server Core Installation)",
arch: "all",
prodName: "Windows Server",
fullName: "Windows Server, version 1803 (Server Core Installation)",
arch: "all",
prodName: "Windows Server",
finalName: "Windows Server, version 1803 (Server Core Installation)",
},
{
fullName: "Windows 10 Version 1809 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1809 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1809 for x64-based Systems",
},
{
fullName: "Windows Server 2019",
arch: "all",
prodName: "Windows Server 2019",
fullName: "Windows Server 2019",
arch: "all",
prodName: "Windows Server 2019",
finalName: "Windows Server 2019 Version 1809",
},
{
fullName: "Windows Server 2019 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2019",
fullName: "Windows Server 2019 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2019",
finalName: "Windows Server 2019 (Server Core installation) Version 1809",
},
{
fullName: "Windows 10 Version 1709 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1709 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1709 for x64-based Systems",
},
{
fullName: "Windows 10 Version 1903 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1903 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1903 for x64-based Systems",
},
{
fullName: "Windows Server, version 1903 (Server Core installation)",
arch: "all",
prodName: "Windows Server",
fullName: "Windows Server, version 1903 (Server Core installation)",
arch: "all",
prodName: "Windows Server",
finalName: "Windows Server, version 1903 (Server Core installation)",
},
{
fullName: "Windows 10 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 for x64-based Systems",
},
{
fullName: "Windows 10 Version 1607 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1607 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1607 for x64-based Systems",
},
{
fullName: "Windows Server 2016",
arch: "all",
prodName: "Windows Server 2016",
fullName: "Windows Server 2016",
arch: "all",
prodName: "Windows Server 2016",
finalName: "Windows Server 2016 Version 1607",
},
{
fullName: "Windows Server 2016 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2016",
fullName: "Windows Server 2016 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2016",
finalName: "Windows Server 2016 (Server Core installation) Version 1607",
},
{
fullName: "Windows 8.1 for x64-based systems",
arch: "64-bit",
prodName: "Windows 8.1",
fullName: "Windows 8.1 for x64-based systems",
arch: "64-bit",
prodName: "Windows 8.1",
finalName: "Windows 8.1 for x64-based systems Version 6.3 / NT 6.3",
},
{
fullName: "Windows Server 2012",
arch: "all",
prodName: "Windows Server 2012",
fullName: "Windows Server 2012",
arch: "all",
prodName: "Windows Server 2012",
finalName: "Windows Server 2012 Version 6.2 / NT 6.2",
},
{
fullName: "Windows Server 2012 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012",
fullName: "Windows Server 2012 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012",
finalName: "Windows Server 2012 (Server Core installation) Version 6.2 / NT 6.2",
},
{
fullName: "Windows Server 2012 R2",
arch: "all",
prodName: "Windows Server 2012 R2",
fullName: "Windows Server 2012 R2",
arch: "all",
prodName: "Windows Server 2012 R2",
finalName: "Windows Server 2012 R2 Version 6.3 / NT 6.3",
},
{
fullName: "Windows Server 2012 R2 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012 R2",
fullName: "Windows Server 2012 R2 (Server Core installation)",
arch: "all",
prodName: "Windows Server 2012 R2",
finalName: "Windows Server 2012 R2 (Server Core installation) Version 6.3 / NT 6.3",
},
{
fullName: "Windows 10 Version 1909 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1909 for x64-based Systems",
arch: "64-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1909 for x64-based Systems",
},
{
fullName: "Windows Server, version 1909 (Server Core installation)",
arch: "all",
prodName: "Windows Server",
fullName: "Windows Server, version 1909 (Server Core installation)",
arch: "all",
prodName: "Windows Server",
finalName: "Windows Server, version 1909 (Server Core installation)",
},
{
fullName: "Windows 10 Version 1803 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1803 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1803 for 32-bit Systems",
},
{
fullName: "Windows 10 Version 1803 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
fullName: "Windows 10 Version 1803 for ARM64-based Systems",
arch: "arm64",
prodName: "Windows 10",
finalName: "Windows 10 Version 1803 for ARM64-based Systems",
},
{
fullName: "Windows 10 Version 1809 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
fullName: "Windows 10 Version 1809 for 32-bit Systems",
arch: "32-bit",
prodName: "Windows 10",
finalName: "Windows 10 Version 1809 for 32-bit Systems",
},
{
fullName: "None Available",
arch: "all",
prodName: "",
fullName: "None Available",
arch: "all",
prodName: "",
finalName: "None Available",
},
{
fullName: "Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation)",
arch: "32-bit",
prodName: "Windows Server 2008",
fullName: "Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation)",
arch: "32-bit",
prodName: "Windows Server 2008",
finalName: "Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation) Version 6.0 / NT 6.0",
},
{
fullName: "Windows Server 2008 for Itanium-Based Systems Service Pack 2",
arch: "itanium",
prodName: "Windows Server 2008",
fullName: "Windows Server 2008 for Itanium-Based Systems Service Pack 2",
arch: "itanium",
prodName: "Windows Server 2008",
finalName: "Windows Server 2008 for Itanium-Based Systems Service Pack 2 Version 6.0 / NT 6.0",
},
{
fullName: "Windows Server 2008 R2 for Itanium-Based Systems Service Pack 1",
arch: "itanium",
prodName: "Windows Server 2008 R2",
fullName: "Windows Server 2008 R2 for Itanium-Based Systems Service Pack 1",
arch: "itanium",
prodName: "Windows Server 2008 R2",
finalName: "Windows Server 2008 R2 for Itanium-Based Systems Service Pack 1 Version 6.1 / NT 6.1",
},
}
@ -429,6 +499,7 @@ func TestFullProductName(t *testing.T) {
for _, tCase := range testCases {
sut := NewProductFromFullName(tCase.fullName)
require.Equal(t, tCase.prodName, sut.Name(), tCase)
require.Equal(t, tCase.finalName, string(sut), tCase)
}
})
}

View file

@ -3,9 +3,10 @@ package parsed
import (
"encoding/json"
"errors"
"os"
"github.com/fleetdm/fleet/v4/server/ptr"
"golang.org/x/exp/slices"
"os"
)
type SecurityBulletin struct {
@ -45,6 +46,9 @@ func UnmarshalBulletin(fPath string) (*SecurityBulletin, error) {
if err != nil {
return nil, err
}
for pID, name := range bulletin.Products {
bulletin.Products[pID] = NewProductFromFullName(string(name))
}
return &bulletin, nil
}

View file

@ -39,7 +39,7 @@ func TestParser(t *testing.T) {
f.Close()
require.NoError(t, err)
// All the products we expect to see, grouped by their product name
// All the products we expect to see after marshaling, grouped by their product name.
expectedProducts := map[string]parsed.Products{
"Windows 10": {
"11568": parsed.NewProductFromFullName("Windows 10 Version 1809 for 32-bit Systems"),
@ -112,6 +112,79 @@ func TestParser(t *testing.T) {
},
}
// All the products we expect to see in the parsed XML file, grouped by product name.
expectedXMLProducts := map[string]parsed.Products{
"Windows 10": {
"11568": parsed.Product("Windows 10 Version 1809 for 32-bit Systems"),
"11569": parsed.Product("Windows 10 Version 1809 for x64-based Systems"),
"11570": parsed.Product("Windows 10 Version 1809 for ARM64-based Systems"),
"11712": parsed.Product("Windows 10 Version 1909 for 32-bit Systems"),
"11713": parsed.Product("Windows 10 Version 1909 for x64-based Systems"),
"11714": parsed.Product("Windows 10 Version 1909 for ARM64-based Systems"),
"11896": parsed.Product("Windows 10 Version 21H1 for x64-based Systems"),
"11897": parsed.Product("Windows 10 Version 21H1 for ARM64-based Systems"),
"11898": parsed.Product("Windows 10 Version 21H1 for 32-bit Systems"),
"11800": parsed.Product("Windows 10 Version 20H2 for x64-based Systems"),
"11801": parsed.Product("Windows 10 Version 20H2 for 32-bit Systems"),
"11802": parsed.Product("Windows 10 Version 20H2 for ARM64-based Systems"),
"11929": parsed.Product("Windows 10 Version 21H2 for 32-bit Systems"),
"11930": parsed.Product("Windows 10 Version 21H2 for ARM64-based Systems"),
"11931": parsed.Product("Windows 10 Version 21H2 for x64-based Systems"),
"10729": parsed.Product("Windows 10 for 32-bit Systems"),
"10735": parsed.Product("Windows 10 for x64-based Systems"),
"10852": parsed.Product("Windows 10 Version 1607 for 32-bit Systems"),
"10853": parsed.Product("Windows 10 Version 1607 for x64-based Systems"),
},
"Windows Server 2019": {
"11571": parsed.Product("Windows Server 2019"),
"11572": parsed.Product("Windows Server 2019 (Server Core installation)"),
},
"Windows Server 2022": {
"11923": parsed.Product("Windows Server 2022"),
"11924": parsed.Product("Windows Server 2022 (Server Core installation)"),
},
"Windows Server": {
"11803": parsed.Product("Windows Server, version 20H2 (Server Core Installation)"),
},
"Windows 11": {
"11926": parsed.Product("Windows 11 for x64-based Systems"),
"11927": parsed.Product("Windows 11 for ARM64-based Systems"),
},
"Windows Server 2016": {
"10816": parsed.Product("Windows Server 2016"),
"10855": parsed.Product("Windows Server 2016 (Server Core installation)"),
},
"Windows 8.1": {
"10481": parsed.Product("Windows 8.1 for 32-bit systems"),
"10482": parsed.Product("Windows 8.1 for x64-based systems"),
},
"Windows RT 8.1": {
"10484": parsed.Product("Windows RT 8.1"),
},
"Windows Server 2012": {
"10378": parsed.Product("Windows Server 2012"),
"10379": parsed.Product("Windows Server 2012 (Server Core installation)"),
},
"Windows Server 2012 R2": {
"10483": parsed.Product("Windows Server 2012 R2"),
"10543": parsed.Product("Windows Server 2012 R2 (Server Core installation)"),
},
"Windows 7": {
"10047": parsed.Product("Windows 7 for 32-bit Systems Service Pack 1"),
"10048": parsed.Product("Windows 7 for x64-based Systems Service Pack 1"),
},
"Windows Server 2008": {
"9312": parsed.Product("Windows Server 2008 for 32-bit Systems Service Pack 2"),
"10287": parsed.Product("Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation)"),
"9318": parsed.Product("Windows Server 2008 for x64-based Systems Service Pack 2"),
"9344": parsed.Product("Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core installation)"),
},
"Windows Server 2008 R2": {
"10051": parsed.Product("Windows Server 2008 R2 for x64-based Systems Service Pack 1"),
"10049": parsed.Product("Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation)"),
},
}
expectedCVEs := map[string][]string{
"Windows 10": {
"CVE-2022-30190",
@ -1213,7 +1286,7 @@ func TestParser(t *testing.T) {
t.Run("parseXML", func(t *testing.T) {
t.Run("only windows products are included", func(t *testing.T) {
var expected []msrcxml.Product
for _, grp := range expectedProducts {
for _, grp := range expectedXMLProducts {
for pID, pFn := range grp {
expected = append(
expected,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -100,6 +100,34 @@
}
}
[purpose='section-headline'] {
padding-top: 64px;
padding-bottom: 64px;
p {
margin-bottom: 0px;
}
}
[purpose='articles'] {
padding-bottom: 64px;
}
[purpose='article-link'] {
padding-top: 24px;
padding-bottom: 24px;
border-bottom: 1px solid var(--Fleet-Black-10, #E2E4EA);
[parasails-component='animated-arrow-button'] {
width: fit-content;
display: flex;
align-items: center;
padding: 0;
text-decoration: none;
font-weight: 700;
[purpose='button-text'] {
width: 100%;
}
}
}
[purpose='testimonials-container'] {
columns: 3;
margin-bottom: 32px;
@ -427,6 +455,16 @@
height: 304px;
}
}
[purpose='section-headline'] {
padding-top: 32px;
padding-bottom: 32px;
p {
margin-bottom: 0px;
}
}
[purpose='articles'] {
padding-bottom: 48px;
}
[purpose='statistics'] {
[purpose='statistics-column'] {
display: flex;

View file

@ -112,6 +112,32 @@
</div>
</div>
<div purpose="page-section">
<div purpose="section-headline">
<h2>Case strudies</h2>
<p>Real-world stories of why the community and customers love Fleet.</p>
</div>
<div purpose="articles">
<div purpose="articles">
<div purpose="article-link">
<animated-arrow-button arrow-color="#3E4771" class="w-100" href="/announcements/consolidate-multiple-tools-with-fleet">🥀 Leading financial company consolidates multiple tools with Fleet</animated-arrow-button>
</div>
<div purpose="article-link">
<animated-arrow-button arrow-color="#3E4771" class="w-100" href="/announcements/global-cloud-platform-simplifies-device-management-with-fleet">🪟 Global edge cloud platform simplifies device management with Fleet</animated-arrow-button>
</div>
<div purpose="article-link">
<animated-arrow-button arrow-color="#3E4771" class="w-100" href="/announcements/worldwide-security-and-authentication-platform-chooses-fleet-for-linux">🚪 Worldwide security and authentication platform chooses Fleet for Linux management</animated-arrow-button>
</div>
<div purpose="article-link">
<animated-arrow-button arrow-color="#3E4771" class="w-100" href="/announcements/large-gaming-company-enhances-server-observability-with-fleet">🔌 Large gaming company enhances server observability with Fleet</animated-arrow-button>
</div>
<div purpose="article-link">
<animated-arrow-button arrow-color="#3E4771" class="w-100" href="/announcements/vehicle-manufacturer-transitions-to-fleet-for-endpoint-security">🚪 Vehicle manufacturer transitions to Fleet for endpoint security</animated-arrow-button>
</div>
<div purpose="article-link">
<animated-arrow-button arrow-color="#3E4771" class="w-100" href="/announcements/foursquare-quickly-migrates-to-fleet">🚪 Foursquare quickly migrates to Fleet for Device Management</animated-arrow-button>
</div>
</div>
</div>
<div class="d-flex flex-row align-items-center justify-content-center">
<a purpose="share-button" href="https://github.com/fleetdm/fleet/edit/main/handbook/company/testimonials.yml" target="_blank">Share your story</a>
</div>