Fleet Sandbox (#5079)

* Add code for the shared infra part of the demo environment

* Checkin

* checkin

* Checkin for pre-provisioner, got terraform working

* Checkin with the pre-deployer working, now blocked by helm chart

* Add interface for helm

* Add some initial code for the JIT Provisioner lambda

Lots of code taken from https://gitlab.com/hmajid2301/articles/-/tree/master/41.%20Create%20a%20webapp%20with%20fizz

* Update helm chart to work with shared infra (#5621)

* Update helm chart to work with shared infra

* Update helm chart README to reflect changes.

* Checkin

* Checkin

* Checkin, Pre-provisioner actually works

* PreProvisioner is now complete

* Make changes to the JIT provisioner based off of actually learning how
to do stuff

* checkin

* Check in, broken currently

* Add all code except provisioning and emailing user

* Checkin

* Checkin, fixed kubernetes

* Checkin

* Forgot a file

* Finish jit provisioner, need to test now

* Checkin, switching to nginx ingress

* Fleets are now actually accessible

* JITProvisioner now returns working fleet instances

* Deprovisioner code done, just need a few bugs fixed

* Fix the deprovisioner so it works now and re-ip

* fixup

* Finished testing the deprovisioner

* Added monitoring and fixed some bugs

* Add stuff for #6548

* fixed per luke's suggestion

* Fix for inactive task definition arns

* move everything to the prod account

* Bump fleet version and fix a couple of bugs

* Fix a couple of bugs

* Lots of security fixes and a few bug fixes

* Rename demo to sandbox to match product's naming

* Revert "Update helm chart to work with shared infra (#5621)"

This reverts commit 610bbd1c00338620f6cc65fe2aff86139551f465.

Co-authored-by: Robert Fairburn <8029478+rfairburn@users.noreply.github.com>
This commit is contained in:
Zachary Winnerman 2022-07-19 14:56:53 -04:00 committed by GitHub
parent f4b20b6ae5
commit 9338fcbcbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 5376 additions and 0 deletions

2
infrastructure/sandbox/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.external_modules
.tfsec

View File

@ -0,0 +1 @@
*.zip

View File

@ -0,0 +1,270 @@
data "aws_iam_policy_document" "sfn-assume-role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["states.amazonaws.com"]
}
}
}
resource "aws_iam_role_policy_attachment" "sfn" {
role = aws_iam_role.sfn.id
policy_arn = aws_iam_policy.sfn.arn
}
resource "aws_iam_policy" "sfn" {
name = "${local.full_name}-sfn"
policy = data.aws_iam_policy_document.sfn.json
}
data "aws_iam_policy_document" "sfn" {
statement {
actions = ["ecs:RunTask"]
resources = [replace(aws_ecs_task_definition.deprovisioner.arn, "/:\\d+$/", ":*"), replace(aws_ecs_task_definition.deprovisioner.arn, "/:\\d+$/", "")]
condition {
test = "ArnLike"
variable = "ecs:cluster"
values = [var.ecs_cluster.arn]
}
}
statement {
actions = ["iam:PassRole"]
resources = ["*"]
condition {
test = "StringLike"
variable = "iam:PassedToService"
values = ["ecs-tasks.amazonaws.com"]
}
}
statement {
actions = ["events:PutTargets", "events:PutRule", "events:DescribeRule"]
resources = ["*"]
}
}
resource "aws_iam_role" "sfn" {
name = "${local.full_name}-sfn"
assume_role_policy = data.aws_iam_policy_document.sfn-assume-role.json
}
resource "aws_iam_role_policy_attachment" "deprovisioner" {
role = aws_iam_role.deprovisioner.id
policy_arn = aws_iam_policy.deprovisioner.arn
}
resource "aws_iam_policy" "deprovisioner" {
name = "${local.full_name}-deprovisioner"
policy = data.aws_iam_policy_document.deprovisioner.json
}
data "aws_iam_policy_document" "deprovisioner" {
statement {
actions = [
"dynamodb:List*",
"dynamodb:DescribeReservedCapacity*",
"dynamodb:DescribeLimits",
"dynamodb:DescribeTimeToLive"
]
resources = ["*"]
}
statement {
actions = [
"dynamodb:BatchGet*",
"dynamodb:DescribeStream",
"dynamodb:DescribeTable",
"dynamodb:Get*",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchWrite*",
"dynamodb:CreateTable",
"dynamodb:Delete*",
"dynamodb:Update*",
"dynamodb:PutItem"
]
resources = [var.dynamodb_table.arn]
}
statement {
actions = [ #tfsec:ignore:aws-iam-no-policy-wildcards
"kms:Encrypt*",
"kms:Decrypt*",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Describe*"
]
resources = [aws_kms_key.ecr.arn, var.kms_key.arn]
}
statement {
actions = ["*"]
resources = ["*"]
}
}
data "aws_iam_policy_document" "deprovisioner-assume-role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "deprovisioner" {
name = "${local.full_name}-deprovisioner"
assume_role_policy = data.aws_iam_policy_document.deprovisioner-assume-role.json
}
resource "aws_ecs_task_definition" "deprovisioner" {
family = "${local.full_name}-deprovisioner"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.deprovisioner.arn
task_role_arn = aws_iam_role.deprovisioner.arn
cpu = 1024
memory = 4096
container_definitions = jsonencode(
[
{
name = "${var.prefix}-deprovisioner"
image = docker_registry_image.deprovisioner.name
mountPoints = []
volumesFrom = []
essential = true
networkMode = "awsvpc"
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.main.name
awslogs-region = data.aws_region.current.name
awslogs-stream-prefix = "${local.full_name}-deprovisioner"
}
},
environment = concat([
{
name = "TF_VAR_mysql_secret"
value = var.mysql_secret.id
},
{
name = "TF_VAR_eks_cluster"
value = var.eks_cluster.eks_cluster_id
},
])
}
])
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "deprovisioner" {
name = "${local.full_name}-deprovisioner"
description = "security group for ${local.full_name}-deprovisioner"
vpc_id = var.vpc.vpc_id
egress {
description = "egress to all"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
resource "aws_sfn_state_machine" "main" {
name = var.prefix
role_arn = aws_iam_role.sfn.arn
definition = <<EOF
{
"Comment": "Controls the lifecycle of a Fleet demo environment",
"StartAt": "Wait",
"States": {
"Wait": {
"Type": "Wait",
"SecondsPath": "$.waitTime",
"Next": "Deprovisioner"
},
"Deprovisioner": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": ${jsonencode(var.vpc.private_subnets)},
"SecurityGroups": ["${aws_security_group.deprovisioner.id}"],
"AssignPublicIp": "DISABLED"
}
},
"Cluster": "${var.ecs_cluster.arn}",
"TaskDefinition": "${replace(aws_ecs_task_definition.deprovisioner.arn, "/:\\d+$/", "")}",
"Overrides": {
"ContainerOverrides": [
{
"Name": "${var.prefix}-deprovisioner",
"Environment": [
{
"Name": "INSTANCE_ID",
"Value.$": "$.instanceID"
}
]
}
]
}
},
"End": true
}
}
}
EOF
}
output "deprovisioner" {
value = aws_sfn_state_machine.main
}
resource "random_uuid" "deprovisioner" {
keepers = {
lambda = data.archive_file.deprovisioner.output_sha
}
}
resource "local_file" "backend-config" {
content = templatefile("${path.module}/deprovisioner/backend-template.conf",
{
remote_state = var.remote_state
})
filename = "${path.module}/deprovisioner/deploy_terraform/backend.conf"
}
data "archive_file" "deprovisioner" {
type = "zip"
output_path = "${path.module}/.deprovisioner.zip"
source_dir = "${path.module}/deprovisioner"
}
resource "docker_registry_image" "deprovisioner" {
name = "${aws_ecr_repository.main.repository_url}:${data.git_repository.main.branch}-${random_uuid.deprovisioner.result}"
keep_remotely = true
build {
context = "${path.module}/deprovisioner/"
pull_parent = true
}
depends_on = [
local_file.backend-config
]
}
output "deprovisioner_role" {
value = aws_iam_role.deprovisioner
}

View File

@ -0,0 +1,18 @@
FROM golang:1.18-alpine AS builder
RUN apk update && apk add --no-cache git curl openssl unzip
WORKDIR /build
COPY . .
RUN go get -d -v
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-extldflags '-static'"
RUN curl https://releases.hashicorp.com/terraform/1.1.8/terraform_1.1.8_linux_amd64.zip > terraform.zip
RUN unzip terraform.zip
RUN rm terraform.zip
RUN chmod 644 $(find . -type f)
RUN chmod 755 $(find . -type d)
RUN chmod 655 lambda terraform
#FROM scratch
#COPY --from=builder /build/lambda /build/terraform /
#COPY --from=builder /build/deploy_terraform /deploy_terraform
#COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/build/lambda"]

View File

@ -0,0 +1,6 @@
bucket = "${remote_state.state_bucket.id}"
key = "terraform.tfstate" # This should be set to account_alias/unique_key/terraform.tfstate
region = "us-east-2"
encrypt = true
kms_key_id = "${remote_state.kms_key.id}"
dynamodb_table = "${remote_state.dynamodb_table.id}"

View File

@ -0,0 +1 @@
backend.conf

View File

@ -0,0 +1,50 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.10.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1.2"
}
mysql = {
source = "petoju/mysql"
version = "3.0.12"
}
helm = {
source = "hashicorp/helm"
version = "2.5.1"
}
}
backend "s3" {}
}
provider "helm" {
kubernetes {
host = data.aws_eks_cluster.cluster.endpoint
token = data.aws_eks_cluster_auth.cluster.token
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
}
}
data "aws_eks_cluster" "cluster" {
name = var.eks_cluster
}
data "aws_eks_cluster_auth" "cluster" {
name = var.eks_cluster
}
provider "mysql" {
endpoint = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["endpoint"]
username = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["username"]
password = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["password"]
}
variable "mysql_secret" {}
variable "eks_cluster" {}
data "aws_secretsmanager_secret_version" "mysql" {
secret_id = var.mysql_secret
}

View File

@ -0,0 +1,16 @@
module github.com/fleetdm/fleet/infrastructure/demo/PreProvisioner/lambda
go 1.18
require (
github.com/aws/aws-lambda-go v1.29.0
github.com/aws/aws-sdk-go v1.43.37
github.com/google/uuid v1.3.0
github.com/jessevdk/go-flags v1.5.0
)
require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)

View File

@ -0,0 +1,33 @@
github.com/aws/aws-lambda-go v1.29.0 h1:u+sfZkvNBUgt0ZkO8Q/jOMBV22DqMDMbZu04oomM2no=
github.com/aws/aws-lambda-go v1.29.0/go.mod h1:aakqVz9vDHhtbt0U2zegh/z9SI2+rJ+yRREZYNQLmWY=
github.com/aws/aws-sdk-go v1.43.37 h1:kyZ7UjaPZaCik+asF33UFOOYSwr9liDRr/UM/vuw8yY=
github.com/aws/aws-sdk-go v1.43.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=

View File

@ -0,0 +1,92 @@
package main
import (
"context"
"github.com/aws/aws-lambda-go/lambda"
flags "github.com/jessevdk/go-flags"
"log"
"os"
"os/exec"
)
type OptionsStruct struct {
LambdaExecutionEnv string `long:"lambda-execution-environment" env:"AWS_EXECUTION_ENV"`
InstanceID string `long:"instance-id" env:"INSTANCE_ID" required:"true"`
}
var options = OptionsStruct{}
type LifecycleRecord struct {
ID string
State string
}
func runCmd(args []string) error {
cmd := exec.Cmd{
Path: "/build/terraform",
Dir: "/build/deploy_terraform",
Stdout: os.Stdout,
Stderr: os.Stderr,
Args: append([]string{"/build/terraform"}, args...),
}
log.Printf("%+v\n", cmd)
return cmd.Run()
}
func initTerraform() error {
err := runCmd([]string{
"init",
"-backend-config=backend.conf",
})
return err
}
func runTerraform(workspace string) error {
err := runCmd([]string{
"workspace",
"select",
workspace,
})
if err != nil {
return err
}
err = runCmd([]string{
"destroy",
"-auto-approve",
"-no-color",
})
return err
}
func handler(ctx context.Context, name NullEvent) error {
if err := initTerraform(); err != nil {
return err
}
if err := runTerraform(options.InstanceID); err != nil {
return err
}
return nil
}
type NullEvent struct{}
func main() {
var err error
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Get config from environment
parser := flags.NewParser(&options, flags.Default)
if _, err = parser.Parse(); err != nil {
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
return
} else {
log.Fatal(err)
}
}
if options.LambdaExecutionEnv == "AWS_Lambda_go1.x" {
lambda.Start(handler)
} else {
if err = handler(context.Background(), NullEvent{}); err != nil {
log.Fatal(err)
}
}
}

