mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Website add articles page. (#5243)
* create '/articles' and add 4 test articles * update build-static-content script for '/articles' * article pages * Validate article page metaData * add articles linked from header, remove test articles * update article styles * comment out /articles route, update blog post links * Move article category page to different branch for future PR * Update build-static-content.js * remove pagescript * Update deploying-fleet-on-render.md * update meta tags * lint fixes * Style & Markdown updates Made a couple of fixes to the styling and markdown. * update view action, replace route with regex, update links * authorsGitHubUserName -> authorGitHubUsername, authorsFullName -> authorFullName Co-authored-by: Mike Thomas <mthomas@fleetdm.com>
This commit is contained in:
parent
384c987389
commit
cd0ec13a0c
154
articles/deploying-fleet-on-aws-with-terraform.md
Normal file
154
articles/deploying-fleet-on-aws-with-terraform.md
Normal file
@ -0,0 +1,154 @@
|
||||
# Deploying Fleet on AWS with Terraform
|
||||
|
||||
![Deploying Fleet on AWS with Terraform](https://miro.medium.com/1*IzLHvDlUTDj3SXzLUQqPYA.png)
|
||||
|
||||
There are many ways to deploy Fleet. Last time, we looked at deploying [Fleet on Render](./articles/deploying-fleet-on-render.md). This time, we’re going to deploy Fleet on AWS with Terraform IaC (infrastructure as code).
|
||||
|
||||
Deploying on AWS with Fleet’s reference architecture will get you a fully functional Fleet instance that can scale to your needs
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
- AWS CLI installed
|
||||
- Terraform installed (version 1.04 or greater)
|
||||
- AWS Account and IAM user capable of creating resources
|
||||
- Clone [Fleet](https://github.com/fleetdm/fleet) or copy the [terraform files](https://github.com/fleetdm/fleet/tree/fleet-v4.7.0/tools/terraform)
|
||||
|
||||
## Bootstrapping
|
||||
|
||||
To bootstrap our [remote state](https://www.terraform.io/docs/language/state/remote.html) resources, we’ll create a S3 bucket and DynamoDB table. You can use the resources in [`remote-state`](https://www.terraform.io/docs/language/state/remote.html) as an example. Override the `prefix` terraform variable to get unique resources.
|
||||
|
||||
1. `terraform init`
|
||||
2. `terraform workspace new prod`
|
||||
3. `terraform apply -var prefix=queryops`
|
||||
|
||||
You should be able to see all the resources that Terraform will create — the **S3 bucket** and the **dynamodb** table:
|
||||
|
||||
```
|
||||
Plan: 3 to add, 0 to change, 0 to destroy.
|
||||
|
||||
Do you want to perform these actions in workspace "dev"?
|
||||
|
||||
Terraform will perform the actions described above.
|
||||
|
||||
Only 'yes' will be accepted to approve.
|
||||
|
||||
Enter a value:
|
||||
```
|
||||
|
||||
After typing `yes` you should have a new S3 bucket named `<prefix>-terraform-remote-state` And the table `<prefix>-terraform-state-lock`. Keep these handy because we’ll need them in the following steps.
|
||||
|
||||
## Infastructure
|
||||
https://github.com/fleetdm/fleet/tools/terraform
|
||||
|
||||
Using the buckets and table we just created, we’ll update the [remote state](https://github.com/fleetdm/fleet/tree/fleet-v4.7.0/tools/terraform/main.tf) to expect the same values:
|
||||
|
||||
```
|
||||
terraform {
|
||||
// bootstrapped in ./remote-state
|
||||
backend "s3" {
|
||||
bucket = "queryops-terraform-remote-state"
|
||||
region = "us-east-2"
|
||||
key = "fleet/"
|
||||
dynamodb_table = "queryops-terraform-state-lock"
|
||||
}
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "3.57.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We’ll also need a `tfvars` file to make some environment-specific variable overrides. Create a file in the same directory named `prod.tfvars` and paste the contents (note the bucket names will have to be unique for your environment):
|
||||
|
||||
```
|
||||
fleet_backend_cpu = 1024
|
||||
fleet_backend_mem = 4096 //software inventory requires 4GB
|
||||
redis_instance = "cache.t3.micro"
|
||||
fleet_min_capacity = 1
|
||||
fleet_max_capacity = 5
|
||||
domain_fleetdm = fleet.queryops.com // YOUR DOMAIN HERE
|
||||
software_inventory = "1"
|
||||
vulnerabilities_path = "/fleet/vuln"
|
||||
osquery_results_s3_bucket = "queryops-osquery-results-archive-dev"
|
||||
osquery_status_s3_bucket = "queryops-osquery-status-archive-dev"
|
||||
file_carve_bucket = "queryops-file-carve"
|
||||
```
|
||||
|
||||
Now we’re ready to apply the terraform:
|
||||
|
||||
1. `terraform init`
|
||||
2. `terraform workspace new prod`
|
||||
3. `terraform apply -var-file=prod.tfvars`
|
||||
|
||||
You should see the planned output, and you will need to confirm the creation. Review this output, and type `yes` when you are ready. This process should take 5–10 minutes.
|
||||
|
||||
Let’s say we own `queryops.com` and have an ACM certificate issued to it. We want to host Fleet at `fleet.queryops.com` so in this case, we’ll need to hand nameserver authority over to `fleet.queryops.com` before ACM will verify via DNS and issue the certificate. To make this work, we need to create an `NS` record on `queryops.com`, and put the same `NS` records that get created after terraform creates the `fleet.queryops.com` hosted zone.
|
||||
|
||||
![Route 53 QueryOps Hosted Zone](https://miro.medium.com/1*hAUEUWBezneuydgClWzChw.png)
|
||||
|
||||
Once `terraform apply` finishes you should see output similar to:
|
||||
|
||||
```
|
||||
acm_certificate_arn = "arn:aws:acm:us-east-2:123169442427:certificate/b2845034-d4e1-4ff2-9630-1c93feaf2185"
|
||||
aws_alb_name = "fleetdm"
|
||||
aws_alb_target_group_name = "fleetdm"
|
||||
backend_security_group = "arn:aws:ec2:us-east-2:123169442427:security-group/sg-00c9fa9632d7e03ca"
|
||||
fleet-backend-task-revision = 5
|
||||
fleet-migration-task-revision = 4
|
||||
fleet_ecs_cluster_arn = "arn:aws:ecs:us-east-2:123169442427:cluster/fleet-backend"
|
||||
fleet_ecs_cluster_id = "arn:aws:ecs:us-east-2:123169442427:cluster/fleet-backend"
|
||||
fleet_ecs_service_name = "fleet"
|
||||
fleet_min_capacity = 2
|
||||
load_balancer_arn_suffix = "app/fleetdm/3427efb8c09088be"
|
||||
mysql_cluster_members = toset([
|
||||
"fleetdm-mysql-iam-1",
|
||||
])
|
||||
nameservers_fleetdm = tolist([
|
||||
"ns-1181.awsdns-19.org",
|
||||
"ns-1823.awsdns-35.co.uk",
|
||||
"ns-314.awsdns-39.com",
|
||||
"ns-881.awsdns-46.net",
|
||||
])
|
||||
private_subnets = [
|
||||
"arn:aws:ec2:us-east-2:123169442427:subnet/subnet-03a54736c942cd1e4",
|
||||
"arn:aws:ec2:us-east-2:123169442427:subnet/subnet-07b59b34d4e0850e5",
|
||||
"arn:aws:ec2:us-east-2:123169442427:subnet/subnet-084d808e122d776af",
|
||||
]
|
||||
redis_cluster_members = toset([
|
||||
"fleetdm-redis-001",
|
||||
"fleetdm-redis-002",
|
||||
"fleetdm-redis-003",
|
||||
])
|
||||
target_group_arn_suffix = "targetgroup/fleetdm/0f3bec83c8b02f58"
|
||||
```
|
||||
|
||||
We can use the output here to create an AWS ECS Task that will migrate the database and prepare it for use.
|
||||
|
||||
```
|
||||
aws ecs run-task --cluster fleet-backend --task-definition fleet-migrate:<latest_version> --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[<private_subnet_id>],securityGroups=[<desired_security_group>]}"
|
||||
```
|
||||
|
||||
Where `<private_subnet_id>` is one of the private subnets, and `<desired_security_group>` is the security group from the output for example:
|
||||
|
||||
```
|
||||
aws ecs run-task --cluster fleet-backend --task-definition fleet-migrate:4 --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[subnet-03a54736c942cd1e4],securityGroups=[sg-00c9fa9632d7e03ca]}"
|
||||
```
|
||||
|
||||
Running this command should kick off the migration task, and Fleet should be ready to go.
|
||||
|
||||
![AWS Console ECS Clusters](https://miro.medium.com/1*vw5pH-2T0zxtH7GtxLBskA.png)
|
||||
|
||||
Navigating to `https://fleet.queryops.com` we should be greeted with the Setup page.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Setting up all the required infrastructure to run a dedicated web service in AWS can be a daunting task. The Fleet team’s goal is to provide a solid base to build from. As most AWS environments have their own specific needs and requirements, this base is intended to be modified and tailored to your specific needs.
|
||||
|
||||
|
||||
<meta name="category" value="guides">
|
||||
<meta name="authorGitHubUsername" value="edwardsb">
|
||||
<meta name="authorFullName" value="Ben Edwards">
|
||||
<meta name="publishedOn" value="2021-11-30">
|
||||
<meta name="articleTitle" value="Deploying Fleet on AWS with Terraform">
|
151
articles/deploying-fleet-on-render.md
Normal file
151
articles/deploying-fleet-on-render.md
Normal file
@ -0,0 +1,151 @@
|
||||
# Deploying Fleet on Render
|
||||
|
||||
![deploying fleet on render](https://miro.medium.com/1*Ai8I3fIS8p0afb8dBWqS-A.jpeg)
|
||||
|
||||
[Render](https://render.com/) is a cloud hosting service that makes it dead simple to get things up and running fast, without the typical headache of larger enterprise hosting providers. Hosting Fleet on Render is a cost effective and scalable cloud environment with a lower barrier to entry, making it a great place to get some experience with [Fleet](https://fleetdm.com/) and [osquery](https://osquery.io/).
|
||||
|
||||
---
|
||||
|
||||
Below we’ll look at how to deploy Fleet on Render using Render WebService & Private Service components. To complete this you’ll need an account on Render, and about 30 minutes.
|
||||
|
||||
Fleet only has 2 external dependencies:
|
||||
|
||||
- MySQL 5.7
|
||||
- Redis 6
|
||||
|
||||
First let’s get these dependencies up and running on Render.
|
||||
|
||||
---
|
||||
|
||||
## MySQL
|
||||
|
||||
Fleet uses MySQL as the datastore to organize host enrollment and other metadata around serving Fleet. Start by forking [https://github.com/edwardsb/render-mysql](https://github.com/edwardsb/render-mysql), then create a new private service within Render. When prompted for the repository — enter your fork’s URL here.
|
||||
|
||||
![Private Service component in Render](https://miro.medium.com/max/866/0*Adu28Jm9-ImazTHV)
|
||||
Private Service component in Render
|
||||
|
||||
This private service will run MySQL, our database, so let’s give it a fitting name, something like “fleet-mysql”.
|
||||
|
||||
We’re also going to need to set up some environment variables and a disk to mount. Expand “Advanced” and enter the following:
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `MYSQL_DATABASE=fleet`
|
||||
- `MYSQL_PASSWORD=supersecurepw`
|
||||
- `MYSQL_ROOT_PASSWORD=supersecurerootpw`
|
||||
- `MYSQL_USER=fleet`
|
||||
|
||||
### Disks
|
||||
|
||||
- Name: `mysql`
|
||||
- Mount Path: `/var/lib/mysql`
|
||||
- Size: `50GB`
|
||||
|
||||
---
|
||||
|
||||
## Redis
|
||||
|
||||
The next dependency we’ll configure is Redis. Fleet uses Redis to ingest and queue the results of distributed queries, cache data, etc. Luckily for us the folks over at Render have a ready-to-deploy Redis template that makes deploying Redis as a private service a single mouse click. Check out [https://render.com/docs/deploy-redis](https://render.com/docs/deploy-redis).
|
||||
|
||||
After it’s deployed, you should see a unique Redis host/port combination, we’ll need that for Fleet so make sure to copy it for later.
|
||||
|
||||
---
|
||||
|
||||
## Fleet
|
||||
|
||||
Now that we have the dependencies up and running, on to Fleet!
|
||||
|
||||
Start by forking or use [https://github.com/edwardsb/fleet-on-render](https://github.com/edwardsb/fleet-on-render) directly. This Dockerfile is based on Fleet, but overrides the default command to include the migration step, which prepares the database by running all required migrations. Normally it’s best to do this as a separate task, or job that runs before a new deployment, but for simplicity we can have it run every time the task starts.
|
||||
|
||||
Back in Render, create a new web service and give it a unique name, since this will be resolvable on the internet, it actually has to be unique on Render’s platform.
|
||||
|
||||
![Web Service component in Render](https://miro.medium.com/max/866/1*fQqcEymjiL29TaR7hqjsvw.png)
|
||||
Web Service component in Render
|
||||
|
||||
Next we will supply the environment variables Fleet needs to connect to the database and redis. We are also going to disable TLS on the Fleet server, since Render is going to handle SSL termination for us.
|
||||
|
||||
Give it the following environment variables:
|
||||
|
||||
- `FLEET_MYSQL_ADDRESS=fleet-mysql:3306`(your unique service address)
|
||||
- `FLEET_MYSQL_DATABASE=fleet`
|
||||
- `FLEET_MYSQL_PASSWORD=supersecurepw`
|
||||
- `FLEET_MYSQL_USERNAME=fleet`
|
||||
- `FLEET_REDIS_ADDRESS=fleet-redis:10000` (your unique Redis host:port from earlier)
|
||||
- `FLEET_SERVER_TLS=false` (Render takes care of SSL termination)
|
||||
|
||||
Additionally we’ll configure the following so Render knows how to build our app and make sure its healthy:
|
||||
|
||||
![Additional component details](https://miro.medium.com/max/1400/1*wqTxWtAElPOQv4ORP4WkMw.png)
|
||||
|
||||
- Health Check Path: `/healthz`
|
||||
- Docker Build Context Directory: `.`
|
||||
- Dockerfile Path: `./Dockerfile`
|
||||
|
||||
Click Create and watch Render deploy Fleet! You should see something like this in the event logs:
|
||||
|
||||
```
|
||||
Migrations completed.
|
||||
ts=2021–09–15T02:09:07.06528012Z transport=http address=0.0.0.0:8080 msg=listening
|
||||
```
|
||||
|
||||
Fleet is up and running, head to your public URL.
|
||||
|
||||
![Fleet deployed on Render](https://miro.medium.com/max/1176/0*HoUPvU4GlitzQa4v)
|
||||
Fleet deployed on Render
|
||||
|
||||
---
|
||||
|
||||
## Setup Fleet and enroll hosts
|
||||
|
||||
You should be prompted with a setup page, where you can enter your name, email, and password. Run through those steps and you should have an empty hosts page waiting for you.
|
||||
|
||||
You’ll find the enroll-secret after clicking “Add New Hosts”. This is a special secret the host will need to register to your Fleet instance. Once you have the enroll-secret you can use `fleetctl` to create Orbit installers, which makes installing and updating osquery super simple. [Download fleetctl](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.3.0) and try the following command (Docker require) on your terminal:
|
||||
|
||||
```
|
||||
fleetctl package --type=msi --enroll-secret <secret> --fleet-url https://<your-unique-service-name>.onrender.com
|
||||
```
|
||||
|
||||
This command creates an `msi` installer pointed at your Fleet instance.
|
||||
|
||||
Now we need some awesome queries to run against the hosts we enroll, check out the collection [here](https://github.com/fleetdm/fleet/tree/main/docs/01-Using-Fleet/standard-query-library).
|
||||
|
||||
To get them into Fleet we can use `fleetctl` again. Run the following on your terminal:
|
||||
|
||||
```
|
||||
curl https://raw.githubusercontent.com/fleetdm/fleet/main/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml -o standard-query-library.yaml
|
||||
```
|
||||
|
||||
Now that we downloaded the standard query library, we’ll apply it using `fleetctl`. First we’ll configure `fleetctl` to use the instance we just built.
|
||||
|
||||
Try running:
|
||||
|
||||
```
|
||||
fleetctl config set --address https://<your-unique-service-name>.onrender.com
|
||||
```
|
||||
|
||||
Next, login with your credentials from when you set up the Fleet instance by running `fleetctl login`:
|
||||
|
||||
```
|
||||
fleetctl login
|
||||
Log in using the standard Fleet credentials.
|
||||
Email: <enter user you just setup>
|
||||
Password:
|
||||
Fleet login successful and context configured!
|
||||
```
|
||||
|
||||
Applying the query library is simple. Just run:
|
||||
|
||||
```
|
||||
fleetctl apply -f standard-query-library.yaml
|
||||
```
|
||||
|
||||
`fleetctl` makes configuring Fleet really easy, directly from your terminal. You can even create API credentials so you can script `fleetctl` commands, and really unlock the power of Fleet.
|
||||
|
||||
That’s it! We have successfully deployed and configured a Fleet instance! Render makes this process super easy, and you can even enable auto-scaling and let the app grow with your needs.
|
||||
|
||||
|
||||
<meta name="category" value="guides">
|
||||
<meta name="authorGitHubUsername" value="edwardsb">
|
||||
<meta name="authorFullName" value="Ben Edwards">
|
||||
<meta name="publishedOn" value="2021-09-21">
|
||||
<meta name="articleTitle" value="Deploying Fleet on Render">
|
47
articles/fleet-user-stories-f100.md
Normal file
47
articles/fleet-user-stories-f100.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Fleet user stories
|
||||
|
||||
## Cloud Security Technical Lead — F100 security and networking company.
|
||||
|
||||
![Two people talking about Fleet](https://miro.medium.com/1*7BTPQ_RRbL9h9YkxT8caDQ.jpeg)
|
||||
|
||||
When we spoke recently, our next Fleet user (who’s name and employer must remain anonymous for contractual reasons) gave us candid insights into how osquery and Fleet has been adopted at their F100 security and networking company.
|
||||
|
||||
### How did you first get started using osquery?
|
||||
|
||||
The first time, or the real time? The first time was right after Facebook announced their new tool, called osquery. I was a security consultant at the time. I installed it on my Mac, ran a few queries, thought “this is cool,” closed it, and forgot about it.
|
||||
|
||||
A while later, I was reintroduced as part of a reorganization of our cloud security team, here at [censored]. We had an initiative to roll out osquery to all our server endpoints, and I was like “I’ve heard of this, neat!” That’s when I joined the osquery Slack, started talking to people about performance, and how to write queries.
|
||||
|
||||
Zach (Wasserman) and Fritz really helped me to get started with performant queries — the osquery community is really helpful.
|
||||
|
||||
My company uses osquery under the hood for a couple of projects, and it solves the problem of having endpoint visibility. The Carbon Blacks of the world will sell you their solutions with all the bells and whistles, but at the end of the day, what you really need is to be able to ask a question, and get an answer.
|
||||
|
||||
### Why are you using Fleet?
|
||||
|
||||
As a part of our whole osquery initiative, we had to deploy with Ansible, which was a little challenging. That, combined with the limitations of Kinesis, and the tooling capabilities we wanted led us to realizing that we needed a fleet manager. We wanted to help security operations be able to write queries to help them with IRs, and we wanted to collect information about devices and store them in the SIEM.
|
||||
|
||||
Level of maintenance and price were both factors. We evaluated a couple of products, like Zercurity, Kolide, osctrl, and sgt. When we heard Fleet was becoming a company, and was going open core, we saw that as a great opportunity to partner up, and drive features and roadmap requests. It would let us balance the needs of the business versus the needs of the many — like support for AWS Lambda as a long destination, for example.
|
||||
|
||||
### How do your end users feel about Fleet?
|
||||
|
||||
So far, the end user is me, and I like it a lot. There’s room for improvement in the UI — not to say that things are bad, but there are features that could be added to make it better. More visibility into what the hosts are doing when it interacts with osquery, getting a reporting dashboard around performance of the Fleet server itself, and the upcoming performance features spring to mind (editor note: query performance was recently released in Fleet 4.3.0.)
|
||||
|
||||
The auth model also has room for improvement, but I’m glad it was introduced.<br>More granularity with that would be dope.
|
||||
|
||||
All that said, Fleet is exactly what we were looking for: a dead simple way to manage osquery and hosts.
|
||||
|
||||
### How are you dealing with alert fatigue and false positives from your SIEM?
|
||||
|
||||
I don’t directly deal with that — that’s our security operations team, mostly. I don’t think we deal with a lot of alert fatigue, or false positives. I would say, conservatively, that 80% of our alerts are actionable. We use Splunk mostly for historical and IR purposes. There are some alerts in there for some very specific purposes, and when they trigger, it means there’s a thing that needs to happen.
|
||||
|
||||
### How could Fleet be better?
|
||||
|
||||
You guys have done things well in a lot of ways, and I’m like; “this makes sense.” More improvements to the documentation would be helpful during adoption — e.g., easy to understand reference docs about YAML and config.
|
||||
|
||||
I love Fleet. It was stupid simple to set up once we were engaged with y’all. You’ve been really responsive to feature requests and help. The fleetctl thing was especially helpful. It was something that I could have done myself, but y’all did it in a day. It’s things like this that have large knock-on effects.
|
||||
|
||||
<meta name="category" value="success stories">
|
||||
<meta name="authorGitHubUsername" value="mike-j-thomas">
|
||||
<meta name="authorFullName" value="Mike Thomas">
|
||||
<meta name="publishedOn" value="2021-09-29">
|
||||
<meta name="articleTitle" value="Fleet user stories — F100">
|
33
articles/fleet-user-stories-schrodinger.md
Normal file
33
articles/fleet-user-stories-schrodinger.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Fleet user stories
|
||||
|
||||
## Jason Walton — Director of information security @ Schrödinger
|
||||
|
||||
![Four speech bubbles and four stars emanating from the Fleet logo](https://miro.medium.com/1*uAZ-YhsFJIhNCl1Cz-lVxQ.jpeg)
|
||||
|
||||
Jason Walton gives us some insight into how his team uses Fleet and osquery at Schrödinger.
|
||||
|
||||
### How did you first get started using osquery?
|
||||
|
||||
I became aware of osquery a number of years ago — maybe 2017 when a colleague mentioned it. I experimented with it locally, and it was very interesting, but I never invested much time until I discovered Fleet (then Kolide Fleet) I believe around 2018.
|
||||
|
||||
### Why are you using Fleet?
|
||||
|
||||
It’s easy to deploy and use in combination with [Launcher](https://github.com/kolide/launcher). It provides me with a single source of truth about endpoints in my organization, and provides a separate “reporting plane” independent of tools used to configure or manage systems. Aggregating data across platforms is also extremely helpful.
|
||||
|
||||
### How do your end users feel about Fleet?
|
||||
|
||||
Our end users don’t notice it’s there — and we have *extremely* technical end users. This differs from other tools like our EDR solution which can occasionally cause performance issues. It’s a very lightweight tool.
|
||||
|
||||
### How are you dealing with alert fatigue and false positives from your SIEM?
|
||||
|
||||
We actually don’t use a SIEM for this reason. We rely on alerts and signals from individual tools that have high fidelity.
|
||||
|
||||
### How could Fleet be better?
|
||||
|
||||
I’ll admit to not liking SQL much. It would be handy to have a query builder for simpler queries. For more advanced queries, perhaps something like Logica that compiles down to SQL but can be nicer to use would be interesting. There’s also a lot of confusion I see from newcomers to osquery and Fleet who want to know where to get results from scheduled queries. Some detailed documentation on possible solutions for this (ELK, or maybe BigQuery?) would go a long way to getting people started.
|
||||
|
||||
<meta name="category" value="success stories">
|
||||
<meta name="authorGitHubUsername" value="mike-j-thomas">
|
||||
<meta name="authorFullName" value="Mike Thomas">
|
||||
<meta name="publishedOn" value="2021-09-10">
|
||||
<meta name="articleTitle" value="Fleet user stories — Schrödinger">
|
38
articles/fleet-user-stories-wayfair.md
Normal file
38
articles/fleet-user-stories-wayfair.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Fleet user stories
|
||||
|
||||
## Ahmed Elshaer — DFIR, Blue Team, SecOps @ Wayfair
|
||||
|
||||
![Two people talking about Fleet](https://miro.medium.com/1*7BTPQ_RRbL9h9YkxT8caDQ.jpeg)
|
||||
|
||||
This week, I spoke with Ahmed Elshaer (DFIR, Blue Team, SecOps) about how Wayfair uses Fleet and osquery:
|
||||
|
||||
### How did you first get started using osquery?
|
||||
|
||||
We were looking for a tool that provided linux logging, and incident response capabilities. Osquery had most of the requirements like logging, ability to scope an incident, interrogate systems but it’s missing the response or the ability to do an action on the remote systems.
|
||||
|
||||
### Why are you using Fleet?
|
||||
|
||||
We have POC’d couple free options and Fleet was the highest engagement and continuous development although it may be missing some features.
|
||||
How do your end users feel about Fleet?
|
||||
|
||||
We are using Fleet only in the remote query on scale, so we find Fleet is doing a good job in that area, and it’s easy to use for any new members.
|
||||
|
||||
### How are you dealing with alert fatigue and false positives from your SIEM?
|
||||
|
||||
We have lots of queries that generate logs, but the ones that go into alerts are verified queries that are intended to hunt malicious or suspicious activity. Those activities are known based on public threat reports, Mitre Attack, or internal Red Team exercise.
|
||||
|
||||
### How could Fleet be better?
|
||||
|
||||
Fleet is doing a pretty good job now by listening to users in different channels, and I’ve seen lots of my discussions and ideas come into
|
||||
|
||||
Fleet very fast, after a release or two. What I hope to see in Fleet are the following:
|
||||
|
||||
- Dashboard for all Assets and Labels, what are the online, offline, and new hosts metrics and over time.
|
||||
- Audit logs for all fleet actions shipped to a remote logging destination
|
||||
- Ability to create a notebook, list of queries that you can run ad-hoc like normal queries, which makes the IR process easier if you want to run and gather lots of data at once.
|
||||
|
||||
<meta name="category" value="success stories">
|
||||
<meta name="authorGitHubUsername" value="mike-j-thomas">
|
||||
<meta name="authorFullName" value="Mike Thomas">
|
||||
<meta name="publishedOn" value="2021-08-20">
|
||||
<meta name="articleTitle" value="Fleet user stories — Wayfair">
|
1633
website/.sailsrc
vendored
1633
website/.sailsrc
vendored
File diff suppressed because it is too large
Load Diff
79
website/api/controllers/articles/view-basic-article.js
vendored
Normal file
79
website/api/controllers/articles/view-basic-article.js
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
module.exports = {
|
||||
|
||||
friendlyName: 'View blog article',
|
||||
|
||||
|
||||
description: 'Display "Blog article" page.',
|
||||
|
||||
urlWildcardSuffix: 'slug',
|
||||
|
||||
inputs: {
|
||||
slug : {
|
||||
description: 'The relative path to the blog page from within this route.',
|
||||
example: 'guides/deploying-fleet-on-render',
|
||||
type: 'string',
|
||||
defaultsTo: ''
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
success: { viewTemplatePath: 'pages/articles/basic-article' },
|
||||
badConfig: { responseType: 'badConfig' },
|
||||
notFound: { responseType: 'notFound' },
|
||||
redirect: { responseType: 'redirect' },
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({slug}) {
|
||||
|
||||
if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.markdownPages) || !sails.config.builtStaticContent.compiledPagePartialsAppPath) {
|
||||
throw {badConfig: 'builtStaticContent.markdownPages'};
|
||||
}
|
||||
let thisPage = _.find(sails.config.builtStaticContent.markdownPages, {
|
||||
url: _.trimRight('/' + slug)
|
||||
});
|
||||
let needsRedirectMaybe = (!thisPage);
|
||||
|
||||
if (needsRedirectMaybe) {
|
||||
// Creating a lower case, repeating-slashless slug
|
||||
let multipleSlashesRegex = /\/{2,}/g;
|
||||
let modifiedslug = slug.toLowerCase().replace(multipleSlashesRegex, '/');
|
||||
// Finding the appropriate page content using the modified slug.
|
||||
let revisedPage = _.find(sails.config.builtStaticContent.markdownPages, {
|
||||
url: _.trimRight('/' + _.trim(modifiedslug, '/'), '/')
|
||||
});
|
||||
if(revisedPage) {
|
||||
// If we matched a page with the modified slug, then redirect to that.
|
||||
throw {redirect: revisedPage.url};
|
||||
} else {
|
||||
// If no page was found, throw a 404 error.
|
||||
throw 'notFound';
|
||||
}
|
||||
}
|
||||
// Setting the pages meta title and description from the articles meta tags.
|
||||
// Note: Every article page will have a 'articleTitle' and a 'authorFullName' meta tag.
|
||||
// if they are undefined, we'll use the generic title and description set in layout.ejs
|
||||
let pageTitleForMeta;
|
||||
if(thisPage.meta.articleTitle) {
|
||||
pageTitleForMeta = thisPage.meta.articleTitle + ' | Fleet for osquery';
|
||||
}
|
||||
let pageDescriptionForMeta;
|
||||
if(!thisPage.meta.articleTitle || !thisPage.meta.authorFullName) {
|
||||
pageDescriptionForMeta = thisPage.meta.articleTitle +' by '+thisPage.meta.authorFullName+'.';
|
||||
}
|
||||
|
||||
// Respond with view.
|
||||
return {
|
||||
path: require('path'),
|
||||
thisPage: thisPage,
|
||||
markdownPages: sails.config.builtStaticContent.markdownPages,
|
||||
compiledPagePartialsAppPath: sails.config.builtStaticContent.compiledPagePartialsAppPath,
|
||||
pageTitleForMeta,
|
||||
pageDescriptionForMeta,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
25
website/assets/js/pages/articles/basic-article.page.js
vendored
Normal file
25
website/assets/js/pages/articles/basic-article.page.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
parasails.registerPage('basic-article', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
1
website/assets/styles/importer.less
vendored
1
website/assets/styles/importer.less
vendored
@ -52,6 +52,7 @@
|
||||
@import 'pages/query-library.less';
|
||||
@import 'pages/docs/basic-documentation.less';
|
||||
@import 'pages/handbook/basic-handbook.less';
|
||||
@import 'pages/articles/basic-article.less';
|
||||
|
||||
@import 'pages/transparency.less';
|
||||
@import 'pages/press-kit.less';
|
||||
|
122
website/assets/styles/pages/articles/basic-article.less
vendored
Normal file
122
website/assets/styles/pages/articles/basic-article.less
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
#basic-article {
|
||||
padding: 0px 24px 0px 24px;
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
hr {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
border-top: 2px solid @core-vibrant-blue-15;
|
||||
}
|
||||
|
||||
[purpose='article-title'] {
|
||||
padding-top: 80px;
|
||||
h1 {
|
||||
margin-bottom: 16px;
|
||||
line-height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='article-details'] {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
span {
|
||||
color: @core-fleet-black-50;
|
||||
}
|
||||
p {
|
||||
margin-block-end: 0px;
|
||||
}
|
||||
img {
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
[purpose='article-content'] {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
h1:first-of-type {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 24px 0px 40px 0px;
|
||||
}
|
||||
a {
|
||||
color: @core-vibrant-blue;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ul, ol {
|
||||
padding-left: 16px;
|
||||
}
|
||||
li {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
li::marker {
|
||||
color: @core-vibrant-blue;
|
||||
}
|
||||
code:not(.nohighlight) {
|
||||
background: #F1F0FF;
|
||||
padding: 4px 8px;
|
||||
font-family: @code-font;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
color: @core-fleet-black;
|
||||
}
|
||||
pre {
|
||||
padding: 24px;
|
||||
border: 1px solid #E2E4EA;
|
||||
border-radius: 6px;
|
||||
margin: 0px 0px 40px;
|
||||
font-family: @code-font;
|
||||
background: #F9FAFC;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
}
|
||||
[purpose='bottom-cta'] {
|
||||
padding-bottom: 80px;
|
||||
[purpose='next-steps-button'] {
|
||||
padding: 16px 32px;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
img {
|
||||
height: 24px;
|
||||
}
|
||||
a {
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='article-title'] {
|
||||
padding-top: 60px;
|
||||
}
|
||||
[purpose='article-content'] {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
1
website/config/policies.js
vendored
1
website/config/policies.js
vendored
@ -34,5 +34,6 @@ module.exports.policies = {
|
||||
'view-platform': true,
|
||||
'view-landing': true,
|
||||
'deliver-demo-signup': true,
|
||||
'articles/*': true,
|
||||
|
||||
};
|
||||
|
8
website/config/routes.js
vendored
8
website/config/routes.js
vendored
@ -68,6 +68,14 @@ module.exports.routes = {
|
||||
}
|
||||
},
|
||||
|
||||
'r|/((device-management|security|releases|engineering|guides|announcements|use-cases)/(.+))$|': {
|
||||
skipAssets: false,
|
||||
action: 'articles/view-basic-article',
|
||||
locals: {
|
||||
currentPage: 'articles',
|
||||
}
|
||||
},// handles /device-management/foo, /security/foo, /releases/foo, /engineering/foo, /guides/foo, /announcements/foo, /use-cases/foo
|
||||
|
||||
'GET /docs/?*': {
|
||||
skipAssets: false,
|
||||
action: 'docs/view-basic-documentation',
|
||||
|
46
website/scripts/build-static-content.js
vendored
46
website/scripts/build-static-content.js
vendored
@ -131,7 +131,8 @@ module.exports = {
|
||||
|
||||
let SECTION_INFOS_BY_SECTION_REPO_PATHS = {
|
||||
'docs/': { urlPrefix: '/docs', },
|
||||
'handbook/': { urlPrefix: '/handbook', }
|
||||
'handbook/': { urlPrefix: '/handbook', },
|
||||
'articles/': { urlPrefix: '/articles', }
|
||||
};
|
||||
let rootRelativeUrlPathsSeen = [];
|
||||
for (let sectionRepoPath of Object.keys(SECTION_INFOS_BY_SECTION_REPO_PATHS)) {// FUTURE: run this in parallel
|
||||
@ -336,6 +337,49 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
// Checking the metadata on /articles pages, and adding the category to the each article's URL.
|
||||
if(sectionRepoPath === 'articles/') {
|
||||
if(!embeddedMetadata.authorGitHubUsername) {
|
||||
// Throwing an error if the article doesn't have a authorGitHubUsername meta tag
|
||||
throw new Error(`Failed compiling markdown content: An article page is missing a authorGitHubUsername meta tag (<meta name="authorGitHubUsername" value="">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, add a meta tag with the authors GitHub username.`);
|
||||
}
|
||||
if(!embeddedMetadata.authorFullName) {
|
||||
// Throwing an error if the article doesn't have a authorFullName meta tag
|
||||
throw new Error(`Failed compiling markdown content: An article page is missing a authorFullName meta tag (<meta name="authorFullName" value="">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, add a meta tag with the authors GitHub username.`);
|
||||
}
|
||||
if(!embeddedMetadata.articleTitle) {
|
||||
// Throwing an error if the article doesn't have a articleTitle meta tag
|
||||
throw new Error(`Failed compiling markdown content: An article page is missing a articleTitle meta tag (<meta name="articleTitle" value="">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, add a meta tag with the title of the article.`);
|
||||
}
|
||||
if(embeddedMetadata.publishedOn) {
|
||||
if(!embeddedMetadata.publishedOn.match(/^([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])$/g)){
|
||||
// Throwing an error if an article page's publishedOn meta value is an invalid ISO date string
|
||||
throw new Error(`Failed compiling markdown content: An article page has an invalid publishedOn value (<meta name="publishedOn" value="${embeddedMetadata.publishedOn}">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, add a meta tag with the ISO formatted date the article was published on`);
|
||||
}
|
||||
} else {
|
||||
// Throwing an error if the article is missing a 'publishedOn' meta tag
|
||||
throw new Error(`Failed compiling markdown content: An article page is missing a publishedOn meta tag (<meta name="publishedOn" value="2022-04-19">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, add a meta tag with the ISO formatted date the article was published on`);
|
||||
}
|
||||
if(embeddedMetadata.category) {
|
||||
// Throwing an error if the article has an invalid category.
|
||||
embeddedMetadata.category = embeddedMetadata.category.toLowerCase();
|
||||
let validArticleCategories = ['product', 'security', 'engineering', 'success stories', 'announcements', 'guides', 'releases' ];
|
||||
if(!validArticleCategories.includes(embeddedMetadata.category)) {
|
||||
throw new Error(`Failed compiling markdown content: An article page has an invalid category meta tag (<meta name="category" value="${embeddedMetadata.category}">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, change the meta tag to a valid category, one of: ${validArticleCategories}`);
|
||||
}
|
||||
} else {
|
||||
// throwing an error if the article is missing a category meta tag
|
||||
throw new Error(`Failed compiling markdown content: An article page is missing a category meta tag (<meta name="category" value="guides">) at "${path.join(topLvlRepoPath, pageSourcePath)}". To resolve, add a meta tag with the category of the article`);
|
||||
}
|
||||
// For article pages, we'll attach the category to the `rootRelativeUrlPath`.
|
||||
// If the article is categorized as 'product' we'll replace the category with 'use-cases', or if it is categorized as 'success story' we'll replace it with 'device-management'
|
||||
rootRelativeUrlPath = (
|
||||
'/' +
|
||||
(embeddedMetadata.category === 'product' ? 'use-cases' : embeddedMetadata.category === 'success stories' ? 'device-management' : embeddedMetadata.category) + '/' +
|
||||
(pageUnextensionedLowercasedRelPath.split(/\//).map((fileOrFolderName) => encodeURIComponent(fileOrFolderName.replace(/^[0-9]+[\-]+/,''))).join('/'))
|
||||
);
|
||||
}
|
||||
|
||||
// Determine unique HTML id
|
||||
// > • This will become the filename of the resulting HTML.
|
||||
let htmlId = (
|
||||
|
1
website/views/layouts/layout-customer.ejs
vendored
1
website/views/layouts/layout-customer.ejs
vendored
@ -176,6 +176,7 @@
|
||||
<script src="/js/pages/account/account-overview.page.js"></script>
|
||||
<script src="/js/pages/account/edit-password.page.js"></script>
|
||||
<script src="/js/pages/account/edit-profile.page.js"></script>
|
||||
<script src="/js/pages/articles/basic-article.page.js"></script>
|
||||
<script src="/js/pages/contact.page.js"></script>
|
||||
<script src="/js/pages/customers/dashboard.page.js"></script>
|
||||
<script src="/js/pages/customers/new-license.page.js"></script>
|
||||
|
1
website/views/layouts/layout-landing.ejs
vendored
1
website/views/layouts/layout-landing.ejs
vendored
@ -178,6 +178,7 @@
|
||||
<script src="/js/pages/account/account-overview.page.js"></script>
|
||||
<script src="/js/pages/account/edit-password.page.js"></script>
|
||||
<script src="/js/pages/account/edit-profile.page.js"></script>
|
||||
<script src="/js/pages/articles/basic-article.page.js"></script>
|
||||
<script src="/js/pages/contact.page.js"></script>
|
||||
<script src="/js/pages/customers/dashboard.page.js"></script>
|
||||
<script src="/js/pages/customers/new-license.page.js"></script>
|
||||
|
21
website/views/layouts/layout.ejs
vendored
21
website/views/layouts/layout.ejs
vendored
@ -117,9 +117,9 @@
|
||||
<div id="mobileNavbarToggleUseCases" purpose="mobile-dropdown" class="collapse align-items-start" data-parent="#mobileDropdowns">
|
||||
<a href="/platform">How it works</a>
|
||||
<span>SUCCESS STORIES</span>
|
||||
<a class="pl-3" href="https://blog.fleetdm.com/fleet-user-stories-e492a08cebfc">Wayfair</a>
|
||||
<a class="pl-3" href="https://blog.fleetdm.com/fleet-user-stories-200c94ca8a10">Schrödinger</a>
|
||||
<a class="pl-3" href="https://blog.fleetdm.com/fleet-user-stories-e5dc300b9e1c">F100 security & networking co.</a>
|
||||
<a class="pl-3" href="/device-management/fleet-user-stories-wayfair">Wayfair</a>
|
||||
<a class="pl-3" href="/device-management/fleet-user-stories-schrodinger">Schrödinger</a>
|
||||
<a class="pl-3" href="/device-management/fleet-user-stories-f100">F100 security & networking co.</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
@ -130,8 +130,8 @@
|
||||
<a href="https://github.com/fleetdm/fleet/releases">Releases</a>
|
||||
<a href="/queries">Query library</a>
|
||||
<span>GUIDES</span>
|
||||
<a class="pl-3" href="https://blog.fleetdm.com/deploying-fleet-on-aws-with-terraform-a58a908e8d40">Deploy on AWS with Terraform</a>
|
||||
<a class="pl-3" href="https://blog.fleetdm.com/deploying-fleet-on-render-2d743aed213f">Deploy on Render</a>
|
||||
<a class="pl-3" href="/guides/deploying-fleet-on-aws-with-terraform">Deploy on AWS with Terraform</a>
|
||||
<a class="pl-3" href="/guides/deploying-fleet-on-render">Deploy on Render</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
@ -158,9 +158,9 @@
|
||||
<a class="dropdown-item mb-1" href="/platform">How it works</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<h6 class="muted dropdown-header">SUCCESS STORIES</h6>
|
||||
<a class="dropdown-item mb-1" href="https://blog.fleetdm.com/fleet-user-stories-e492a08cebfc">Wayfair</a>
|
||||
<a class="dropdown-item mb-1" href="https://blog.fleetdm.com/fleet-user-stories-200c94ca8a10">Schrödinger</a>
|
||||
<a class="dropdown-item" href="https://blog.fleetdm.com/fleet-user-stories-e5dc300b9e1c">F100 security & networking co.</a>
|
||||
<a class="dropdown-item mb-1" href="/device-management/fleet-user-stories-wayfair">Wayfair</a>
|
||||
<a class="dropdown-item mb-1" href="/device-management/fleet-user-stories-schrodinger">Schrödinger</a>
|
||||
<a class="dropdown-item" href="/device-management/fleet-user-stories-f100">F100 security & networking co.</a>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="dropdown-button" class="btn-group">
|
||||
@ -171,8 +171,8 @@
|
||||
<a class="dropdown-item mb-1" href="/queries">Query library</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<span class="muted dropdown-header">GUIDES</span>
|
||||
<a class="dropdown-item mb-1" href="https://blog.fleetdm.com/deploying-fleet-on-aws-with-terraform-a58a908e8d40">Deploy on AWS with Terraform</a>
|
||||
<a class="dropdown-item" href="https://blog.fleetdm.com/deploying-fleet-on-render-2d743aed213f">Deploy on Render</a>
|
||||
<a class="dropdown-item mb-1" href="/guides/deploying-fleet-on-aws-with-terraform">Deploy on AWS with Terraform</a>
|
||||
<a class="dropdown-item" href="/guides/deploying-fleet-on-render">Deploy on Render</a>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="dropdown-button" class="btn-group">
|
||||
@ -334,6 +334,7 @@
|
||||
<script src="/js/pages/account/account-overview.page.js"></script>
|
||||
<script src="/js/pages/account/edit-password.page.js"></script>
|
||||
<script src="/js/pages/account/edit-profile.page.js"></script>
|
||||
<script src="/js/pages/articles/basic-article.page.js"></script>
|
||||
<script src="/js/pages/contact.page.js"></script>
|
||||
<script src="/js/pages/customers/dashboard.page.js"></script>
|
||||
<script src="/js/pages/customers/new-license.page.js"></script>
|
||||
|
31
website/views/pages/articles/basic-article.ejs
vendored
Normal file
31
website/views/pages/articles/basic-article.ejs
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<div id="basic-article" v-cloak>
|
||||
<div style="max-width: 800px;" class="container-fluid px-0">
|
||||
<div purpose="article-title">
|
||||
<h1>{{thisPage.meta.articleTitle}}</h1>
|
||||
</div>
|
||||
<div purpose="article-details" class="d-flex flex-row align-items-center">
|
||||
<span><js-timestamp format="billing" :at="thisPage.meta.publishedOn"></js-timestamp></span>
|
||||
<span class="px-2">|</span>
|
||||
<img style="height: 28px; width: 28px; border-radius: 100%;" alt="The author's GitHub profile picture" :src="'https://github.com/'+thisPage.meta.authorGitHubUsername+'.png?size=200'">
|
||||
<p class="pl-2 font-weight-bold">{{thisPage.meta.authorFullName}}</p>
|
||||
</div>
|
||||
<div purpose="article-content">
|
||||
<%- partial(path.relative(path.dirname(__filename), path.resolve( sails.config.appPath, path.join(sails.config.builtStaticContent.compiledPagePartialsAppPath, thisPage.htmlId)))) %>
|
||||
</div>
|
||||
<hr>
|
||||
<div purpose="bottom-cta" class="d-block">
|
||||
<h3 style="font-size: 24px; line-height: 32px;">Explore Fleet</h3>
|
||||
<p class="my-4">Find out how Fleet can benefit your organization by exploring our docs and community. <br>Want to get up and running quicker? Then try out Fleet locally on your device - you’ll be up and running in minutes.</p>
|
||||
<div class="d-sm-flex">
|
||||
<a href="/get-started" class="d-flex btn btn-primary btn-md justify-content-center align-items-center mr-sm-3" purpose="next-steps-button">
|
||||
Try it out
|
||||
</a>
|
||||
<a href="/slack" target="_blank" class="d-flex btn btn-md btn-outline-secondary justify-content-center align-items-center mt-3 mt-sm-0" purpose="next-steps-button">
|
||||
<img class="pr-3" alt="Slack logo" src="/images/logo-slack-24x24@2x.png"/>
|
||||
Join the Fleet community on Slack
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
Loading…
Reference in New Issue
Block a user