View File

@ -0,0 +1,174 @@
resource "aws_lb_listener_rule" "jitprovisioner" {
listener_arn = var.alb_listener.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.jitprovisioner.arn
}
condition {
host_header {
values = [var.base_domain]
}
}
}
resource "aws_lb_target_group_attachment" "jitprovisioner" {
target_group_arn = aws_lb_target_group.jitprovisioner.arn
target_id = aws_lambda_function.jitprovisioner.arn
depends_on = [aws_lambda_permission.jitprovisioner]
}
resource "aws_lambda_permission" "jitprovisioner" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.jitprovisioner.arn
principal = "elasticloadbalancing.amazonaws.com"
source_arn = aws_lb_target_group.jitprovisioner.arn
}
resource "aws_lb_target_group" "jitprovisioner" {
name = "${local.full_name}-lambda"
target_type = "lambda"
lambda_multi_value_headers_enabled = true
}
data "aws_iam_policy_document" "lambda_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
resource "aws_iam_role" "jitprovisioner" {
name = "${var.prefix}-lambda"
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}
resource "aws_iam_role_policy_attachment" "jitprovisioner-ecr" {
role = aws_iam_role.jitprovisioner.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
}
resource "aws_iam_role_policy_attachment" "jitprovisioner-vpc" {
role = aws_iam_role.jitprovisioner.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
resource "aws_iam_policy" "jitprovisioner" {
name = "${var.prefix}-jitprovisioner"
policy = data.aws_iam_policy_document.jitprovisioner.json
}
data "aws_iam_policy_document" "jitprovisioner" {
statement {
actions = [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:ConditionCheckItem",
"dynamodb:PutItem",
"dynamodb:DescribeTable",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem",
]
resources = [var.dynamodb_table.arn, "${var.dynamodb_table.arn}/*"]
}
statement {
actions = [ #tfsec:ignore:aws-iam-no-policy-wildcards
"kms:Encrypt*",
"kms:Decrypt*",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Describe*"
]
resources = [var.kms_key.arn]
}
statement {
actions = ["states:StartExecution"]
resources = [aws_sfn_state_machine.main.arn]
}
statement {
actions = ["states:DescribeExecution"]
resources = ["*"]
}
}
resource "aws_iam_role_policy_attachment" "jitprovisioner" {
role = aws_iam_role.jitprovisioner.name
policy_arn = aws_iam_policy.jitprovisioner.arn
}
resource "aws_lambda_function" "jitprovisioner" {
# If the file is not in the current working directory you will need to include a
# path.module in the filename.
image_uri = docker_registry_image.jitprovisioner.name
package_type = "Image"
function_name = "${var.prefix}-lambda"
role = aws_iam_role.jitprovisioner.arn
reserved_concurrent_executions = -1
kms_key_arn = var.kms_key.arn
timeout = 10
memory_size = 512
vpc_config {
security_group_ids = [aws_security_group.jitprovisioner.id]
subnet_ids = var.vpc.private_subnets
}
tracing_config {
mode = "Active"
}
environment {
variables = {
DYNAMODB_LIFECYCLE_TABLE = var.dynamodb_table.id
LIFECYCLE_SFN = aws_sfn_state_machine.main.arn
FLEET_BASE_URL = "${var.base_domain}"
}
}
}
output "jitprovisioner" {
value = aws_lambda_function.jitprovisioner
}
resource "random_uuid" "jitprovisioner" {
keepers = {
lambda = data.archive_file.jitprovisioner.output_sha
}
}
data "archive_file" "jitprovisioner" {
type = "zip"
output_path = "${path.module}/.jitprovisioner.zip"
source_dir = "${path.module}/lambda"
}
resource "docker_registry_image" "jitprovisioner" {
name = "${aws_ecr_repository.main.repository_url}:${data.git_repository.main.branch}-${random_uuid.jitprovisioner.result}"
keep_remotely = true
build {
context = "${path.module}/lambda/"
pull_parent = true
}
}
resource "aws_security_group" "jitprovisioner" {
name = local.full_name
vpc_id = var.vpc.vpc_id
description = local.full_name
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}

View File

@ -0,0 +1 @@
lambda

View File

@ -0,0 +1,11 @@
FROM golang:1.18-alpine AS builder
WORKDIR /build
COPY . .
RUN go get -d -v
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-extldflags '-static'"
#FROM scratch
#COPY --from=builder /build/lambda /build/terraform /
#COPY --from=builder /build/deploy_terraform /deploy_terraform
#COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/build/lambda"]

View File

@ -0,0 +1,116 @@
module github.com/fleetdm/fleet/infrastructure/demo/JITProvisioner/lambda
go 1.18
require (
github.com/akrylysov/algnhsa v0.12.1
github.com/aws/aws-sdk-go v1.44.25
github.com/fleetdm/fleet/v4 v4.1.0
github.com/gin-gonic/gin v1.7.7
github.com/jessevdk/go-flags v1.5.0
github.com/loopfz/gadgeto v0.11.2
github.com/wI2L/fizz v0.20.0
go.elastic.co/apm/module/apmgin/v2 v2.1.0
go.elastic.co/apm/v2 v2.1.0
)
require (
cloud.google.com/go v0.81.0 // indirect
cloud.google.com/go/pubsub v1.5.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/WatchBeam/clock v0.0.0-20170901150240-b08e6b4da7ea // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-lambda-go v1.31.1 // indirect
github.com/beevik/etree v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elastic/go-licenser v0.4.0 // indirect
github.com/elastic/go-sysinfo v1.7.1 // indirect
github.com/elastic/go-windows v1.0.1 // indirect
github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-kit/kit v0.9.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gomodule/redigo v1.8.4 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/igm/sockjs-go/v3 v3.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jcchavezs/porto v0.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/jstemmer/go-junit-report v0.9.1 // indirect
github.com/kolide/kit v0.0.0-20180421083548-36eb8dc43916 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mna/redisc v1.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/open-policy-agent/opa v0.24.0 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v0.9.3 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.4.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/russellhaering/goxmldsig v1.1.0 // indirect
github.com/santhosh-tekuri/jsonschema v1.2.4 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v0.0.6 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.8.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/throttled/throttled/v2 v2.8.0 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect
go.elastic.co/apm/module/apmhttp/v2 v2.1.0 // indirect
go.elastic.co/fastjson v1.1.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.44.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
google.golang.org/grpc v1.38.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/guregu/null.v3 v3.4.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
howett.net/plist v1.0.0 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,259 @@
package main
import (
"github.com/akrylysov/algnhsa"
//"github.com/gin-contrib/cors" TODO: use cors
"github.com/gin-gonic/gin"
flags "github.com/jessevdk/go-flags"
//"github.com/juju/errors"
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/aws/aws-sdk-go/service/sfn"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/loopfz/gadgeto/tonic"
"github.com/wI2L/fizz"
"github.com/wI2L/fizz/openapi"
"go.elastic.co/apm/module/apmgin/v2"
_ "go.elastic.co/apm/v2"
"log"
"math/rand"
"strings"
"time"
"errors"
)
type OptionsStruct struct {
LambdaExecutionEnv string `long:"lambda-execution-environment" env:"AWS_EXECUTION_ENV"`
LifecycleTable string `long:"dynamodb-lifecycle-table" env:"DYNAMODB_LIFECYCLE_TABLE" required:"true"`
LifecycleSFN string `long:"lifecycle-sfn" env:"LIFECYCLE_SFN" required:"true"`
FleetBaseURL string `long:"fleet-base-url" env:"FLEET_BASE_URL" required:"true"`
}
var options = OptionsStruct{}
type LifecycleRecord struct {
ID string
State string
RedisDB int `dynamodbav:"redis_db"`
}
func getExpiry(id string) (ret time.Time, err error) {
var execArn arn.ARN
var exec *sfn.DescribeExecutionOutput
var input struct {
WaitTime int `json:"waitTime"`
}
execArn, err = arn.Parse(options.LifecycleSFN)
if err != nil {
return
}
execArn.Resource = fmt.Sprintf("execution:%s:%s", strings.Split(execArn.Resource, ":")[1], id)
exec, err = sfn.New(session.New()).DescribeExecution(&sfn.DescribeExecutionInput{
ExecutionArn: aws.String(execArn.String()),
})
if err != nil {
return
}
if err = json.Unmarshal([]byte(*exec.Input), &input); err != nil {
return
}
var dur time.Duration
if dur, err = time.ParseDuration(fmt.Sprintf("%ds", input.WaitTime)); err != nil {
return
}
ret = exec.StartDate.Add(dur)
return
}
func claimFleet(fleet LifecycleRecord, svc *dynamodb.DynamoDB) (err error) {
log.Printf("Claiming instance: %+v", fleet)
// Perform a conditional update to claim the item
input := &dynamodb.UpdateItemInput{
ConditionExpression: aws.String("#fleet_state = :v1"),
TableName: aws.String(options.LifecycleTable),
Key: map[string]*dynamodb.AttributeValue{
"ID": {
S: aws.String(fleet.ID),
},
},
UpdateExpression: aws.String("set #fleet_state = :v2"),
ExpressionAttributeNames: map[string]*string{"#fleet_state": aws.String("State")},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":v1": {
S: aws.String("unclaimed"),
},
":v2": {
S: aws.String("claimed"),
},
},
}
if _, err = svc.UpdateItem(input); err != nil {
return
}
return
}
func getFleetInstance() (ret LifecycleRecord, err error) {
log.Print("Getting fleet instance")
svc := dynamodb.New(session.New())
// Loop until we get one
for {
input := &dynamodb.QueryInput{
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":v1": {
S: aws.String("unclaimed"),
},
},
KeyConditionExpression: aws.String("#fleet_state = :v1"),
TableName: aws.String(options.LifecycleTable),
ExpressionAttributeNames: map[string]*string{"#fleet_state": aws.String("State")},
IndexName: aws.String("FleetState"),
}
var result *dynamodb.QueryOutput
if result, err = svc.Query(input); err != nil {
return
}
recs := []LifecycleRecord{}
if err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &recs); err != nil {
return
}
ret = recs[rand.Intn(len(recs))]
if err = claimFleet(ret, svc); err != nil {
log.Print(err)
continue
}
return
}
}
func triggerSFN(id, expiry string) (err error) {
var endTime time.Time
log.Print("Triggering state machine")
if endTime, err = time.Parse(time.RFC3339, expiry); err != nil {
return
}
if int(endTime.Sub(time.Now()).Seconds()) < 0 {
return errors.New("Expiry time is in the past")
}
sfnInStr, err := json.Marshal(struct {
InstanceID string `json:"instanceID"`
WaitTime int `json:"waitTime"`
}{
InstanceID: id,
WaitTime: int(endTime.Sub(time.Now()).Seconds()),
})
if err != nil {
return
}
sfnIn := sfn.StartExecutionInput{
Input: aws.String(string(sfnInStr)),
Name: aws.String(id),
StateMachineArn: aws.String(options.LifecycleSFN),
}
_, err = sfn.New(session.New()).StartExecution(&sfnIn)
return
}
type HealthInput struct{}
type HealthOutput struct {
Message string `json:"message" description:"The status of the API." example:"The API is healthy"`
}
func Health(c *gin.Context, in *HealthInput) (ret *HealthOutput, err error) {
ret = &HealthOutput{
Message: "Healthy",
}
return
}
type NewFleetInput struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required"`
SandboxExpiration string `json:"sandbox_expiration" validate:"required"`
Password string `json:"password" validate:"required"`
}
type NewFleetOutput struct {
URL string
}
func NewFleet(c *gin.Context, in *NewFleetInput) (ret *NewFleetOutput, err error) {
ret = &NewFleetOutput{}
fleet, err := getFleetInstance()
if err != nil {
log.Print(err)
return
}
log.Print("Creating fleet client")
ret.URL = fmt.Sprintf("https://%s.%s", fleet.ID, options.FleetBaseURL)
log.Print(ret.URL)
client, err := service.NewClient(ret.URL, true, "", "")
if err != nil {
log.Print(err)
return
}
log.Print("Creating admin user")
if _, err = client.Setup(in.Email, in.Name, in.Password, fleet.ID); err != nil {
log.Print(err)
return
}
if err = triggerSFN(fleet.ID, in.SandboxExpiration); err != nil {
log.Print(err)
return
}
return
}
type ExpiryInput struct {
ID string `query:"id" validate:"required"`
}
type ExpiryOutput struct {
Timestamp string `json:"timestamp"`
}
func GetExpiry(c *gin.Context, in *ExpiryInput) (ret *ExpiryOutput, err error) {
ret = &ExpiryOutput{}
var expiry time.Time
if expiry, err = getExpiry(in.ID); err != nil {
return
}
ret.Timestamp = expiry.Format(time.RFC3339)
return
}
func main() {
rand.Seed(time.Now().Unix())
var err error
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Get config from environment
parser := flags.NewParser(&options, flags.Default)
if _, err = parser.Parse(); err != nil {
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
return
} else {
log.Fatal(err)
}
}
r := gin.Default()
r.Use(apmgin.Middleware(r))
f := fizz.NewFromEngine(r)
infos := &openapi.Info{
Title: "Fleet Demo JITProvisioner",
Description: "Provisions new Fleet instances upon request",
Version: "1.0.0",
}
f.GET("/openapi.json", nil, f.OpenAPI(infos, "json"))
f.GET("/health", nil, tonic.Handler(Health, 200))
f.POST("/new", nil, tonic.Handler(NewFleet, 200))
f.GET("/expires", nil, tonic.Handler(GetExpiry, 200))
algnhsa.ListenAndServe(r, nil)
}

View File

@ -0,0 +1,48 @@
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
git = {
source = "paultyng/git"
version = "~> 0.1.0"
}
}
}
data "aws_region" "current" {}
locals {
name = "jit"
full_name = "${var.prefix}-${local.name}"
}
resource "aws_cloudwatch_log_group" "main" {
name = local.full_name
kms_key_id = var.kms_key.arn
retention_in_days = 30
}
resource "aws_kms_key" "ecr" {
deletion_window_in_days = 10
enable_key_rotation = true
}
resource "aws_ecr_repository" "main" {
name = var.prefix
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
}
data "git_repository" "main" {
path = "${path.module}/../../../"
}

View File

@ -0,0 +1,11 @@
variable "prefix" {}
variable "dynamodb_table" {}
variable "vpc" {}
variable "remote_state" {}
variable "mysql_secret" {}
variable "eks_cluster" {}
variable "redis_cluster" {}
variable "alb_listener" {}
variable "base_domain" {}
variable "ecs_cluster" {}
variable "kms_key" {}

View File

@ -0,0 +1 @@
.lambda.zip

View File

@ -0,0 +1,11 @@
FROM golang:1.18-alpine AS builder
WORKDIR /build
COPY . .
RUN go get -d -v
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-extldflags '-static'"
#FROM scratch
#COPY --from=builder /build/lambda /build/terraform /
#COPY --from=builder /build/deploy_terraform /deploy_terraform
#COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/build/lambda"]

View File

@ -0,0 +1,14 @@
module github.com/fleetdm/fleet/infrastructure/demo/Monitoring/lambda
go 1.18
require (
github.com/aws/aws-lambda-go v1.32.1
github.com/aws/aws-sdk-go v1.44.50
github.com/jessevdk/go-flags v1.5.0
)
require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
)

View File

@ -0,0 +1,29 @@
github.com/aws/aws-lambda-go v1.32.1 h1:ls0FU8Mt7ayJszb945zFkUfzxhkQTli8mpJstVcDtCY=
github.com/aws/aws-lambda-go v1.32.1/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM=
github.com/aws/aws-sdk-go v1.44.50 h1:dg6nbI+4734bTj1Q6FCQqiIiE+lb8HpGQJqZEvZeMrY=
github.com/aws/aws-sdk-go v1.44.50/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -0,0 +1,114 @@
package main
import (
"context"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
flags "github.com/jessevdk/go-flags"
"log"
)
type OptionsStruct struct {
LambdaExecutionEnv string `long:"lambda-execution-environment" env:"AWS_EXECUTION_ENV"`
LifecycleTable string `long:"dynamodb-lifecycle-table" env:"DYNAMODB_LIFECYCLE_TABLE" required:"true"`
}
var options = OptionsStruct{}
type LifecycleRecord struct {
State string
}
func getInstancesCount() (int64, int64, error) {
log.Print("getInstancesCount")
svc := dynamodb.New(session.New())
// Example iterating over at most 3 pages of a Scan operation.
var count, unclaimedCount int64
err := svc.ScanPages(
&dynamodb.ScanInput{
TableName: aws.String(options.LifecycleTable),
},
func(page *dynamodb.ScanOutput, lastPage bool) bool {
count += *page.Count
recs := []LifecycleRecord{}
if err := dynamodbattribute.UnmarshalListOfMaps(page.Items, &recs); err != nil {
log.Print(err)
return false
}
for _, i := range recs {
if i.State == "unclaimed" {
unclaimedCount++
}
}
return true
})
if err != nil {
return 0, 0, err
}
return count, unclaimedCount, nil
}
type NullEvent struct{}
func handler(ctx context.Context, name NullEvent) error {
totalCount, unclaimedCount, err := getInstancesCount()
svc := cloudwatch.New(session.New())
log.Printf("Publishing %d, %d", totalCount, unclaimedCount)
_, err = svc.PutMetricData(&cloudwatch.PutMetricDataInput{
Namespace: aws.String("Fleet/sandbox"),
MetricData: []*cloudwatch.MetricDatum{
&cloudwatch.MetricDatum{
Dimensions: []*cloudwatch.Dimension{
&cloudwatch.Dimension{
Name: aws.String("Type"),
Value: aws.String("totalCount"),
},
},
MetricName: aws.String("instances"),
Value: aws.Float64(float64(totalCount)),
Unit: aws.String(cloudwatch.StandardUnitCount),
},
&cloudwatch.MetricDatum{
Dimensions: []*cloudwatch.Dimension{
&cloudwatch.Dimension{
Name: aws.String("Type"),
Value: aws.String("unclaimedCount"),
},
},
MetricName: aws.String("instances"),
Value: aws.Float64(float64(unclaimedCount)),
Unit: aws.String(cloudwatch.StandardUnitCount),
},
},
})
if err != nil {
log.Print(err)
return err
}
return nil
}
func main() {
var err error
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Get config from environment
parser := flags.NewParser(&options, flags.Default)
if _, err = parser.Parse(); err != nil {
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
return
} else {
log.Fatal(err)
}
}
if options.LambdaExecutionEnv == "AWS_Lambda_go1.x" {
lambda.Start(handler)
} else {
if err = handler(context.Background(), NullEvent{}); err != nil {
log.Fatal(err)
}
}
}

View File

@ -0,0 +1,279 @@
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
git = {
source = "paultyng/git"
version = "~> 0.1.0"
}
}
}
data "aws_region" "current" {}
locals {
full_name = "${var.prefix}-monitoring"
}
module "notify_slack" {
source = "terraform-aws-modules/notify-slack/aws"
version = "~> 4.0"
sns_topic_name = var.prefix
slack_webhook_url = var.slack_webhook
slack_channel = "#g-infrastructure"
slack_username = "monitoring"
}
data "aws_iam_policy_document" "lifecycle-lambda-assume-role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
resource "aws_iam_role_policy_attachment" "lifecycle-lambda-lambda" {
role = aws_iam_role.lifecycle-lambda.id
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "lifecycle-lambda" {
role = aws_iam_role.lifecycle-lambda.id
policy_arn = aws_iam_policy.lifecycle-lambda.arn
}
resource "aws_iam_policy" "lifecycle-lambda" {
name = "${local.full_name}-lifecycle-lambda"
policy = data.aws_iam_policy_document.lifecycle-lambda.json
}
data "aws_iam_policy_document" "lifecycle-lambda" {
statement {
actions = [
"dynamodb:List*",
"dynamodb:DescribeReservedCapacity*",
"dynamodb:DescribeLimits",
"dynamodb:DescribeTimeToLive"
]
resources = ["*"]
}
statement {
actions = [
"dynamodb:BatchGet*",
"dynamodb:DescribeStream",
"dynamodb:DescribeTable",
"dynamodb:Get*",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchWrite*",
"dynamodb:CreateTable",
"dynamodb:Delete*",
"dynamodb:Update*",
"dynamodb:PutItem"
]
resources = [var.dynamodb_table.arn]
}
statement {
actions = [ #tfsec:ignore:aws-iam-no-policy-wildcards
"kms:Encrypt*",
"kms:Decrypt*",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Describe*"
]
resources = [aws_kms_key.ecr.arn]
}
statement {
actions = ["cloudwatch:PutMetricData"]
resources = ["*"]
}
}
resource "aws_iam_role" "lifecycle-lambda" {
name = local.full_name
assume_role_policy = data.aws_iam_policy_document.lifecycle-lambda-assume-role.json
}
resource "aws_kms_key" "ecr" {
deletion_window_in_days = 10
enable_key_rotation = true
}
resource "aws_ecr_repository" "main" {
name = local.full_name
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
}
resource "random_uuid" "lifecycle-lambda" {
keepers = {
lambda = data.archive_file.lifecycle-lambda.output_sha
}
}
data "archive_file" "lifecycle-lambda" {
type = "zip"
output_path = "${path.module}/.lambda.zip"
source_dir = "${path.module}/lambda"
}
data "git_repository" "main" {
path = "${path.module}/../../../"
}
resource "docker_registry_image" "lifecycle-lambda" {
name = "${aws_ecr_repository.main.repository_url}:${data.git_repository.main.branch}-${random_uuid.lifecycle-lambda.result}"
keep_remotely = true
build {
context = "${path.module}/lambda/"
pull_parent = true
}
}
resource "aws_cloudwatch_event_rule" "lifecycle" {
name_prefix = local.full_name
schedule_expression = "rate(5 minutes)"
is_enabled = true
}
resource "aws_cloudwatch_event_target" "lifecycle" {
rule = aws_cloudwatch_event_rule.lifecycle.name
arn = aws_lambda_function.lifecycle.arn
}
resource "aws_lambda_function" "lifecycle" {
# If the file is not in the current working directory you will need to include a
# path.module in the filename.
image_uri = docker_registry_image.lifecycle-lambda.name
package_type = "Image"
function_name = "${local.full_name}-lifecycle-lambda"
kms_key_arn = var.kms_key.arn
role = aws_iam_role.lifecycle-lambda.arn
reserved_concurrent_executions = -1
timeout = 10
memory_size = 512
tracing_config {
mode = "Active"
}
environment {
variables = {
DYNAMODB_LIFECYCLE_TABLE = var.dynamodb_table.id
}
}
}
resource "aws_lambda_permission" "lifecycle" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lifecycle.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.lifecycle.arn
}
resource "aws_cloudwatch_metric_alarm" "totalInstances" {
alarm_name = "${var.prefix}-lifecycle-totalCount"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "instances"
namespace = "Fleet/sandbox"
period = "900"
statistic = "Average"
threshold = "90"
alarm_actions = [module.notify_slack.slack_topic_arn]
ok_actions = [module.notify_slack.slack_topic_arn]
treat_missing_data = "breaching"
datapoints_to_alarm = 1
dimensions = {
Type = "totalCount"
}
}
resource "aws_cloudwatch_metric_alarm" "unclaimed" {
alarm_name = "${var.prefix}-lifecycle-unclaimed"
comparison_operator = "LessThanThreshold"
evaluation_periods = "1"
metric_name = "instances"
namespace = "Fleet/sandbox"
period = "900"
statistic = "Average"
threshold = "10"
alarm_actions = [module.notify_slack.slack_topic_arn]
ok_actions = [module.notify_slack.slack_topic_arn]
treat_missing_data = "breaching"
datapoints_to_alarm = 1
dimensions = {
Type = "unclaimedCount"
}
}
resource "aws_cloudwatch_metric_alarm" "lb" {
for_each = toset(["HTTPCode_ELB_5XX_Count", "HTTPCode_Target_5XX_Count"])
alarm_name = "${var.prefix}-lb-${each.key}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = each.key
namespace = "AWS/ApplicationELB"
period = "120"
statistic = "Sum"
threshold = "0"
alarm_actions = [module.notify_slack.slack_topic_arn]
ok_actions = [module.notify_slack.slack_topic_arn]
treat_missing_data = "notBreaching"
dimensions = {
LoadBalancer = var.lb.arn_suffix
}
}
resource "aws_cloudwatch_metric_alarm" "jitprovisioner" {
for_each = toset(["Errors"])
alarm_name = "${var.prefix}-jitprovisioner-${each.key}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = each.key
namespace = "AWS/Lambda"
period = "120"
statistic = "Sum"
threshold = "0"
alarm_actions = [module.notify_slack.slack_topic_arn]
ok_actions = [module.notify_slack.slack_topic_arn]
treat_missing_data = "notBreaching"
dimensions = {
FunctionName = var.jitprovisioner.id
}
}
resource "aws_cloudwatch_metric_alarm" "deprovisioner" {
for_each = toset(["ExecutionsFailed"])
alarm_name = "${var.prefix}-deprovisioner-${each.key}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = each.key
namespace = "AWS/States"
period = "120"
statistic = "Sum"
threshold = "0"
alarm_actions = [module.notify_slack.slack_topic_arn]
ok_actions = [module.notify_slack.slack_topic_arn]
treat_missing_data = "notBreaching"
dimensions = {
StateMachineArn = var.deprovisioner.arn
}
}

View File

@ -0,0 +1,7 @@
variable "prefix" {}
variable "lb" {}
variable "jitprovisioner" {}
variable "deprovisioner" {}
variable "slack_webhook" {}
variable "dynamodb_table" {}
variable "kms_key" {}

View File

@ -0,0 +1 @@
.lambda.zip

View File

@ -0,0 +1,2 @@
lambda
deploy_terraform/backend.conf

View File

@ -0,0 +1,18 @@
FROM golang:1.18-alpine AS builder
RUN apk update && apk add --no-cache git curl openssl unzip
WORKDIR /build
COPY . .
RUN go get -d -v
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-extldflags '-static'"
RUN curl https://releases.hashicorp.com/terraform/1.1.8/terraform_1.1.8_linux_amd64.zip > terraform.zip
RUN unzip terraform.zip
RUN rm terraform.zip
RUN chmod 644 $(find . -type f)
RUN chmod 755 $(find . -type d)
RUN chmod 655 lambda terraform
#FROM scratch
#COPY --from=builder /build/lambda /build/terraform /
#COPY --from=builder /build/deploy_terraform /deploy_terraform
#COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/build/lambda"]

View File

@ -0,0 +1,6 @@
bucket = "${remote_state.state_bucket.id}"
key = "terraform.tfstate" # This should be set to account_alias/unique_key/terraform.tfstate
region = "us-east-2"
encrypt = true
kms_key_id = "${remote_state.kms_key.id}"
dynamodb_table = "${remote_state.dynamodb_table.id}"

View File

@ -0,0 +1,11 @@
apiVersion: v1
description: A Helm chart for Fleet
name: fleet
keywords:
- fleet
- osquery
version: v4.12.0
home: https://github.com/fleetdm/fleet
sources:
- https://github.com/fleetdm/fleet.git
appVersion: v4.12.0

View File

@ -0,0 +1,56 @@
## Fleet Helm Chart
This directory contains a Helm Chart that makes deploying Fleet on Kubernetes easy.
### Usage
#### 1. Create namespace
This Helm chart optionally provisions a Kubernetes namespace. Alternatively, you can add one with `kubectl create namespace <name>` or by creating a YAML file containing the namespace and applying it to your cluster.
#### 2. Create the necessary secrets
This Helm chart optionally creates Kubernetes `Secret`s for MySQL and Redis necessary for Fleet to operate. If you manually create them instead, at a minimum, secrets for the MySQL password must be created. For example, if you are deploying into a namespace called `fleet`:
```yaml
---
kind: Secret
apiVersion: v1
metadata:
name: mysql
namespace: fleet
stringData:
mysql-password: this-is-a-bad-password
```
If you use Fleet's TLS capabilities, TLS connections to the MySQL server, or AWS access secret keys, additional secrets and keys are needed. The name of each `Secret` must match the value of `secretName` for each section in the `values.yaml` file and the key of each secret must match the related key value from the values file. For example, to configure Fleet's TLS, you would use a Secret like the one below.
```yaml
kind: Secret
apiVersion: v1
metadata:
name: fleet
namespace: fleet
stringData:
server.cert: |
your-pem-encoded-certificate-here
server.key: |
your-pem-encoded-key-here
```
Once all of your secrets are configured, use `kubectl apply -f <secret_file_name.yaml> --namespace <your_namespace>` to create them in the cluster.
#### 3. Further Configuration
To configure how Fleet runs, such as specifying the number of Fleet instances to deploy or changing the logger plugin for Fleet, edit the `values.yaml` file to your desired settings.
#### 4. Deploy Fleet
Once the secrets have been created and you have updated the values to match your required configuration, you can deploy with the following command.
```sh
helm upgrade --install fleet fleet \
--namespace <your_namespace> \
--repo https://fleetdm.github.io/fleet/charts \
--values values.yaml
```

View File

@ -0,0 +1,353 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | trim | nindent 8 }}
{{- end }}
labels:
{{- with .Values.podLabels }}
{{- toYaml . | trim | nindent 8 }}
{{- end }}
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
spec:
containers:
- name: {{ .Values.fleetName }}
command: [/usr/bin/fleet]
args: ["serve"]
image: fleetdm/fleet:{{ .Values.imageTag }}
ports:
- name: {{ .Values.fleetName }}
containerPort: {{ .Values.fleet.listenPort }}
resources:
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
env:
## BEGIN FLEET SECTION
- name: FLEET_DEMO
value: "1"
- name: FLEET_SERVER_ADDRESS
value: "0.0.0.0:{{ .Values.fleet.listenPort }}"
- name: FLEET_AUTH_BCRYPT_COST
value: "{{ .Values.fleet.auth.bcryptCost }}"
- name: FLEET_AUTH_SALT_KEY_SIZE
value: "{{ .Values.fleet.auth.saltKeySize }}"
- name: FLEET_APP_TOKEN_KEY_SIZE
value: "{{ .Values.fleet.app.tokenKeySize }}"
- name: FLEET_APP_TOKEN_VALIDITY_PERIOD
value: "{{ .Values.fleet.app.inviteTokenValidityPeriod }}"
- name: FLEET_SESSION_KEY_SIZE
value: "{{ .Values.fleet.session.keySize }}"
- name: FLEET_SESSION_DURATION
value: "{{ .Values.fleet.session.duration }}"
- name: FLEET_LOGGING_DEBUG
value: "{{ .Values.fleet.logging.debug }}"
- name: FLEET_LOGGING_JSON
value: "{{ .Values.fleet.logging.json }}"
- name: FLEET_LOGGING_DISABLE_BANNER
value: "{{ .Values.fleet.logging.disableBanner }}"
- name: FLEET_SERVER_TLS
value: "{{ .Values.fleet.tls.enabled }}"
{{- if .Values.fleet.tls.enabled }}
- name: FLEET_SERVER_TLS_COMPATIBILITY
value: "{{ .Values.fleet.tls.compatibility }}"
- name: FLEET_SERVER_CERT
value: "/secrets/tls/{{ .Values.fleet.tls.certSecretKey }}"
- name: FLEET_SERVER_KEY
value: "/secrets/tls/{{ .Values.fleet.tls.keySecretKey }}"
{{- end }}
{{- if ne .Values.fleet.carving.s3.bucketName "" }}
- name: FLEET_S3_BUCKET
value: "{{ .Values.fleet.carving.s3.bucketName }}"
- name: FLEET_S3_PREFIX
value: "{{ .Values.fleet.carving.s3.prefix }}"
{{- if ne .Values.fleet.carving.s3.accessKeyID "" }}
- name: FLEET_S3_ACCESS_KEY_ID
value: "{{ .Values.fleet.carving.s3.accessKeyID }}"
- name: FLEET_S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.fleet.secretName }}"
key: "{{ .Values.fleet.carving.s3.secretKey }}"
{{ else }}
- name: FLEET_S3_STS_ASSUME_ROLE_ARN
value: "{{ .Values.fleet.carving.s3.stsAssumeRoleARN }}"
{{- end }}
{{- end }}
## END FLEET SECTION
## BEGIN MYSQL SECTION
- name: FLEET_MYSQL_ADDRESS
value: "{{ .Values.mysql.address }}"
- name: FLEET_MYSQL_DATABASE
value: "{{ .Values.mysql.database }}"
- name: FLEET_MYSQL_USERNAME
value: "{{ .Values.mysql.username }}"
- name: FLEET_MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.mysql.secretName }}
key: {{ .Values.mysql.passwordKey }}
- name: FLEET_MYSQL_MAX_OPEN_CONNS
value: "{{ .Values.mysql.maxOpenConns }}"
- name: FLEET_MYSQL_MAX_IDLE_CONNS
value: "{{ .Values.mysql.maxIdleConns }}"
- name: FLEET_MYSQL_CONN_MAX_LIFETIME
value: "{{ .Values.mysql.connMaxLifetime }}"
{{- if .Values.mysql.tls.enabled }}
- name: FLEET_MYSQL_TLS_CA
value: "/secrets/mysql/{{ .Values.mysql.tls.caCertKey }}"
- name: FLEET_MYSQL_TLS_CERT
value: "/secrets/mysql/{{ .Values.mysql.tls.certKey }}"
- name: FLEET_MYSQL_TLS_KEY
value: "/secrets/mysql/{{ .Values.mysql.tls.keyKey }}"
- name: FLEET_MYSQL_TLS_CONFIG
value: "{{ .Values.mysql.tls.config }}"
- name: FLEET_MYSQL_TLS_SERVER_NAME
value: "{{ .Values.mysql.tls.serverName }}"
{{- end }}
## END MYSQL SECTION
## BEGIN REDIS SECTION
- name: FLEET_REDIS_ADDRESS
value: "{{ .Values.redis.address }}"
- name: FLEET_REDIS_DATABASE
value: "{{ .Values.redis.database }}"
{{- if .Values.redis.usePassword }}
- name: FLEET_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Values.redis.secretName }}"
key: "{{ .Values.redis.passwordKey }}"
{{- end }}
## END REDIS SECTION
## BEGIN OSQUERY SECTION
- name: FLEET_OSQUERY_NODE_KEY_SIZE
value: "{{ .Values.osquery.nodeKeySize }}"
- name: FLEET_OSQUERY_LABEL_UPDATE_INTERVAL
value: "{{ .Values.osquery.labelUpdateInterval }}"
- name: FLEET_OSQUERY_DETAIL_UPDATE_INTERVAL
value: "{{ .Values.osquery.detailUpdateInterval }}"
- name: FLEET_OSQUERY_STATUS_LOG_PLUGIN
value: "{{ .Values.osquery.logging.statusPlugin }}"
- name: FLEET_OSQUERY_RESULT_LOG_PLUGIN
value: "{{ .Values.osquery.logging.resultPlugin }}"
{{- if eq .Values.osquery.logging.statusPlugin "filesystem" }}
- name: FLEET_FILESYSTEM_STATUS_LOG_FILE
value: "/logs/{{ .Values.osquery.logging.filesystem.statusLogFile }}"
{{- end }}
{{- if eq .Values.osquery.logging.resultPlugin "filesystem" }}
- name: FLEET_FILESYSTEM_RESULT_LOG_FILE
value: "/logs/{{ .Values.osquery.logging.filesystem.resultLogFile }}"
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }}
- name: FLEET_FILESYSTEM_ENABLE_LOG_ROTATION
value: "{{ .Values.osquery.logging.filesystem.enableRotation }}"
- name: FLEET_FILESYSTEM_ENABLE_LOG_COMPRESSION
value: "{{ .Values.osquery.logging.filesystem.enableCompression }}"
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "firehose") (eq .Values.osquery.logging.resultPlugin "firehose") }}
- name: FLEET_FIREHOSE_REGION
value: "{{ .Values.osquery.logging.firehose.region }}"
{{- if eq .Values.osquery.logging.statusPlugin "firehose" }}
- name: FLEET_FIREHOSE_STATUS_STREAM
value: "{{ .Values.osquery.logging.firehose.statusStream }}"
{{- end }}
{{- if eq .Values.osquery.logging.resultPlugin "firehose" }}
- name: FLEET_FIREHOSE_RESULT_STREAM
value: "{{ .Values.osquery.logging.firehose.resultStream }}"
{{- end }}
{{- if ne .Values.osquery.logging.firehose.accessKeyID "" }}
- name: FLEET_FIREHOSE_ACCESS_KEY_ID
value: "{{ .Values.osquery.logging.firehose.accessKeyID }}"
- name: FLEET_FIREHOSE_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.osquery.secretName }}"
key: "{{ .Values.osquery.logging.firehose.secretKey }}"
{{ else }}
- name: FLEET_FIREHOSE_STS_ASSUME_ROLE_ARN
value: "{{ .Values.osquery.logging.firehose.stsAssumeRoleARN }}"
{{- end }}
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "kinesis") (eq .Values.osquery.logging.resultPlugin "kinesis") }}
- name: FLEET_KINESIS_REGION
value: "{{ .Values.osquery.logging.kinesis.region }}"
{{- if eq .Values.osquery.logging.statusPlugin "kinesis" }}
- name: FLEET_KINESIS_STATUS_STREAM
value: "{{ .Values.osquery.logging.kinesis.statusStream }}"
{{- end }}
{{- if eq .Values.osquery.logging.resultPlugin "kinesis" }}
- name: FLEET_KINESIS_RESULT_STREAM
value: "{{ .Values.osquery.logging.kinesis.resultStream }}"
{{- end }}
{{- if ne .Values.osquery.logging.kinesis.accessKeyID "" }}
- name: FLEET_KINESIS_ACCESS_KEY_ID
value: "{{ .Values.osquery.logging.kinesis.accessKeyID }}"
- name: FLEET_KINESIS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.osquery.secretName }}"
key: "{{ .Values.osquery.logging.kinesis.secretKey }}"
{{ else }}
- name: FLEET_KINESIS_STS_ASSUME_ROLE_ARN
value: "{{ .Values.osquery.logging.kinesis.stsAssumeRoleARN }}"
{{- end }}
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "lambda") (eq .Values.osquery.logging.resultPlugin "lambda") }}
- name: FLEET_LAMBDA_REGION
value: "{{ .Values.osquery.logging.lambda.region }}"
{{- if eq .Values.osquery.logging.statusPlugin "lambda" }}
- name: FLEET_LAMBDA_STATUS_FUNCTION
value: "{{ .Values.osquery.logging.lambda.statusFunction }}"
{{- end }}
{{- if eq .Values.osquery.logging.resultPlugin "lambda" }}
- name: FLEET_LAMBDA_RESULT_FUNCTION
value: "{{ .Values.osquery.logging.lambda.resultFunction }}"
{{- end }}
{{- if ne .Values.osquery.logging.lambda.accessKeyID "" }}
- name: FLEET_LAMBDA_ACCESS_KEY_ID
value: "{{ .Values.osquery.logging.lambda.accessKeyID }}"
- name: FLEET_LAMBDA_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.osquery.secretName }}"
key: "{{ .Values.osquery.logging.lambda.secretKey }}"
{{ else }}
- name: FLEET_LAMBDA_STS_ASSUME_ROLE_ARN
value: "{{ .Values.osquery.logging.lambda.stsAssumeRoleARN }}"
{{- end }}
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "pubsub") (eq .Values.osquery.logging.resultPlugin "pubsub") }}
- name: FLEET_PUBSUB_PROJECT
value: "{{ .Values.osquery.logging.pubsub.project }}"
{{- end }}
{{- if eq .Values.osquery.logging.statusPlugin "pubsub" }}
- name: FLEET_PUBSUB_STATUS_TOPIC
value: "{{ .Values.osquery.logging.pubsub.statusTopic }}"
{{- end }}
{{- if eq .Values.osquery.logging.resultPlugin "pubsub" }}
- name: FLEET_PUBSUB_RESULT_TOPIC
value: "{{ .Values.osquery.logging.pubsub.resultTopic }}"
{{- end }}
## END OSQUERY SECTION
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 3333
runAsUser: 3333
runAsNonRoot: true
livenessProbe:
httpGet:
path: /healthz
port: {{ .Values.fleet.listenPort }}
readinessProbe:
httpGet:
path: /healthz
port: {{ .Values.fleet.listenPort }}
{{- if or (.Values.fleet.tls.enabled) (.Values.mysql.tls.enabled) (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }}
volumeMounts:
{{- if .Values.fleet.tls.enabled }}
- name: {{ .Values.fleetName }}-tls
readOnly: true
mountPath: /secrets/tls
{{- end }}
{{- if .Values.mysql.tls.enabled }}
- name: mysql-tls
readOnly: true
mountPath: /secrets/mysql
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }}
- name: osquery-logs
mountPath: /logs
{{- end }}
{{- end }}
{{- if .Values.gke.cloudSQL.enableProxy }}
- name: cloudsql-proxy
image: "gcr.io/cloudsql-docker/gce-proxy:{{ .Values.gke.cloudSQL.imageTag }}"
command:
- "/cloud_sql_proxy"
- "-verbose={{ .Values.gke.cloudSQL.verbose}}"
- "-instances={{ .Values.gke.cloudSQL.instanceName }}=tcp:3306"
resources:
limits:
cpu: 0.5 # 500Mhz
memory: 150Mi
requests:
cpu: 0.1 # 100Mhz
memory: 50Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 3333
runAsUser: 3333
runAsNonRoot: true
{{- end }}
hostPID: false
hostNetwork: false
hostIPC: false
serviceAccountName: {{ .Values.fleetName }}
{{- if or (.Values.fleet.tls.enabled) (.Values.mysql.tls.enabled) (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }}
volumes:
{{- if .Values.fleet.tls.enabled }}
- name: {{ .Values.fleetName }}-tls
secret:
secretName: "{{ .Values.fleet.secretName }}"
{{- end }}
{{- if .Values.mysql.tls.enabled }}
- name: mysql-tls
secret:
secretName: "{{ .Values.mysql.secretName }}"
{{- end }}
{{- if or (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }}
- name: osquery-logs
emptyDir:
sizeLimit: "{{ .Values.osquery.logging.filesystem.volumeSize }}"
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,9 @@
{{- if .Values.gke.ingress.useManagedCertificate }}
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: {{ .Values.fleetName }}
spec:
domains:
- {{ .Values.hostName }}
{{- end }}

View File

@ -0,0 +1,39 @@
{{- if .Values.createIngress }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
{{- if or .Values.ingressAnnotations .Values.gke.useGKEIngress }}
annotations:
{{- range $key, $value := $.Values.ingressAnnotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- if .Values.gke.ingress.useGKEIngress }}
kubernetes.io/ingress.class: gce
{{- if .Values.gke.ingress.useManagedCertificate }}
kubernetes.io/ingress.allow-http: "false"
networking.gke.io/managed-certificates: fleet
{{- end }}
{{- end }}
{{- end }}
labels:
app: {{ .Values.fleetName }}
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}
spec:
rules:
- host: {{ .Values.hostName }}
http:
paths:
- path: /
# Next line required in k8s 1.19 and not supported in <=1.17
# pathType: Exact
backend:
service:
name: {{ .Values.fleetName }}
port:
number: 8080
pathType: Prefix
{{- end }}

View File

@ -0,0 +1,134 @@
{{- if .Values.fleet.autoApplySQLMigrations }}
apiVersion: batch/v1
kind: Job
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}-migration
namespace: {{ .Release.Namespace }}
spec:
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | trim | nindent 8 }}
{{- end }}
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
spec:
restartPolicy: Never
containers:
- name: {{ .Values.fleetName }}-migration
command: [/usr/bin/fleet]
args: ["prepare","db","--no-prompt"]
image: fleetdm/fleet:{{ .Values.imageTag }}
resources:
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
env:
- name: FLEET_SERVER_ADDRESS
value: "0.0.0.0:{{ .Values.fleet.listenPort }}"
- name: FLEET_AUTH_BCRYPT_COST
value: "{{ .Values.fleet.auth.bcryptCost }}"
- name: FLEET_AUTH_SALT_KEY_SIZE
value: "{{ .Values.fleet.auth.saltKeySize }}"
- name: FLEET_APP_TOKEN_KEY_SIZE
value: "{{ .Values.fleet.app.tokenKeySize }}"
- name: FLEET_APP_TOKEN_VALIDITY_PERIOD
value: "{{ .Values.fleet.app.inviteTokenValidityPeriod }}"
- name: FLEET_SESSION_KEY_SIZE
value: "{{ .Values.fleet.session.keySize }}"
- name: FLEET_SESSION_DURATION
value: "{{ .Values.fleet.session.duration }}"
- name: FLEET_LOGGING_DEBUG
value: "{{ .Values.fleet.logging.debug }}"
- name: FLEET_LOGGING_JSON
value: "{{ .Values.fleet.logging.json }}"
- name: FLEET_LOGGING_DISABLE_BANNER
value: "{{ .Values.fleet.logging.disableBanner }}"
- name: FLEET_SERVER_TLS
value: "{{ .Values.fleet.tls.enabled }}"
{{- if .Values.fleet.tls.enabled }}
- name: FLEET_SERVER_TLS_COMPATIBILITY
value: "{{ .Values.fleet.tls.compatibility }}"
- name: FLEET_SERVER_CERT
value: "/secrets/tls/{{ .Values.fleet.tls.certSecretKey }}"
- name: FLEET_SERVER_KEY
value: "/secrets/tls/{{ .Values.fleet.tls.keySecretKey }}"
{{- end }}
## END FLEET SECTION
## BEGIN MYSQL SECTION
- name: FLEET_MYSQL_ADDRESS
value: "{{ .Values.mysql.address }}"
- name: FLEET_MYSQL_DATABASE
value: "{{ .Values.mysql.database }}"
- name: FLEET_MYSQL_USERNAME
value: "{{ .Values.mysql.username }}"
- name: FLEET_MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.mysql.secretName }}
key: {{ .Values.mysql.passwordKey }}
- name: FLEET_MYSQL_MAX_OPEN_CONNS
value: "{{ .Values.mysql.maxOpenConns }}"
- name: FLEET_MYSQL_MAX_IDLE_CONNS
value: "{{ .Values.mysql.maxIdleConns }}"
- name: FLEET_MYSQL_CONN_MAX_LIFETIME
value: "{{ .Values.mysql.connMaxLifetime }}"
{{- if .Values.mysql.tls.enabled }}
- name: FLEET_MYSQL_TLS_CA
value: "/secrets/mysql/{{ .Values.mysql.tls.caCertKey }}"
- name: FLEET_MYSQL_TLS_CERT
value: "/secrets/mysql/{{ .Values.mysql.tls.certKey }}"
- name: FLEET_MYSQL_TLS_KEY
value: "/secrets/mysql/{{ .Values.mysql.tls.keyKey }}"
- name: FLEET_MYSQL_TLS_CONFIG
value: "{{ .Values.mysql.tls.config }}"
- name: FLEET_MYSQL_TLS_SERVER_NAME
value: "{{ .Values.mysql.tls.serverName }}"
{{- end }}
## END MYSQL SECTION
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 3333
runAsUser: 3333
runAsNonRoot: true
volumeMounts:
{{- if .Values.mysql.tls.enabled }}
- name: mysql-tls
readOnly: true
mountPath: /secrets/mysql
{{- end }}
volumes:
{{- if .Values.mysql.tls.enabled }}
- name: mysql-tls
secret:
secretName: "{{ .Values.mysql.secretName }}"
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,11 @@
{{- if .Values.createNamespace }}
apiVersion: v1
kind: Namespace
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Release.Namespace }}
{{- end }}

View File

@ -0,0 +1,42 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- core
resources:
- secrets
resourceNames:
- {{ .Values.mysql.secretName }}
- {{ .Values.redis.secretName }}
- {{ .Values.fleet.secretName }}
- {{ .Values.osquery.secretName }}
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Values.fleetName }}
subjects:
- apiGroup: ""
kind: ServiceAccount
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: ServiceAccount
metadata:
{{- if or .Values.serviceAccountAnnotations .Values.gke.workloadIdentityEmail }}
annotations:
{{- with .Values.serviceAccountAnnotations}}
{{ toYaml . | trim | indent 2}}
{{- end }}
{{- if ne .Values.gke.workloadIdentityEmail "" }}
iam.gke.io/gcp-service-account: {{ .Values.gke.workloadIdentityEmail }}
{{- end }}
{{- end }}
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}

View File

@ -0,0 +1,31 @@
{{- if .Values.mysql.createSecret }}
apiVersion: v1
kind: Secret
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.mysql.secretName }}
namespace: {{ .Release.Namespace }}
stringData:
{{ .Values.mysql.passwordKey }}: {{ .Values.mysql.password | quote }}
type: Opaque
---
{{- end }}
{{- if .Values.redis.createSecret }}
apiVersion: v1
kind: Secret
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.redis.secretName }}
namespace: {{ .Release.Namespace }}
stringData:
{{ .Values.redis.passwordKey }}: {{ .Values.redis.password }}
type: Opaque
{{- end }}

View File

@ -0,0 +1,22 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ .Values.fleetName }}
namespace: {{ .Release.Namespace }}
spec:
selector:
app: fleet
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
ports:
- name: {{ .Values.fleetName }}
port: {{ .Values.fleet.listenPort }}
{{- if .Values.gke.ingress.useGKEIngress }}
type: NodePort
{{- end }}

View File

@ -0,0 +1,183 @@
## Section: Kubernetes
# All settings related to how Fleet is deployed in Kubernetes
# The name used for deployment/role/sa/etc. Useful for when deploying multiple separate
# fleet instances into the same Namespace.
fleetName: fleet
hostName: fleet.localhost
replicas: 3 # The number of Fleet instances to deploy
imageTag: v4.12.0 # Version of Fleet to deploy
createNamespace: false # Whether or not to automatically create the Namespace
createIngress: true # Whether or not to automatically create an Ingress
ingressAnnotations: {} # Additional annotation to add to the Ingress
podLabels: {} # Additional labels to add to the Fleet pod
podAnnotations: {} # Additional annotations to add to the Fleet pod
serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account
resources:
limits:
cpu: 1 # 1GHz
memory: 1Gi
requests:
cpu: 0.1 # 100Mhz
memory: 50Mi
# Node labels for pod assignment
# ref: https://kubernetes.io/docs/user-guide/node-selection/
nodeSelector: {}
# Tolerations for pod assignment
# ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations: []
# Configurable affinity for pod assignment
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- fleet
topologyKey: kubernetes.io/hostname
weight: 100
## Section: Fleet
# All of the settings relating to configuring the Fleet server
fleet:
listenPort: 8080
# Name of the Secret resource storing TLS and S3 bucket secrets
secretName: fleet
# Whether or not to run `fleet db prepare` to run SQL migrations before starting Fleet
autoApplySQLMigrations: true
tls:
enabled: true
compatibility: modern
certSecretKey: server.cert
keySecretKey: server.key
auth:
bcryptCost: 12
saltKeySize: 24
app:
tokenKeySize: 24
inviteTokenValidityPeriod: 120h # 5 days
session:
keySize: 64
duration: 2160h # 90 days
logging:
debug: false
json: false
disableBanner: false
carving:
s3:
bucketName: ""
prefix: ""
accessKeyID: ""
secretKey: s3-bucket
stsAssumeRoleARN: ""
## Section: osquery
# All of the settings related to osquery's interactions with the Fleet server
osquery:
# Name of the secret resource containing optional secrets for AWS credentials
secretName: osquery
nodeKeySize: 24
labelUpdateInterval: 30m
detailUpdateInterval: 30m
# To change where Fleet store the logs sent from osquery, set the values below
logging:
statusPlugin: filesystem
resultPlugin: filesystem
# To congigure the filesystem logger, change the values below
filesystem:
statusLogFile: osquery_status # will be placed in the /logs volume
resultLogFile: osquery_result # will be placed in the /logs volume
enableRotation: false
enableCompression: false
volumeSize: 20Gi # the maximum size of the volume
# To configure the AWS Firehose logger, change the values below
firehose:
region: ""
accessKeyID: ""
secretKey: firehose
stsAssumeRoleARN: ""
statusStream: ""
resultStream: ""
# To configure the AWS Kinesis logger, change the values below
kinesis:
region: ""
accessKeyID: ""
secretKey: kinesis
stsAssumeRoleARN: ""
statusStream: ""
resultStream: ""
# To configure the AWS Lambda logger, change the values below
lambda:
region: ""
accessKeyID: ""
secretKey: lambda
stsAssumeRoleARN: ""
statusFunction: ""
resultFunction: ""
# To configure the GCP PubSub logger, change the values below
pubsub:
project: ""
statusTopic: ""
resultTopic: ""
## Section: MySQL
# All of the connection settings for MySQL
mysql:
createSecret: false
# Name of the Secret resource containing MySQL password and TLS secrets
secretName: mysql
address: 127.0.0.1:3306
database: fleet
username: fleet
# Only needed if creating secret.
password: default
passwordKey: mysql-password
maxOpenConns: 50
maxIdleConns: 50
connMaxLifetime: 0
tls:
enabled: false
caCertKey: ca.cert
certKey: client.cert
keyKey: client.key
config: ""
serverName: ""
## Section: Redis
# All of the connection settings for Redis
redis:
createSecret: false
address: 127.0.0.1:6379
database: "0"
usePassword: false
secretName: redis
# Only needed if creating secret.
password: default
passwordKey: redis-password
## Section: GKE
# Settings that make running on Google Kubernetes Engine easier
gke:
# The CloudSQL Proxy runs as a container in the Fleet Pod that proxies connections to a Cloud SQL instance
cloudSQL:
enableProxy: false
imageTag: 1.17-alpine
verbose: true
instanceName: ""
# The GKE Ingress requires a few changes that other ingress controllers don't
ingress:
useGKEIngress: false
useManagedCertificate: false
# Workload Identity allows the K8s service account to assume the IAM permissions of a GCP service account
workloadIdentityEmail: ""

View File

@ -0,0 +1,170 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.10.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1.2"
}
mysql = {
source = "petoju/mysql"
version = "3.0.12"
}
helm = {
source = "hashicorp/helm"
version = "2.5.1"
}
}
backend "s3" {}
}
provider "helm" {
kubernetes {
host = data.aws_eks_cluster.cluster.endpoint
token = data.aws_eks_cluster_auth.cluster.token
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
}
}
data "aws_eks_cluster" "cluster" {
name = var.eks_cluster
}
data "aws_eks_cluster_auth" "cluster" {
name = var.eks_cluster
}
provider "mysql" {
endpoint = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["endpoint"]
username = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["username"]
password = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["password"]
}
variable "mysql_secret" {}
variable "eks_cluster" {}
variable "redis_address" {}
variable "redis_database" {}
variable "lifecycle_table" {}
variable "base_domain" {}
resource "mysql_user" "main" {
user = terraform.workspace
host = "%"
plaintext_password = random_password.db.result
}
resource "mysql_database" "main" {
name = terraform.workspace
}
resource "mysql_grant" "main" {
user = mysql_user.main.user
database = mysql_database.main.name
host = "%"
privileges = ["ALL"]
}
data "aws_secretsmanager_secret_version" "mysql" {
secret_id = var.mysql_secret
}
resource "random_password" "db" {
length = 8
}
resource "helm_release" "main" {
name = terraform.workspace
chart = "${path.module}/fleet"
set {
name = "fleetName"
value = terraform.workspace
}
set {
name = "mysql.password"
value = random_password.db.result
}
set {
name = "mysql.createSecret"
value = true
}
set {
name = "mysql.secretName"
value = terraform.workspace
}
set {
name = "mysql.username"
value = mysql_user.main.user
}
set {
name = "mysql.database"
value = terraform.workspace
}
set {
name = "mysql.address"
value = jsondecode(data.aws_secretsmanager_secret_version.mysql.secret_string)["endpoint"]
}
set {
name = "fleet.tls.enabled"
value = false
}
set {
name = "redis.address"
value = var.redis_address
}
set {
name = "redis.database"
value = var.redis_database
}
set {
name = "kubernetes.io/ingress.class"
value = "nginx"
}
set {
name = "hostName"
value = "${terraform.workspace}.${var.base_domain}"
}
set {
name = "ingressAnnotations.kubernetes\\.io/ingress\\.class"
value = "haproxy"
}
set {
name = "replicas"
value = "2"
}
set {
name = "imageTag"
value = "v4.17.0"
}
}
resource "aws_dynamodb_table_item" "main" {
table_name = var.lifecycle_table
hash_key = "ID"
item = <<ITEM
{
"ID": {"S": "${terraform.workspace}"},
"State": {"S": "unclaimed"},
"redis_db": {"N": "${var.redis_database}"}
}
ITEM
depends_on = [helm_release.main]
}

View File

@ -0,0 +1,33 @@
module github.com/fleetdm/fleet/infrastructure/demo/PreProvisioner/lambda
go 1.18
require (
github.com/aws/aws-lambda-go v1.29.0
github.com/aws/aws-sdk-go v1.43.37
github.com/awslabs/aws-lambda-go-api-proxy v0.13.1
github.com/gin-gonic/gin v1.7.7
github.com/go-sql-driver/mysql v1.6.0
github.com/google/uuid v1.3.0
github.com/jessevdk/go-flags v1.5.0
github.com/otiai10/copy v1.7.0
)
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/protobuf v1.4.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)

View File

@ -0,0 +1,321 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
github.com/aws/aws-lambda-go v1.29.0 h1:u+sfZkvNBUgt0ZkO8Q/jOMBV22DqMDMbZu04oomM2no=
github.com/aws/aws-lambda-go v1.29.0/go.mod h1:aakqVz9vDHhtbt0U2zegh/z9SI2+rJ+yRREZYNQLmWY=
github.com/aws/aws-sdk-go v1.43.37 h1:kyZ7UjaPZaCik+asF33UFOOYSwr9liDRr/UM/vuw8yY=
github.com/aws/aws-sdk-go v1.43.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/awslabs/aws-lambda-go-api-proxy v0.13.1 h1:2hASLXizNyRvrBJPLc7IsNuS7hWhWRTOWOyrCPHdktA=
github.com/awslabs/aws-lambda-go-api-proxy v0.13.1/go.mod h1:iAimuKT3RS8Aw99XOMAlaYlhvj7QeuoHxf9gm9NYHLU=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-chi/chi/v5 v5.0.2/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gofiber/fiber/v2 v2.1.0/go.mod h1:aG+lMkwy3LyVit4CnmYUbUdgjpc3UYOltvlJZ78rgQ0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/golog v0.0.18/go.mod h1:jRYl7dFYqP8aQj9VkwdBUXYZSfUktm+YYg1arJILfyw=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
github.com/kataras/pio v0.0.8/go.mod h1:NFfMp2kVP1rmV4N6gH6qgWpuoDKlrOeYi3VrAIWCGsE=
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,195 @@
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/google/uuid"
flags "github.com/jessevdk/go-flags"
"log"
"math/rand"
"os"
"os/exec"
)
type OptionsStruct struct {
LambdaExecutionEnv string `long:"lambda-execution-environment" env:"AWS_EXECUTION_ENV"`
LifecycleTable string `long:"dynamodb-lifecycle-table" env:"DYNAMODB_LIFECYCLE_TABLE" required:"true"`
MaxInstances int64 `long:"max-instances" env:"MAX_INSTANCES" required:"true"`
QueuedInstances int64 `long:"queued-instances" env:"QUEUED_INSTANCES" required:"true"`
}
var options = OptionsStruct{}
type LifecycleRecord struct {
ID string
State string
}
func getInstancesCount() (int64, int64, error) {
log.Print("getInstancesCount")
svc := dynamodb.New(session.New())
// Example iterating over at most 3 pages of a Scan operation.
var count, unclaimedCount int64
err := svc.ScanPages(
&dynamodb.ScanInput{
TableName: aws.String(options.LifecycleTable),
},
func(page *dynamodb.ScanOutput, lastPage bool) bool {
log.Print(page)
count += *page.Count
recs := []LifecycleRecord{}
if err := dynamodbattribute.UnmarshalListOfMaps(page.Items, &recs); err != nil {
log.Print(err)
return false
}
for _, i := range recs {
if i.State == "unclaimed" {
unclaimedCount++
}
}
return true
})
if err != nil {
return 0, 0, err
}
return count, unclaimedCount, nil
}
type NullEvent struct{}
func min(a, b int64) int64 {
// I really have to implement this myself?
if a < b {
return a
}
return b
}
func runCmd(args []string) error {
cmd := exec.Cmd{
Path: "/build/terraform",
Dir: "/build/deploy_terraform",
Stdout: os.Stdout,
Stderr: os.Stderr,
Args: append([]string{"/build/terraform"}, args...),
}
log.Printf("%+v\n", cmd)
return cmd.Run()
}
func initTerraform() error {
err := runCmd([]string{
"init",
"-backend-config=backend.conf",
})
return err
}
func runTerraform(workspace string, redis_database int) error {
err := runCmd([]string{
"workspace",
"new",
workspace,
})
if err != nil {
return err
}
err = runCmd([]string{
"apply",
"-auto-approve",
"-no-color",
"-var",
fmt.Sprintf("redis_database=%d", redis_database),
})
return err
}
func idExists(id int) (bool, error) {
svc := dynamodb.New(session.New())
input := &dynamodb.QueryInput{
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":v1": {
N: aws.String(fmt.Sprintf("%d", id)),
},
},
KeyConditionExpression: aws.String("redis_db = :v1"),
TableName: aws.String(options.LifecycleTable),
IndexName: aws.String("RedisDatabases"),
}
result, err := svc.Query(input)
if err != nil {
return false, err
}
return *result.Count != 0, nil
}
func getRedisDatabase() (int, error) {
for {
ret := rand.Intn(65536)
exists, err := idExists(ret)
if err != nil {
return 0, err
}
if !exists {
return ret, nil
}
}
}
func handler(ctx context.Context, name NullEvent) error {
// check if we need to do anything
totalCount, unclaimedCount, err := getInstancesCount()
if err != nil {
return err
}
if totalCount >= options.MaxInstances {
return nil
}
if unclaimedCount >= options.QueuedInstances {
return nil
}
numToReady := min(options.MaxInstances-totalCount, options.QueuedInstances-unclaimedCount)
// deploy terraform to initialize everything
for i := int64(0); i < numToReady; i++ {
if i == 0 {
if err := initTerraform(); err != nil {
return err
}
}
redisDatabase, err := getRedisDatabase()
if err != nil {
return err
}
if err := runTerraform(fmt.Sprintf("t%s", uuid.New().String()[:8]), redisDatabase); err != nil {
return err
}
}
return nil
}
func main() {
var err error
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Get config from environment
parser := flags.NewParser(&options, flags.Default)
if _, err = parser.Parse(); err != nil {
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
return
} else {
log.Fatal(err)
}
}
if options.LambdaExecutionEnv == "AWS_Lambda_go1.x" {
lambda.Start(handler)
} else {
if err = handler(context.Background(), NullEvent{}); err != nil {
log.Fatal(err)
}
}
}

View File

@ -0,0 +1,322 @@
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
git = {
source = "paultyng/git"
version = "~> 0.1.0"
}
}
}
data "aws_region" "current" {}
locals {
name = "preprovisioner"
full_name = "${var.prefix}-${local.name}"
}
resource "aws_cloudwatch_log_group" "main" {
name = local.full_name
kms_key_id = var.kms_key.arn
retention_in_days = 30
}
data "aws_iam_policy_document" "events-assume-role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}
}
}
resource "aws_iam_role_policy_attachment" "events" {
role = aws_iam_role.events.id
policy_arn = aws_iam_policy.events.arn
}
resource "aws_iam_policy" "events" {
name = "${local.full_name}-events"
policy = data.aws_iam_policy_document.events.json
}
data "aws_iam_policy_document" "events" {
statement {
actions = ["ecs:RunTask"]
resources = [replace(aws_ecs_task_definition.main.arn, "/:\\d+$/", ":*"), replace(aws_ecs_task_definition.main.arn, "/:\\d+$/", "")]
condition {
test = "ArnLike"
variable = "ecs:cluster"
values = [var.ecs_cluster.arn]
}
}
statement {
actions = ["iam:PassRole"]
resources = ["*"]
condition {
test = "StringLike"
variable = "iam:PassedToService"
values = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "events" {
name = "${local.full_name}-events"
path = "/service-role/"
assume_role_policy = data.aws_iam_policy_document.events-assume-role.json
}
data "aws_iam_policy_document" "lambda-assume-role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role_policy_attachment" "lambda" {
role = aws_iam_role.lambda.id
policy_arn = aws_iam_policy.lambda.arn
}
resource "aws_iam_role_policy_attachment" "lambda-ecs" {
role = aws_iam_role.lambda.id
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
}
resource "aws_iam_policy" "lambda" {
name = "${var.prefix}-lambda"
policy = data.aws_iam_policy_document.lambda.json
}
data "aws_iam_policy_document" "lambda" {
statement {
actions = [
"dynamodb:List*",
"dynamodb:DescribeReservedCapacity*",
"dynamodb:DescribeLimits",
"dynamodb:DescribeTimeToLive"
]
resources = ["*"]
}
statement {
actions = [
"dynamodb:BatchGet*",
"dynamodb:DescribeStream",
"dynamodb:DescribeTable",
"dynamodb:Get*",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchWrite*",
"dynamodb:CreateTable",
"dynamodb:Delete*",
"dynamodb:Update*",
"dynamodb:PutItem"
]
resources = [var.dynamodb_table.arn]
}
statement {
actions = [ #tfsec:ignore:aws-iam-no-policy-wildcards
"kms:Encrypt*",
"kms:Decrypt*",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Describe*"
]
resources = [aws_kms_key.ecr.arn, var.kms_key.arn]
}
statement {
actions = ["*"]
resources = ["*"]
}
}
resource "aws_iam_role" "lambda" {
name = local.full_name
assume_role_policy = data.aws_iam_policy_document.lambda-assume-role.json
}
output "lambda_role" {
value = aws_iam_role.lambda
}
resource "aws_security_group" "lambda" {
name = local.full_name
description = "security group for ${local.full_name}"
vpc_id = var.vpc.vpc_id
egress {
description = "egress to all"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
data "aws_eks_cluster" "cluster" {
name = var.eks_cluster.eks_cluster_id
}
resource "aws_ecs_task_definition" "main" {
family = local.full_name
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.lambda.arn
task_role_arn = aws_iam_role.lambda.arn
cpu = 1024
memory = 4096
container_definitions = jsonencode(
[
{
name = local.name
image = docker_registry_image.main.name
mountPoints = []
volumesFrom = []
essential = true
networkMode = "awsvpc"
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.main.name
awslogs-region = data.aws_region.current.name
awslogs-stream-prefix = local.full_name
}
},
environment = concat([
{
name = "TF_VAR_mysql_secret"
value = var.mysql_secret.id
},
{
name = "TF_VAR_mysql_cluster_name"
value = var.eks_cluster.eks_cluster_id
},
{
name = "TF_VAR_eks_cluster"
value = var.eks_cluster.eks_cluster_id
},
{
name = "DYNAMODB_LIFECYCLE_TABLE"
value = var.dynamodb_table.id
},
{
name = "TF_VAR_lifecycle_table"
value = var.dynamodb_table.id
},
{
name = "TF_VAR_base_domain"
value = var.base_domain
},
{
name = "MAX_INSTANCES"
value = "100"
},
{
name = "QUEUED_INSTANCES"
value = "20"
},
{
name = "TF_VAR_redis_address"
value = "${var.redis_cluster.primary_endpoint_address}:6379"
},
])
}
])
lifecycle {
create_before_destroy = true
}
}
resource "aws_kms_key" "ecr" {
deletion_window_in_days = 10
enable_key_rotation = true
}
resource "aws_ecr_repository" "main" {
name = "${var.prefix}-lambda"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
}
resource "random_uuid" "main" {
keepers = {
lambda = data.archive_file.main.output_sha
}
}
resource "local_file" "backend-config" {
content = templatefile("${path.module}/lambda/backend-template.conf",
{
remote_state = var.remote_state
})
filename = "${path.module}/lambda/deploy_terraform/backend.conf"
}
data "archive_file" "main" {
type = "zip"
output_path = "${path.module}/.lambda.zip"
source_dir = "${path.module}/lambda"
}
data "git_repository" "main" {
path = "${path.module}/../../../"
}
resource "docker_registry_image" "main" {
name = "${aws_ecr_repository.main.repository_url}:${data.git_repository.main.branch}-${random_uuid.main.result}"
keep_remotely = true
build {
context = "${path.module}/lambda/"
pull_parent = true
}
depends_on = [
local_file.backend-config
]
}
resource "aws_cloudwatch_event_rule" "main" {
name_prefix = var.prefix
schedule_expression = "rate(1 hour)"
is_enabled = true
}
resource "aws_cloudwatch_event_target" "main" {
rule = aws_cloudwatch_event_rule.main.name
arn = var.ecs_cluster.arn
role_arn = aws_iam_role.events.arn
ecs_target {
task_count = 1
task_definition_arn = aws_ecs_task_definition.main.arn
launch_type = "FARGATE"
network_configuration {
subnets = var.vpc.private_subnets
security_groups = [aws_security_group.lambda.id]
assign_public_ip = false
}
}
}

View File

@ -0,0 +1,3 @@
output "lambda_security_group" {
value = aws_security_group.lambda
}

View File

@ -0,0 +1,10 @@
variable "prefix" {}
variable "dynamodb_table" {}
variable "vpc" {}
variable "remote_state" {}
variable "mysql_secret" {}
variable "eks_cluster" {}
variable "redis_cluster" {}
variable "base_domain" {}
variable "ecs_cluster" {}
variable "kms_key" {}

View File

@ -0,0 +1,117 @@
resource "aws_lb" "main" {
name = var.prefix
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.lb.id]
subnets = var.vpc.public_subnets
enable_deletion_protection = true
}
output "lb" {
value = aws_lb.main
}
resource "aws_security_group" "lb" {
name = "${var.prefix}-lb"
vpc_id = var.vpc.vpc_id
description = "${var.prefix}-lb"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
certificate_arn = aws_acm_certificate.main.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.eks.arn
}
}
resource "aws_lb_listener" "redirect" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
output "alb_listener" {
value = aws_lb_listener.main
}
resource "aws_acm_certificate" "main" {
domain_name = "*.${var.base_domain}"
subject_alternative_names = [var.base_domain]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_acm_certificate_validation" "main" {
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [for r in cloudflare_record.cert : r.hostname]
}
data "cloudflare_zone" "main" {
name = "fleetdm.com"
}
resource "cloudflare_record" "cert" {
for_each = { for o in aws_acm_certificate.main.domain_validation_options.* : o.resource_record_name => o... }
zone_id = data.cloudflare_zone.main.id
name = replace(each.value[0].resource_record_name, ".fleetdm.com.", "")
type = each.value[0].resource_record_type
value = replace(each.value[0].resource_record_value, "/.$/", "")
ttl = 1
proxied = false
}
resource "cloudflare_record" "main" {
zone_id = data.cloudflare_zone.main.id
name = "sandbox"
type = "CNAME"
value = aws_lb.main.dns_name
proxied = false
}
resource "cloudflare_record" "wildcard" {
zone_id = data.cloudflare_zone.main.id
name = "*.sandbox"
type = "CNAME"
value = aws_lb.main.dns_name
proxied = false
}

View File

@ -0,0 +1,196 @@
provider "kubernetes" {
experiments {
manifest_resource = true
}
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
token = data.aws_eks_cluster_auth.cluster.token
}
provider "helm" {
kubernetes {
host = data.aws_eks_cluster.cluster.endpoint
token = data.aws_eks_cluster_auth.cluster.token
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
}
}
provider "kubectl" {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
token = data.aws_eks_cluster_auth.cluster.token
load_config_file = false
apply_retry_count = 5
}
locals {
cluster_version = "1.21"
}
output "eks_cluster" {
value = module.aws-eks-accelerator-for-terraform
}
terraform {
required_providers {
kubectl = {
source = "gavinbunney/kubectl"
version = "1.14.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "3.18.0"
}
}
}
data "aws_iam_role" "admin" {
name = "admin"
}
module "aws-eks-accelerator-for-terraform" {
source = "github.com/aws-samples/aws-eks-accelerator-for-terraform.git"
cluster_name = var.prefix
# EKS Cluster VPC and Subnets
vpc_id = var.vpc.vpc_id
private_subnet_ids = var.vpc.private_subnets
# EKS CONTROL PLANE VARIABLES
cluster_version = local.cluster_version
# EKS MANAGED NODE GROUPS
managed_node_groups = {
mg_4 = {
node_group_name = "managed-ondemand"
instance_types = ["t3.medium"]
subnet_ids = var.vpc.private_subnets
}
}
map_roles = concat([for i in var.eks_allowed_roles : {
rolearn = i.arn
username = i.id
groups = ["system:masters"]
}], [{
rolearn = data.aws_iam_role.admin.arn
username = data.aws_iam_role.admin.id
groups = ["system:masters"]
}])
fargate_profiles = {
default = {
fargate_profile_name = "default"
fargate_profile_namespaces = [
{
namespace = "default"
}
]
subnet_ids = flatten([var.vpc.private_subnets])
}
}
}
data "aws_eks_cluster" "cluster" {
name = module.aws-eks-accelerator-for-terraform.eks_cluster_id
}
data "aws_eks_cluster_auth" "cluster" {
name = module.aws-eks-accelerator-for-terraform.eks_cluster_id
}
module "kubernetes-addons" {
source = "github.com/aws-samples/aws-eks-accelerator-for-terraform.git//modules/kubernetes-addons"
eks_cluster_id = module.aws-eks-accelerator-for-terraform.eks_cluster_id
eks_cluster_endpoint = module.aws-eks-accelerator-for-terraform.eks_cluster_endpoint
eks_cluster_version = local.cluster_version
eks_oidc_provider = module.aws-eks-accelerator-for-terraform.eks_oidc_issuer_url
eks_worker_security_group_id = module.aws-eks-accelerator-for-terraform.worker_node_security_group_id
# EKS Managed Add-ons
enable_amazon_eks_vpc_cni = true
enable_amazon_eks_coredns = true
enable_amazon_eks_kube_proxy = true
enable_amazon_eks_aws_ebs_csi_driver = true
#K8s Add-ons
enable_aws_load_balancer_controller = true
enable_metrics_server = false
enable_cluster_autoscaler = true
enable_vpa = true
enable_prometheus = false
enable_ingress_nginx = false
enable_aws_for_fluentbit = false
enable_argocd = false
enable_fargate_fluentbit = false
enable_argo_rollouts = false
enable_kubernetes_dashboard = false
enable_yunikorn = false
depends_on = [module.aws-eks-accelerator-for-terraform.managed_node_groups]
}
resource "helm_release" "haproxy_ingress" {
name = "haproxy-ingress-controller"
namespace = "kube-system"
repository = "https://haproxy-ingress.github.io/charts"
chart = "haproxy-ingress"
set {
name = "controller.hostNetwork"
value = "true"
}
set {
name = "controller.kind"
value = "DaemonSet"
}
set {
name = "controller.service.type"
value = "NodePort"
}
}
resource "aws_lb_target_group" "eks" {
name = var.prefix
port = 80
protocol = "HTTP"
vpc_id = var.vpc.vpc_id
health_check {
matcher = "404"
}
}
resource "kubernetes_manifest" "targetgroupbinding" {
manifest = {
"apiVersion" = "elbv2.k8s.aws/v1beta1"
"kind" = "TargetGroupBinding"
"metadata" = {
"name" = "haproxy"
"namespace" = "kube-system"
}
"spec" = {
"targetGroupARN" = aws_lb_target_group.eks.arn
"serviceRef" = {
"name" = helm_release.haproxy_ingress.name
"port" = 80
}
"targetType" = "instance"
"networking" = {
"ingress" = [{
"from" = [{
"securityGroup" = {
"groupID" = aws_security_group.lb.id
}
}]
"ports" = [{
"protocol" = "TCP"
}]
}]
}
}
}
}

View File

@ -0,0 +1,95 @@
resource "random_password" "database_password" {
length = 16
special = false
}
resource "aws_kms_key" "main" {
description = "${var.prefix}-${random_pet.db_secret_postfix.id}"
deletion_window_in_days = 10
enable_key_rotation = true
}
resource "random_pet" "db_secret_postfix" {
length = 1
}
resource "aws_secretsmanager_secret" "database_password_secret" {
name = "/fleet/database/password/master-2-${random_pet.db_secret_postfix.id}"
kms_key_id = aws_kms_key.main.id
}
resource "aws_secretsmanager_secret_version" "database_password_secret_version" {
secret_id = aws_secretsmanager_secret.database_password_secret.id
secret_string = random_password.database_password.result
}
resource "aws_secretsmanager_secret" "mysql" {
name = "/fleet/database/password/mysql-${random_pet.db_secret_postfix.id}"
kms_key_id = aws_kms_key.main.id
}
output "mysql_secret" {
value = aws_secretsmanager_secret.mysql
}
resource "aws_secretsmanager_secret_version" "mysql" {
secret_id = aws_secretsmanager_secret.mysql.id
secret_string = jsonencode({
endpoint = module.main.cluster_endpoint
username = module.main.cluster_master_username
password = module.main.cluster_master_password
})
}
module "main" {
source = "terraform-aws-modules/rds-aurora/aws"
version = "6.2.0"
name = var.prefix
engine = "aurora-mysql"
engine_version = "5.7.mysql_aurora.2.10.0"
engine_mode = "serverless"
storage_encrypted = true
master_username = "fleet"
master_password = random_password.database_password.result
create_random_password = false
enable_http_endpoint = false
performance_insights_enabled = true
vpc_id = var.vpc.vpc_id
subnets = var.vpc.database_subnets
create_security_group = true
allowed_security_groups = var.allowed_security_groups
allowed_cidr_blocks = ["10.0.0.0/8"]
kms_key_id = aws_kms_key.main.arn
performance_insights_kms_key_id = aws_kms_key.main.arn
monitoring_interval = 60
apply_immediately = true
skip_final_snapshot = true
db_parameter_group_name = aws_db_parameter_group.main.id
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.main.id
scaling_configuration = {
auto_pause = true
min_capacity = 2
max_capacity = 16
seconds_until_auto_pause = 300
timeout_action = "ForceApplyCapacityChange"
}
}
resource "aws_db_parameter_group" "main" {
name = "${var.prefix}-aurora-db-mysql-parameter-group"
family = "aurora-mysql5.7"
description = "${var.prefix}-aurora-db-mysql-parameter-group"
}
resource "aws_rds_cluster_parameter_group" "main" {
name = "${var.prefix}-aurora-mysql-cluster-parameter-group"
family = "aurora-mysql5.7"
description = "${var.prefix}-aurora-mysql-cluster-parameter-group"
}

View File

@ -0,0 +1,55 @@
resource "aws_elasticache_replication_group" "main" {
availability_zones = ["us-east-2a", "us-east-2b", "us-east-2c"]
engine = "redis"
parameter_group_name = aws_elasticache_parameter_group.main.id
subnet_group_name = var.vpc.elasticache_subnet_group_name
security_group_ids = [aws_security_group.redis.id]
replication_group_id = var.prefix
num_cache_clusters = 3
node_type = "cache.m6g.large"
engine_version = "5.0.6"
port = "6379"
snapshot_retention_limit = 0
automatic_failover_enabled = true
at_rest_encryption_enabled = false #tfsec:ignore:aws-elasticache-enable-at-rest-encryption
transit_encryption_enabled = false #tfsec:ignore:aws-elasticache-enable-in-transit-encryption
apply_immediately = true
description = var.prefix
}
resource "aws_elasticache_parameter_group" "main" { #tfsec:ignore:aws-vpc-add-description-to-security-group-rule
name = var.prefix
family = "redis5.0"
parameter {
name = "client-output-buffer-limit-pubsub-hard-limit"
value = "0"
}
parameter {
name = "client-output-buffer-limit-pubsub-soft-limit"
value = "0"
}
parameter {
name = "databases"
value = "65536"
}
}
resource "aws_security_group" "redis" { #tfsec:ignore:aws-cloudwatch-log-group-customer-key tfsec:ignore:aws-vpc-add-description-to-security-group
name = "${var.prefix}-redis"
vpc_id = var.vpc.vpc_id
description = "${var.prefix}-redis"
ingress {
from_port = 6379
to_port = 6397
protocol = "TCP"
cidr_blocks = var.vpc.private_subnets_cidr_blocks
}
}
output "redis_cluster" {
value = aws_elasticache_replication_group.main
}

View File

@ -0,0 +1,14 @@
variable "prefix" {}
variable "allowed_security_groups" {
type = list(string)
default = []
}
variable "eks_allowed_roles" {
type = list(any)
default = []
}
variable "vpc" {}
variable "base_domain" {}

View File

@ -0,0 +1,8 @@
bucket = "fleet-terraform-state20220408141538466600000002"
key = "fleet-cloud-sandbox-prod/sandbox/terraform.tfstate" # This should be set to account_alias/unique_key/terraform.tfstate
workspace_key_prefix = "fleet-cloud-sandbox-prod" # This should be set to the account alias
region = "us-east-2"
encrypt = true
kms_key_id = "9f98a443-ffd7-4dbe-a9c3-37df89b2e42a"
dynamodb_table = "tf-remote-state-lock"
role_arn = "arn:aws:iam::353365949058:role/terraform-fleet-cloud-sandbox-prod"

View File

@ -0,0 +1,242 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.10.0"
}
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
git = {
source = "paultyng/git"
version = "~> 0.1.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1.2"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 3.18.0"
}
}
backend "s3" {}
}
provider "aws" {
region = "us-east-2"
default_tags {
tags = {
environment = "fleet-demo-${terraform.workspace}"
terraform = "https://github.com/fleetdm/fleet/tree/main/infrastructure/demo"
state = "s3://fleet-loadtesting-tfstate/demo-environment"
}
}
}
provider "aws" {
alias = "replica"
region = "us-west-1"
default_tags {
tags = {
environment = "fleet-demo-${terraform.workspace}"
terraform = "https://github.com/fleetdm/fleet/tree/main/infrastructure/demo"
state = "s3://fleet-loadtesting-tfstate/demo-environment"
}
}
}
provider "cloudflare" {}
provider "random" {}
data "aws_ecr_authorization_token" "token" {}
provider "docker" {
# Configuration options
registry_auth {
address = "${data.aws_caller_identity.current.account_id}.dkr.ecr.us-east-2.amazonaws.com"
username = data.aws_ecr_authorization_token.token.user_name
password = data.aws_ecr_authorization_token.token.password
}
}
provider "git" {}
data "aws_caller_identity" "current" {}
data "git_repository" "tf" {
path = "${path.module}/../../"
}
locals {
prefix = "sandbox-prod"
base_domain = "sandbox.fleetdm.com"
}
data "aws_iam_policy_document" "kms" {
statement {
actions = ["kms:*"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
}
resources = ["*"]
}
statement {
actions = [
"kms:Encrypt*",
"kms:Decrypt*",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Describe*",
]
resources = ["*"]
principals {
type = "Service"
# TODO hard coded region
identifiers = ["logs.us-east-2.amazonaws.com"]
}
}
}
resource "aws_kms_key" "main" {
policy = data.aws_iam_policy_document.kms.json
enable_key_rotation = true
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.12.0"
name = local.prefix
cidr = "10.11.0.0/16"
# TODO hard coded AZs
azs = ["us-east-2a", "us-east-2b", "us-east-2c"]
private_subnets = ["10.11.16.0/20", "10.11.32.0/20", "10.11.48.0/20"]
public_subnets = ["10.11.128.0/24", "10.11.129.0/24", "10.11.130.0/24"]
database_subnets = ["10.11.131.0/24", "10.11.132.0/24", "10.11.133.0/24"]
elasticache_subnets = ["10.11.134.0/24", "10.11.135.0/24", "10.11.136.0/24"]
create_database_subnet_group = false
create_database_subnet_route_table = true
create_elasticache_subnet_group = true
create_elasticache_subnet_route_table = true
enable_vpn_gateway = false
one_nat_gateway_per_az = false
single_nat_gateway = true
enable_nat_gateway = true
}
module "shared-infrastructure" {
source = "./SharedInfrastructure"
prefix = local.prefix
vpc = module.vpc
allowed_security_groups = [module.pre-provisioner.lambda_security_group.id]
eks_allowed_roles = [module.pre-provisioner.lambda_role, module.jit-provisioner.deprovisioner_role]
base_domain = local.base_domain
}
module "pre-provisioner" {
source = "./PreProvisioner"
prefix = local.prefix
vpc = module.vpc
kms_key = aws_kms_key.main
dynamodb_table = aws_dynamodb_table.lifecycle-table
remote_state = module.remote_state
mysql_secret = module.shared-infrastructure.mysql_secret
eks_cluster = module.shared-infrastructure.eks_cluster
redis_cluster = module.shared-infrastructure.redis_cluster
ecs_cluster = aws_ecs_cluster.main
base_domain = local.base_domain
}
module "jit-provisioner" {
source = "./JITProvisioner"
prefix = local.prefix
vpc = module.vpc
kms_key = aws_kms_key.main
dynamodb_table = aws_dynamodb_table.lifecycle-table
remote_state = module.remote_state
mysql_secret = module.shared-infrastructure.mysql_secret
eks_cluster = module.shared-infrastructure.eks_cluster
redis_cluster = module.shared-infrastructure.redis_cluster
alb_listener = module.shared-infrastructure.alb_listener
ecs_cluster = aws_ecs_cluster.main
base_domain = local.base_domain
}
module "monitoring" {
source = "./Monitoring"
prefix = local.prefix
slack_webhook = var.slack_webhook
kms_key = aws_kms_key.main
lb = module.shared-infrastructure.lb
jitprovisioner = module.jit-provisioner.jitprovisioner
deprovisioner = module.jit-provisioner.deprovisioner
dynamodb_table = aws_dynamodb_table.lifecycle-table
}
resource "aws_dynamodb_table" "lifecycle-table" {
name = "${local.prefix}-lifecycle"
billing_mode = "PAY_PER_REQUEST"
hash_key = "ID"
server_side_encryption {
enabled = true
kms_key_arn = aws_kms_key.main.arn
}
point_in_time_recovery {
enabled = true
}
attribute {
name = "ID"
type = "S"
}
attribute {
name = "State"
type = "S"
}
attribute {
name = "redis_db"
type = "N"
}
global_secondary_index {
name = "RedisDatabases"
hash_key = "redis_db"
projection_type = "KEYS_ONLY"
}
global_secondary_index {
name = "FleetState"
hash_key = "State"
projection_type = "ALL"
}
}
module "remote_state" {
source = "nozaq/remote-state-s3-backend/aws"
tags = {}
providers = {
aws = aws
aws.replica = aws.replica
}
}
resource "aws_ecs_cluster" "main" {
name = local.prefix
setting {
name = "containerInsights"
value = "enabled"
}
}
variable "slack_webhook" {}

View File

@ -0,0 +1,15 @@
## Terraform for the Fleet Demo Environment
This folder holds the infrastructure code for Fleet's demo environment. See https://github.com/fleetdm/fleet-infra/pull/3 for design documentation.
The interface into this code is designed to be minimal.
If you require changes beyond whats described here, contact @zwinnerman-fleetdm.
### Deploying your code to the loadtesting environment
1. Initialize your terraform environment with `terraform init`
2. Check out the appropiate workspace for your code, for instance `terraform workspace select production`
3. Apply terraform with your branch name with `terraform apply -var tag=BRANCH_NAME -var-file production.tfvars`
### Bugs
1. module.shared-infrastructure.kubernetes_manifest.targetgroupbinding is bugged sometimes, if it gives issues just comment it out
1. on a fresh apply, module.shared-infrastructure.aws_acm_certificate.main will have to be targeted first, then a normal apply can follow
1. If errors happen, see if applying again will fix it