Add 'orbit/' from commit 'ab3047bb39f1e2be331d1ff18b4eb768619033c4'

git-subtree-dir: orbit
git-subtree-mainline: d5974aad97
git-subtree-split: ab3047bb39
This commit is contained in:
Tomas Touceda 2021-08-04 16:58:25 -03:00
commit 3ac8494d23
60 changed files with 5288 additions and 0 deletions

1
orbit/.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.go text eol=lf

32
orbit/.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: build-go
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

View file

@ -0,0 +1,19 @@
name: golangci-lint
on:
push:
branches:
- main
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be
# specified without patch version: we always use the latest patch
# version.
version: v1.33

24
orbit/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
.DS_Store
.idea
*.log
tmp/
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Build/packaging results
dist/
*.pkg
*.deb
*.rpm
*.msi

77
orbit/.goreleaser.yml Normal file
View file

@ -0,0 +1,77 @@
before:
hooks:
- go mod download
builds:
- id: orbit
dir: ./cmd/orbit/
binary: orbit
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
goarch:
- amd64
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser
hooks:
post: ./tools/build/sign-macos.sh {{ .Path }}
- id: orbit-package
dir: ./cmd/package/
binary: orbit-package
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
goarch:
- amd64
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser
hooks:
post: ./tools/build/sign-macos.sh {{ .Path }}
archives:
- id: orbit
builds:
- orbit
name_template: orbit_{{.Version}}_{{.Os}}
replacements:
darwin: macos
format_overrides:
- goos: windows
format: zip
- id: orbit-package
builds:
- orbit-package
name_template: orbit-package_{{.Version}}_{{.Os}}
replacements:
darwin: macos
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-untagged"
changelog:
skip: true
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
release:
github:
owner: fleetdm
name: orbit
draft: true

7
orbit/LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright (c) 2021-present Fleet Device Management Inc
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

203
orbit/README.md Normal file
View file

@ -0,0 +1,203 @@
<img width="200" alt="Fleet logo, landscape, dark text, transparent background" src="https://user-images.githubusercontent.com/618009/103300491-9197e280-49c4-11eb-8677-6b41027be800.png">
# Orbit osquery
Orbit is an [osquery](https://github.com/osquery/osquery) runtime and autoupdater. With Orbit, it's easy to deploy osquery, manage configurations, and stay up to date. Orbit eases the deployment of osquery connected with a [Fleet server](https://github.com/fleetdm/fleet), and is a (near) drop-in replacement for osquery in a variety of deployment scenarios.
Orbit is the recommended agent for Fleet. But Orbit can be used with or without Fleet, and Fleet can be used with or without Orbit.
## Try Orbit
#### With [`fleetctl preview` already running](https://github.com/fleetdm/fleet#try-fleet) and [Go](https://golang.org/doc/install) 1.16 installed:
```bash
# From within the top-level directory of this repo…
# Generate a macOS installer pointed at your local Fleet
go run ./cmd/package --type=pkg --fleet-url=localhost:8412 --insecure --enroll-secret=YOUR_FLEET_ENROLL_SECRET_HERE
```
> With fleetctl preview running, you can find your Fleet enroll secret by selecting the "Add new host" button on the Hosts page in the Fleet UI.
An installer configured to point at your Fleet instance has now been generated.
Now run that installer (double click, on a Mac) to enroll your own computer as a host in Fleet. Refresh after several seconds (≈30s), and you should now see your local computer as a new host in Fleet.
## Bugs
To report a bug or request a feature, [click here](https://github.com/fleetdm/fleet/issues).
## Capabilities
| Capability | Status |
| ------------------------------------ | ------ |
| Secure autoupdate for osquery | ✅ |
| Secure autoupdate for Orbit | ✅ |
| Configurable update channels | ✅ |
| Full osquery flag customization | ✅ |
| Package tooling for macOS `.pkg` | ✅ |
| Package tooling for Linux `.deb` | ✅ |
| Package tooling for Linux `.rpm` | ✅ |
| Package tooling for Windows `.msi` | ✅ |
| Manage/update osquery extensions | 🔜 |
| Manage cgroups for Linux performance | 🔜 |
## Usage
General information and flag documentation can be accessed by running `orbit --help`.
### Permissions
Orbit generally expects root permissions to be able to create and access it's working files.
To get root level permissions:
#### macOS/Linux
Prefix `orbit` commands with `sudo` (`sudo orbit ...`) or run in a root shell.
#### Windows
Run Powershell or cmd.exe with "Run as administrator" and start `orbit` commands from that shell.
### Osquery shell
Run an `osqueryi` shell with `orbit osqueryi` or `orbit shell`.
### Connect to a Fleet server
Use the `--fleet-url` and `--enroll-secret` flags to connect to a Fleet server.
For example:
```sh
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value
```
Use `--fleet_certificate` to provide a path to a certificate bundle when necessary for osquery to verify the authenticity of the Fleet server (typically when using a Windows client or self-signed certificates):
```sh
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --fleet-certificate=cert.pem
```
Add the `--insecure` flag for connections using otherwise invalid certificates:
```sh
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --insecure
```
### Osquery flags
Orbit can be used as near drop-in replacement for `osqueryd`, enhancing standard osquery with autoupdate capabilities. Orbit passes through any options after `--` directly to the `osqueryd` instance.
For example, the following would be a typical drop-in usage of Orbit:
```sh
orbit -- --flagfile=flags.txt
```
## Packaging
Orbit, like standalone osquery, is typically deployed via OS-specific packages. Tooling is provided with this repository to generate installation packages.
### Dependencies
Orbit currently supports building packages on macOS and Linux.
Before building packages, clone or download this repository and [install Go](https://golang.org/doc/install).
Building Windows packages requires Docker to be installed.
### Packaging support
- **macOS** - `.pkg` package generation with (optional) [Notarization](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution) and codesigning - Persistence via `launchd`.
- **Linux** - `.deb` (Debian, Ubuntu, etc.) & `.rpm` (RHEL, CentOS, etc.) package generation - Persistence via `systemd`.
- **Windows** - `.msi` package generation - Persistence via Services.
### Building packages
Use `go run ./cmd/package` from this directory to run the packaging tools.
The only required parameter is `--type`, use one of `deb`, `rpm`, `pkg`, or `msi`.
Configure osquery to connect to a Fleet (or other TLS) server with the `--fleet-url` and `--enroll-secret` flags.
A minimal invocation for communicating with Fleet:
```sh
go run ./cmd/package --type deb --fleet-url=fleet.example.com --enroll-secret=notsosecret
```
This will build a `.deb` package configured to communicate with a Fleet server at `fleet.example.com` using the enroll secret `notsosecret`.
When the Fleet server uses a self-signed (or otherwise invalid) TLS certificate, package with the `--insecure` or `--fleet-certificate` options.
See `go run ./cmd/package` for the full range of packaging options.
#### Update channels
Orbit uses the concept of "update channels" to determine the version of Orbit, osquery, and any extensions (extension support coming soon) to run. This concept is modeled from the common versioning convention for Docker containers.
Configure update channels for Orbit and osqueryd with the `--orbit-channel` and `--osqueryd-channel` flags when packaging.
| Channel | Versions |
| ------- | -------- |
| `4` | 4.x.x |
| `4.6` | 4.6.x |
| `4.6.0` | 4.6.0 |
Additionally `stable` and `edge` are special channel names. `stable` will always return the version Fleet deems to be stable, while `edge` will provide newer releases for beta testing.
#### macOS signing & Notarization
Orbit's packager can automate the codesigning and Notarization steps to allow the resulting package to generate packages that appear "trusted" when install on macOS hosts. Signing & notarization are supported only on macOS hosts.
For signing, a "Developer ID Installer" certificate must be available on the build machine ([generation instructions](https://help.apple.com/xcode/mac/current/#/dev154b28f09)). Use `security find-identity -v` to verify the existence of this certificate and make note of the identifier provided in the left column.
For Notarization, valid App Store Connect credentials must be available on the build machine. Set these in the environment variables `AC_USERNAME` and `AC_PASSWORD`. It is common to configure this via [app-specific passwords](https://support.apple.com/en-ca/HT204397).
Build a signed and notarized macOS package with an invocation like the following:
```sh
AC_USERNAME=zach@example.com AC_PASSWORD=llpk-sije-kjlz-jdzw go run ./cmd/package --type=pkg --fleet-url=fleet.example.com --enroll-secret=63SBzTT+2UyW --sign-identity 3D7260BF99539C6E80A94835A8921A988F4E6498 --notarize
```
This process may take several minutes to complete as the Notarization process completes on Apple's servers.
After successful notarization, the generated "ticket" is automatically stapled to the package.
## FAQs
### How does Orbit compare with Kolide Launcher?
Orbit is inspired by the success of [Kolide Launcher](https://github.com/kolide/launcher), and approaches a similar problem domain with new strategies informed by the challenges encountered in real world deployments. Orbit does not share any code with Launcher.
- Both Orbit and Launcher use [The Update Framework](https://theupdateframework.com/) specification for managing updates. Orbit utilizes the official [go-tuf](https://github.com/theupdateframework/go-tuf) library, while Launcher has it's own implementation of the specification.
- Orbit can be deployed as a (near) drop-in replacement for osquery, supporting full customization of the osquery flags. Launcher heavily manages the osquery flags making deployment outside of Fleet or Kolide's SaaS difficult.
- Orbit prefers the battle-tested plugins of osquery. Orbit uses the built-in logging, configuration, and live query plugins, while Launcher uses custom implementations.
- Orbit prefers the built-in osquery remote APIs. Launcher utilizes a custom gRPC API that has led to issues with character encoding, load balancers/proxies, and request size limits.
- Orbit encourages use of the osquery performance Watchdog, while Launcher disables the Watchdog.
Additionally, Orbit aims to tackle problems out of scope for Launcher:
- Configure updates via release channels, providing more granular control over agent versioning.
- Support for deploying and updating osquery extensions (🔜).
- Manage osquery versions and startup flags from a remote (Fleet) server (🔜).
- Further control of osquery performance via cgroups (🔜).
### Is Orbit Free?
Yes! Orbit is licensed under an MIT license and all uses are encouraged.
### How does orbit update osquery? And how do the stable and edge channels get triggered to update osquery on a self hosted Fleet instance?
Orbit uses a configurable update server. We expect that many folks will just use the update server we manage (similar to what Kolide does with Launcher's update server). We are also offering [tooling for self-managing an update server](https://github.com/fleetdm/fleet/blob/master/docs/3-Deployment/4-fleetctl-agent-updates.md) as part of Fleet Basic (the subscription offering).
## Community
#### Chat
Please join us in the #fleet channel on [osquery Slack](https://osquery.slack.com/join/shared_invite/zt-h29zm0gk-s2DBtGUTW4CFel0f0IjTEw#/).
<a href="https://fleetdm.com"><img alt="Banner featuring a futuristic cloud city with the Fleet logo" src="https://user-images.githubusercontent.com/618009/98254443-eaf21100-1f41-11eb-9e2c-63a0545601f3.jpg"/></a>

1
orbit/changes/.keep Normal file
View file

@ -0,0 +1 @@
IGNORE BUT DO NOT REMOVE. THIS IS HERE SO THAT THE DIRECTORY REMAINS

View file

@ -0,0 +1 @@
* Fix permissions for building MSI when packaging as root user. Fixes fleetdm/fleet#1424

377
orbit/cmd/orbit/orbit.go Normal file
View file

@ -0,0 +1,377 @@
package main
import (
"context"
"crypto/x509"
"fmt"
"io/fs"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/dgraph-io/badger/v2"
"github.com/fleetdm/orbit/pkg/certificate"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/database"
"github.com/fleetdm/orbit/pkg/insecure"
"github.com/fleetdm/orbit/pkg/osquery"
"github.com/fleetdm/orbit/pkg/update"
"github.com/fleetdm/orbit/pkg/update/filestore"
"github.com/oklog/run"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var (
// Flags set by goreleaser during build
version = ""
commit = ""
date = ""
)
func main() {
app := cli.NewApp()
app.Name = "Orbit osquery"
app.Usage = "A powered-up, (near) drop-in replacement for osquery"
app.Commands = []*cli.Command{
versionCommand,
shellCommand,
}
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "root-dir",
Usage: "Root directory for Orbit state",
Value: update.DefaultOptions.RootDirectory,
EnvVars: []string{"ORBIT_ROOT_DIR"},
},
&cli.BoolFlag{
Name: "insecure",
Usage: "Disable TLS certificate verification",
EnvVars: []string{"ORBIT_INSECURE"},
},
&cli.StringFlag{
Name: "fleet-url",
Usage: "URL (host:port) of Fleet server",
EnvVars: []string{"ORBIT_FLEET_URL"},
},
&cli.StringFlag{
Name: "fleet-certificate",
Usage: "Path to server cerificate bundle",
EnvVars: []string{"ORBIT_FLEET_CERTIFICATE"},
},
&cli.StringFlag{
Name: "update-url",
Usage: "URL for update server",
Value: "https://tuf.fleetctl.com",
EnvVars: []string{"ORBIT_UPDATE_URL"},
},
&cli.StringFlag{
Name: "enroll-secret",
Usage: "Enroll secret for authenticating to Fleet server",
EnvVars: []string{"ORBIT_ENROLL_SECRET"},
},
&cli.StringFlag{
Name: "enroll-secret-path",
Usage: "Path to file containing enroll secret",
EnvVars: []string{"ORBIT_ENROLL_SECRET_PATH"},
},
&cli.StringFlag{
Name: "osqueryd-channel",
Usage: "Update channel of osqueryd to use",
Value: "stable",
EnvVars: []string{"ORBIT_OSQUERYD_CHANNEL"},
},
&cli.StringFlag{
Name: "orbit-channel",
Usage: "Update channel of Orbit to use",
Value: "stable",
EnvVars: []string{"ORBIT_ORBIT_CHANNEL"},
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable debug logging",
EnvVars: []string{"ORBIT_DEBUG"},
},
&cli.BoolFlag{
Name: "version",
Usage: "Get Orbit version",
},
&cli.StringFlag{
Name: "log-file",
Usage: "Log to this file path in addition to stderr",
},
}
app.Action = func(c *cli.Context) error {
if c.Bool("version") {
fmt.Println("orbit " + version)
return nil
}
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true})
if logfile := c.String("log-file"); logfile != "" {
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND, 0o600)
if err != nil {
return errors.Wrap(err, "open logfile")
}
log.Logger = log.Output(zerolog.MultiLevelWriter(
zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true},
zerolog.ConsoleWriter{Out: f, TimeFormat: time.RFC3339Nano, NoColor: true},
))
}
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if c.Bool("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
if c.Bool("insecure") && c.String("fleet-certificate") != "" {
return errors.New("insecure and fleet-certificate may not be specified together")
}
if c.String("enroll-secret-path") != "" {
if c.String("enroll-secret") != "" {
return errors.New("enroll-secret and enroll-secret-path may not be specified together")
}
b, err := ioutil.ReadFile(c.String("enroll-secret-path"))
if err != nil {
return errors.Wrap(err, "read enroll secret file")
}
if err := c.Set("enroll-secret", strings.TrimSpace(string(b))); err != nil {
return errors.Wrap(err, "set enroll secret from file")
}
}
if err := os.MkdirAll(c.String("root-dir"), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "initialize root dir")
}
dbPath := filepath.Join(c.String("root-dir"), "orbit.db")
db, err := database.Open(dbPath)
if err != nil {
if errors.Is(err, badger.ErrTruncateNeeded) {
db, err = database.OpenTruncate(dbPath)
if err != nil {
return err
}
log.Warn().Msg("Open badger required truncate. Data loss is possible.")
} else {
return err
}
}
defer func() {
if err := db.Close(); err != nil {
log.Error().Err(err).Msg("Close badger")
}
}()
localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json"))
if err != nil {
log.Fatal().Err(err).Msg("failed to create local metadata store")
}
// Initialize updater and get expected version
opt := update.DefaultOptions
opt.RootDirectory = c.String("root-dir")
opt.ServerURL = c.String("update-url")
opt.LocalStore = localStore
opt.InsecureTransport = c.Bool("insecure")
updater, err := update.New(opt)
if err != nil {
return err
}
if err := updater.UpdateMetadata(); err != nil {
log.Info().Err(err).Msg("failed to update metadata. using saved metadata.")
}
osquerydPath, err := updater.Get("osqueryd", c.String("osqueryd-channel"))
if err != nil {
return err
}
// Clear leftover files from updates
if err := filepath.Walk(c.String("root-dir"), func(path string, info fs.FileInfo, err error) error {
// Ignore anything not containing .old extension
if !strings.HasSuffix(path, ".old") {
return nil
}
if err := os.RemoveAll(path); err != nil {
log.Info().Err(err).Msg("failed to remove .old")
return nil
}
log.Debug().Str("path", path).Msg("cleaned up old")
return nil
}); err != nil {
return errors.Wrap(err, "cleanup old files")
}
var g run.Group
updateRunner, err := update.NewRunner(updater, update.RunnerOptions{
CheckInterval: 10 * time.Second,
Targets: map[string]string{
"osqueryd": c.String("osqueryd-channel"),
"orbit": c.String("orbit-channel"),
},
})
if err != nil {
return err
}
g.Add(updateRunner.Execute, updateRunner.Interrupt)
var options []func(*osquery.Runner) error
options = append(options, osquery.WithDataPath(c.String("root-dir")))
fleetURL := c.String("fleet-url")
if !strings.HasPrefix(fleetURL, "http") {
fleetURL = "https://" + fleetURL
}
enrollSecret := c.String("enroll-secret")
if enrollSecret != "" {
options = append(options,
osquery.WithEnv([]string{"ENROLL_SECRET=" + enrollSecret}),
osquery.WithFlags([]string{"--enroll_secret_env=ENROLL_SECRET"}),
)
}
if fleetURL != "https://" && c.Bool("insecure") {
proxy, err := insecure.NewTLSProxy(fleetURL)
if err != nil {
return errors.Wrap(err, "create TLS proxy")
}
g.Add(
func() error {
log.Info().
Str("addr", fmt.Sprintf("localhost:%d", proxy.Port)).
Str("target", c.String("fleet-url")).
Msg("using insecure TLS proxy")
err := proxy.InsecureServeTLS()
return err
},
func(error) {
if err := proxy.Close(); err != nil {
log.Error().Err(err).Msg("close proxy")
}
},
)
certPath := filepath.Join(os.TempDir(), "fleet.crt")
// Write cert that proxy uses
err = ioutil.WriteFile(certPath, []byte(insecure.ServerCert), os.ModePerm)
if err != nil {
return errors.Wrap(err, "write server cert")
}
// Rewrite URL to the proxy URL. Note the proxy handles any URL
// prefix so we don't need to carry that over here.
parsedURL := &url.URL{
Scheme: "https",
Host: fmt.Sprintf("localhost:%d", proxy.Port),
}
// Check and log if there are any errors with TLS connection.
pool, err := certificate.LoadPEM(certPath)
if err != nil {
return errors.Wrap(err, "load certificate")
}
if err := certificate.ValidateConnection(pool, fleetURL); err != nil {
log.Info().Err(err).Msg("Failed to connect to Fleet server. Osquery connection may fail.")
}
options = append(options,
osquery.WithFlags(osquery.FleetFlags(parsedURL)),
osquery.WithFlags([]string{"--tls_server_certs", certPath}),
)
} else if fleetURL != "https://" {
if enrollSecret == "" {
return errors.New("enroll secret must be specified to connect to Fleet server")
}
parsedURL, err := url.Parse(fleetURL)
if err != nil {
return errors.Wrap(err, "parse URL")
}
options = append(options,
osquery.WithFlags(osquery.FleetFlags(parsedURL)),
)
if certPath := c.String("fleet-certificate"); certPath != "" {
// Check and log if there are any errors with TLS connection.
pool, err := certificate.LoadPEM(certPath)
if err != nil {
return errors.Wrap(err, "load certificate")
}
if err := certificate.ValidateConnection(pool, fleetURL); err != nil {
log.Info().Err(err).Msg("Failed to connect to Fleet server. Osquery connection may fail.")
}
options = append(options,
osquery.WithFlags([]string{"--tls_server_certs=" + certPath}),
)
} else {
// Check and log if there are any errors with TLS connection.
pool, err := x509.SystemCertPool()
if err != nil {
log.Info().Err(err).Msg("Failed to retrieve system cert pool. Cannot validate Fleet server connection.")
} else if err := certificate.ValidateConnection(pool, fleetURL); err != nil {
log.Info().Err(err).Msg("Failed to connect to Fleet server. Osquery connection may fail. Provide certificate with --fleet-certificate.")
}
}
}
// --force is sometimes needed when an older osquery process has not
// exited properly
options = append(options, osquery.WithFlags([]string{"--force"}))
if c.Bool("debug") {
options = append(options,
osquery.WithFlags([]string{"--verbose", "--tls_dump"}),
)
}
// Handle additional args after --
options = append(options, osquery.WithFlags(c.Args().Slice()))
// Create an osquery runner with the provided options
r, _ := osquery.NewRunner(osquerydPath, options...)
g.Add(r.Execute, r.Interrupt)
// Install a signal handler
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
g.Add(run.SignalHandler(ctx, os.Interrupt, os.Kill))
if err := g.Run(); err != nil {
log.Error().Err(err).Msg("unexpected exit")
}
return nil
}
if err := app.Run(os.Args); err != nil {
log.Error().Err(err).Msg("")
}
}
var versionCommand = &cli.Command{
Name: "version",
Usage: "Get the orbit version",
Flags: []cli.Flag{},
Action: func(c *cli.Context) error {
fmt.Println("orbit " + version)
fmt.Println("commit - " + commit)
fmt.Println("date - " + date)
return nil
},
}

90
orbit/cmd/orbit/shell.go Normal file
View file

@ -0,0 +1,90 @@
package main
import (
"context"
"os"
"path/filepath"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/osquery"
"github.com/fleetdm/orbit/pkg/update"
"github.com/fleetdm/orbit/pkg/update/filestore"
"github.com/oklog/run"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var shellCommand = &cli.Command{
Name: "shell",
Aliases: []string{"osqueryi"},
Usage: "Run the osqueryi shell",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "osqueryd-channel",
Usage: "Channel of osqueryd version to use",
Value: "stable",
EnvVars: []string{"ORBIT_OSQUERYD_CHANNEL"},
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable debug logging",
EnvVars: []string{"ORBIT_DEBUG"},
},
},
Action: func(c *cli.Context) error {
if c.Bool("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
if err := os.MkdirAll(c.String("root-dir"), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "initialize root dir")
}
localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json"))
if err != nil {
log.Fatal().Err(err).Msg("failed to create local metadata store")
}
// Initialize updater and get expected version
opt := update.DefaultOptions
opt.RootDirectory = c.String("root-dir")
opt.ServerURL = c.String("update-url")
opt.LocalStore = localStore
opt.InsecureTransport = c.Bool("insecure")
updater, err := update.New(opt)
if err != nil {
return err
}
if err := updater.UpdateMetadata(); err != nil {
log.Info().Err(err).Msg("failed to update metadata. using saved metadata.")
}
osquerydPath, err := updater.Get("osqueryd", c.String("osqueryd-channel"))
if err != nil {
return err
}
var g run.Group
// Create an osquery runner with the provided options
r, _ := osquery.NewRunner(
osquerydPath,
osquery.WithShell(),
// Handle additional args after --
osquery.WithFlags(c.Args().Slice()),
)
g.Add(r.Execute, r.Interrupt)
// Install a signal handler
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
g.Add(run.SignalHandler(ctx, os.Interrupt, os.Kill))
if err := g.Run(); err != nil {
log.Error().Err(err).Msg("unexpected exit")
}
return nil
},
}

View file

@ -0,0 +1,142 @@
package main
import (
"os"
"time"
"github.com/fleetdm/orbit/pkg/packaging"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
func main() {
var opt packaging.Options
log.Logger = log.Output(
zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano},
)
zerolog.SetGlobalLevel(zerolog.InfoLevel)
app := cli.NewApp()
app.Name = "Orbit osquery"
app.Usage = "A powered-up, (near) drop-in replacement for osquery"
app.Commands = []*cli.Command{}
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "type",
Usage: "Type of package to build",
Required: true,
},
&cli.StringFlag{
Name: "enroll-secret",
Usage: "Enroll secret for authenticating to Fleet server",
Destination: &opt.EnrollSecret,
},
&cli.StringFlag{
Name: "fleet-url",
Usage: "URL (host:port) of Fleet server",
Destination: &opt.FleetURL,
},
&cli.StringFlag{
Name: "fleet-certificate",
Usage: "Path to server cerificate bundle",
Destination: &opt.FleetCertificate,
},
&cli.StringFlag{
Name: "identifier",
Usage: "Identifier for package product",
Value: "com.fleetdm.orbit",
Destination: &opt.Identifier,
},
&cli.StringFlag{
Name: "version",
Usage: "Version for package product",
Value: "0.0.3",
Destination: &opt.Version,
},
&cli.BoolFlag{
Name: "insecure",
Usage: "Disable TLS certificate verification",
Destination: &opt.Insecure,
},
&cli.BoolFlag{
Name: "service",
Usage: "Install orbit/osquery with a persistence service (launchd, systemd, etc.)",
Value: true,
Destination: &opt.StartService,
},
&cli.StringFlag{
Name: "sign-identity",
Usage: "Identity to use for macOS codesigning",
Destination: &opt.SignIdentity,
},
&cli.BoolFlag{
Name: "notarize",
Usage: "Whether to notarize macOS packages",
Destination: &opt.Notarize,
},
&cli.StringFlag{
Name: "osqueryd-channel",
Usage: "Update channel of osqueryd to use",
Value: "stable",
Destination: &opt.OsquerydChannel,
},
&cli.StringFlag{
Name: "orbit-channel",
Usage: "Update channel of Orbit to use",
Value: "stable",
Destination: &opt.OrbitChannel,
},
&cli.StringFlag{
Name: "update-url",
Usage: "URL for update server",
Value: "https://tuf.fleetctl.com",
Destination: &opt.UpdateURL,
},
&cli.StringFlag{
Name: "update-roots",
Usage: "Root key JSON metadata for update server (from fleetctl updates roots)",
Destination: &opt.UpdateRoots,
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable debug logging",
Destination: &opt.Debug,
},
}
app.Before = func(c *cli.Context) error {
if opt.Debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
return nil
}
app.Action = func(c *cli.Context) error {
if opt.FleetURL != "" || opt.EnrollSecret != "" {
if opt.FleetURL == "" || opt.EnrollSecret == "" {
return errors.New("--enroll-secret and --fleet-url must be provided together")
}
}
if opt.Insecure && opt.FleetCertificate != "" {
return errors.New("--insecure and --fleet-certificate may not be provided together")
}
switch c.String("type") {
case "pkg":
return packaging.BuildPkg(opt)
case "deb":
return packaging.BuildDeb(opt)
case "rpm":
return packaging.BuildRPM(opt)
case "msi":
return packaging.BuildMSI(opt)
default:
return errors.New("type must be one of ('pkg', 'deb', 'rpm', 'msi')")
}
}
if err := app.Run(os.Args); err != nil {
log.Fatal().Err(err).Msg("package failed")
}
}

19
orbit/go.mod Normal file
View file

@ -0,0 +1,19 @@
module github.com/fleetdm/orbit
go 1.15
require (
github.com/dgraph-io/badger/v2 v2.2007.2
github.com/fatih/color v1.10.0
github.com/goreleaser/nfpm/v2 v2.2.2
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
github.com/mitchellh/gon v0.2.3
github.com/oklog/run v1.1.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.20.0
github.com/stretchr/testify v1.6.1
github.com/theupdateframework/go-tuf v0.0.0-20201230183259-aee6270feb55
github.com/urfave/cli/v2 v2.3.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
)

852
orbit/go.sum Normal file
View file

@ -0,0 +1,852 @@
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a h1:wFEQiK85fRsEVF0CRrPAos5LoAryUsIX1kPW/WrIqFw=
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Djarvur/go-err113 v0.1.0 h1:uCRZZOdMQ0TZPHYTdYpoC0bLYJKPEHPUJ8MeAa51lNU=
github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/ashanbrown/forbidigo v1.0.0 h1:QdNXBduDUopc3GW+YVYZn8jzmIMklQiCfdN2N5+dQeE=
github.com/ashanbrown/forbidigo v1.0.0/go.mod h1:PH+zMRWE15yW69fYfe7Kn8nYR6yYyafc3ntEGh2BBAg=
github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a h1:/U9tbJzDRof4fOR51vwzWdIBsIH6R2yU0KG1MBRM2Js=
github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bombsimon/wsl/v3 v3.1.0 h1:E5SRssoBgtVFPcYWUOFJEcgaySgdtTNYzsSKDOY7ss8=
github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+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/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
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 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daixiang0/gci v0.2.7 h1:bosLNficubzJZICsVzxuyNc6oAbdz0zcqLG2G/RxtY4=
github.com/daixiang0/gci v0.2.7/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc=
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingajkin/go-header v0.4.2 h1:jEeSF4sdv8/3cT/WY8AgDHUoItNSoEZ7qg9dX7pc218=
github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA=
github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
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 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz5Co8TFO8EupIdlcpwShBmY98dkT2xeHkvEI=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-critic/go-critic v0.5.2 h1:3RJdgf6u4NZUumoP8nzbqiiNT8e1tC2Oc7jlgqre/IA=
github.com/go-critic/go-critic v0.5.2/go.mod h1:cc0+HvdE3lFpqLecgqMaJcvWWH77sLdBp+wLGPM1Yyo=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ=
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
github.com/go-toolsmith/pkgload v1.0.0 h1:4DFWWMXVfbcN5So1sBNW9+yeiMqLFGl1wFLTL5R0Tgg=
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk=
github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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.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.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy7WKgLXmpQ5bHTrq5GDsp8R9Qs67g0=
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.34.1 h1:xf1yVlLBNeCIoOHWXhwqnUeaqzONllRSgiLSahNt0Mw=
github.com/golangci/golangci-lint v1.34.1/go.mod h1:6Bnn7T0JYin7uukitgL6f9E9auQatlT0RMNOKG9lSHU=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
github.com/golangci/misspell v0.3.5 h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo=
github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 h1:XQKc8IYQOeRwVs36tDrEmTgDgP88d5iEURwpmtiAlOM=
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.0.0-20201225075926-0a97c2c4b688 h1:jyGRCFMyDK/gKe6QRZQWci5+wEUBFElvxLHs3iwO3hY=
github.com/google/rpmpack v0.0.0-20201225075926-0a97c2c4b688/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/goreleaser/chglog v0.1.2 h1:tdzAb/ILeMnphzI9zQ7Nkq+T8R9qyXli8GydD8plFRY=
github.com/goreleaser/chglog v0.1.2/go.mod h1:tTZsFuSZK4epDXfjMkxzcGbrIOXprf0JFp47BjIr3B8=
github.com/goreleaser/fileglob v0.3.1 h1:OTFDWqUUHjQazk2N5GdUqEbqT/grBnRARaAXsV07q1Y=
github.com/goreleaser/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
github.com/goreleaser/nfpm/v2 v2.2.2 h1:aLGEqhMDcSNg1RHwzUBHwRUbmiK8HfD1sTq8bG7/WMc=
github.com/goreleaser/nfpm/v2 v2.2.2/go.mod h1:WtPzYQh+Ls57prS9RzXegrGejqILUS1aZibXHTTJ8eI=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw=
github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0=
github.com/gostaticanalysis/analysisutil v0.6.1 h1:/1JkoHe4DVxur+0wPvi26FoQfe1E3ZGqIXS3aaSLiaw=
github.com/gostaticanalysis/analysisutil v0.6.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0=
github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI=
github.com/gostaticanalysis/comment v1.4.1 h1:xHopR5L2lRz6OsjH4R2HG5wRhW9ySl3FsHIvi5pcXwc=
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.3-0.20191025211905-234833755cb2 h1:STV8OvzphW1vlhPFxcG8d6OIilzBSKRAoWFJt+Onu10=
github.com/hashicorp/go-hclog v0.9.3-0.20191025211905-234833755cb2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.6.3 h1:tuulM+WnToeqa05z83YLmKabZxrySOmJAd4mJ+s2Nfg=
github.com/hashicorp/go-retryablehttp v0.6.3/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3 h1:7nkB9fLPMwtn/R6qfPcHileL/x9ydlhw8XyDrLI1ZXg=
github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48=
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kulti/thelper v0.1.0 h1:ig1EW6yhDiRNN3dplbhdsW2gTvbxTz9i4q5Rr/tRfpk=
github.com/kulti/thelper v0.1.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
github.com/kunwardeep/paralleltest v1.0.2 h1:/jJRv0TiqPoEy/Y8dQxCFJhD56uS/pnvtatgTZBHokU=
github.com/kunwardeep/paralleltest v1.0.2/go.mod h1:ZPqNm1fVHPllh5LPVujzbVz1JN2GhLxSfY+oqUsvG30=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77LoN/M=
github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ=
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/matoous/godox v0.0.0-20200801072554-4fb83dc2941e h1:2U5rOmpaB96l35w+NDjMtmmrp2e6a6AJKoc4B5+7UwA=
github.com/matoous/godox v0.0.0-20200801072554-4fb83dc2941e/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
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/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mbilski/exhaustivestruct v1.1.0 h1:4ykwscnAFeHJruT+EY3M3vdeP8uXMh0VV2E61iR7XD8=
github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gon v0.2.3 h1:fObN7hD14VacGG++t27GzTW6opP0lwI7TsgTPL55wBo=
github.com/mitchellh/gon v0.2.3/go.mod h1:Ua18ZhqjZHg8VyqZo8kNHAY331ntV6nNJ9mT3s2mIo8=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4=
github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k=
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakabonne/nestif v0.3.0 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaPw=
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/exhaustive v0.1.0 h1:kVlMw8h2LHPMGUVqUj6230oQjjTMFjwcZrnkhXzFfl8=
github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.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.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
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/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
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/polyfloyd/go-errorlint v0.0.0-20201127212506-19bd8db6546f h1:xAw10KgJqG5NJDfmRqJ05Z0IFblKumjtMeyiOLxj3+4=
github.com/polyfloyd/go-errorlint v0.0.0-20201127212506-19bd8db6546f/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw=
github.com/quasilyte/go-ruleguard v0.2.1 h1:56eRm0daAyny9UhJnmtJW/UyLZQusukBAB8oT8AHKHo=
github.com/quasilyte/go-ruleguard v0.2.1/go.mod h1:hN2rVc/uS4bQhQKTio2XaSJSafJwqBUWWwtssT3cQmc=
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/quasilyte/regex/syntax v0.0.0-20200805063351-8f842688393c h1:+gtJ/Pwj2dgUGlZgTrNFqajGYKZQc7Piqus/S6DK9CE=
github.com/quasilyte/regex/syntax v0.0.0-20200805063351-8f842688393c/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.2.0 h1:YWfhGOrXwLGiqcC/u5EqG6YeS8nh+1fw0HEc85CVZro=
github.com/ryancurrah/gomodguard v1.2.0/go.mod h1:rNqbC4TOIdUDcVMSIpNNAzTbzXAZa6W5lnUepvuMMgQ=
github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw=
github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b h1:+gCnWOZV8Z/8jehJ2CdqB47Z3S+SREmQcuXkRFLNsiI=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc=
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
github.com/securego/gosec/v2 v2.5.0 h1:kjfXLeKdk98gBe2+eYRFMpC4+mxmQQtbidpiiOQ69Qc=
github.com/securego/gosec/v2 v2.5.0/go.mod h1:L/CDXVntIff5ypVHIkqPXbtRpJiNCh6c6Amn68jXDjo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs=
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY=
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ=
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=
github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/ssgreg/nlreturn/v2 v2.1.0 h1:6/s4Rc49L6Uo6RLjhWZGBpWWjfzk2yrf1nIW8m4wgVA=
github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b h1:HxLVTlqcHhFAz3nWUcuvpH7WuOMv8LQoCWmruLfFH2U=
github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 h1:iGnD/q9160NWqKZZ5vY4p0dMiYMRknzctfSkqA4nBDw=
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613/go.mod h1:g6AnIpDSYMcphz193otpSIzN+11Rs+AAIIC6rm1enug=
github.com/tetafro/godot v1.3.2 h1:HzWC3XjadkyeuBZxkfAFNY20UVvle0YD51I6zf6RKlU=
github.com/tetafro/godot v1.3.2/go.mod h1:ah7jjYmOMnIjS9ku2krapvGQrFNtTLo9Z/qB3dGU1eU=
github.com/theupdateframework/go-tuf v0.0.0-20201230183259-aee6270feb55 h1:Zn+mA4qTRyao2Petd+YovKaFOUuxDj158kqCIqvwTow=
github.com/theupdateframework/go-tuf v0.0.0-20201230183259-aee6270feb55/go.mod h1:L+uU/NRFK/7h0NYAnsmvsX9EghDB5QVCcHCIrK2h5nw=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94 h1:ig99OeTyDwQWhPe2iw9lwfQVF1KB3Q4fpP3X7/2VBG8=
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756 h1:zV5mu0ESwb+WnzqVaW2z1DdbAP0S46UtjY8DHQupQP4=
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
github.com/tommy-muehle/go-mnd v1.3.1-0.20201008215730-16041ac3fe65 h1:Y0bLA422kvb32uZI4fy/Plop/Tbld0l9pSzl+j1FWok=
github.com/tommy-muehle/go-mnd v1.3.1-0.20201008215730-16041ac3fe65/go.mod h1:T22e7iRN4LsFPZGyRLRXeF+DWVXFuV9thsyO7NjbbTI=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs=
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
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/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-20190108225652-1e06a53dbb7e/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190221075227-b4e8571b14e0/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200302150141-5c8b2ff67527/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201011145850-ed2f50202694/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2 h1:vEtypaVub6UvKkiXZ2xx9QIvp9TL7sI7xp7vdi2kezA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8=
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -0,0 +1,92 @@
// Package certificate contains functions for handling TLS certificates.
package certificate
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"net/url"
"github.com/pkg/errors"
)
// FetchPEM retrieves the certificate chain presented by the server listening at
// hostname in PEM format.
//
// Adapted from https://stackoverflow.com/a/46735876/491710
func FetchPEM(hostname string) ([]byte, error) {
conn, err := tls.Dial("tcp", hostname, &tls.Config{
InsecureSkipVerify: true,
})
if err != nil {
return nil, errors.Wrap(err, "dial server to fetch PEM")
}
defer conn.Close()
var b bytes.Buffer
for _, cert := range conn.ConnectionState().PeerCertificates {
err := pem.Encode(&b, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})
if err != nil {
return nil, errors.Wrap(err, "encode PEM")
}
}
return b.Bytes(), nil
}
// LoadPEM loads certificates from a PEM file and returns a cert pool containing
// the certificates.
func LoadPEM(path string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
contents, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrap(err, "read certificate file")
}
if ok := pool.AppendCertsFromPEM(contents); !ok {
return nil, errors.Errorf("no valid ceritificates found in %s", path)
}
return pool, nil
}
// ValidateConnection checks that a connection can be successfully established
// to the server URL using the cert pool provided. The validation performed is
// not sufficient to verify authenticity of the server, but it can help to catch
// certificate errors and provide more detailed messages to users.
func ValidateConnection(pool *x509.CertPool, fleetURL string) error {
parsed, err := url.Parse(fleetURL)
if err != nil {
return errors.Wrap(err, "parse url")
}
conn, err := tls.Dial("tcp", parsed.Host, &tls.Config{
ClientCAs: pool,
InsecureSkipVerify: true,
VerifyConnection: func(state tls.ConnectionState) error {
if len(state.PeerCertificates) == 0 {
return errors.New("no peer certificates")
}
cert := state.PeerCertificates[0]
if _, err := cert.Verify(x509.VerifyOptions{
DNSName: parsed.Hostname(),
Roots: pool,
}); err != nil {
return errors.Wrap(err, "verify certificate")
}
return nil
},
})
if err != nil {
return errors.Wrap(err, "dial for validate")
}
defer conn.Close()
return nil
}

View file

@ -0,0 +1,70 @@
package certificate
import (
"io/ioutil"
"net"
"net/http"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFetchPEMInvalidHostname(t *testing.T) {
t.Parallel()
_, err := FetchPEM("foobar")
require.Error(t, err)
}
func TestFetchPEM(t *testing.T) {
t.Parallel()
certPath := filepath.Join("testdata", "test.crt")
keyPath := filepath.Join("testdata", "test.key")
expectedCert, err := ioutil.ReadFile(certPath)
require.NoError(t, err)
var port int
go func() {
// Assign any available port
listener, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
port = listener.Addr().(*net.TCPAddr).Port
defer listener.Close()
err = http.ServeTLS(listener, nil, certPath, keyPath)
require.NoError(t, err)
}()
// Sleep to allow the goroutine to run and start the server.
time.Sleep(10 * time.Millisecond)
pem, err := FetchPEM("localhost:" + strconv.Itoa(port))
require.NoError(t, err)
assert.Equal(t, expectedCert, pem)
}
func TestLoadPEM(t *testing.T) {
t.Parallel()
pool, err := LoadPEM(filepath.Join("testdata", "test.crt"))
require.NoError(t, err)
assert.True(t, len(pool.Subjects()) > 0)
}
func TestLoadErrorNoCertificates(t *testing.T) {
t.Parallel()
_, err := LoadPEM(filepath.Join("testdata", "empty.crt"))
require.Error(t, err)
}
func TestLoadErrorMissingFile(t *testing.T) {
t.Parallel()
_, err := LoadPEM(filepath.Join("testdata", "invalid_path"))
require.Error(t, err)
}

View file

@ -0,0 +1 @@
no cert contained in here

28
orbit/pkg/certificate/testdata/test.crt vendored Normal file
View file

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIErjCCApYCCQCj1g+mDUoEQjANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5s
b2NhbGhvc3Q6ODU0MzAeFw0yMDEyMTcyMzA4MzJaFw00ODA1MDMyMzA4MzJaMBkx
FzAVBgNVBAMMDmxvY2FsaG9zdDo4NTQzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAyAThP2RVyrZd1hnrjoz0f87dT3xC6PDFMf+SG55A8YhtvylHG3Ph
Xq8TZ6oFhWmqLjllAGKlfJRcTnqY9f3mtDFvNt5yTu7Ot32VL5viZobp5fw0Wfcu
G2rKENZ5/3r6pE5iD2oRU5CxGsdnjRhKfVwK1ayF60hY23xY5TE+RVnyvuqsRMWU
2vTD3rx8k9g1/kJwkujG0eL3K3NslamM5yWxsRdUk0MCQDGlr176zqToUIhMqQLR
vkKw1hm+m9OQHHn3pucUdPbQynWMVEUKCCF19wtqW2rUW9POgkPD4cPf3D999rvK
Y08XhzXmS74bAbDXBKHCFAQFFJUseSNEDjIBn/gfMH6KX0x1kiRixj/hdel9VdkO
dUl/AScfOTg71DxjQDbp/EW/diufNZwadXbBjiKeys8Zi9ISt4WQmXc08FTRCb9T
6rghenH2UBOT9zZn8yIoq38WfZ2dUmJPhIdEh27GOa/ets1psNg8lh+bEDKmDstd
6/SXlHR4Pfhqy4cqppG09Z/Hl6fgyr5U4/sdWN4gm/ZkbTGgPGJ2fQEuuuEsFXC2
US9aJcEC4rw103yNr7/4P9u3U+iHGbq8wOFCEtf4p4mi/9S1Txsupnd/hbla2k2W
vNYhzqbbatf/A6ahc8Vee8x+Zd/fOcuk0khb8evq/TnBJIsw67aXPEECAwEAATAN
BgkqhkiG9w0BAQsFAAOCAgEAhYWB/mrNLZi1zru0W1gmfuSE3wBrN0G5Ce/alNi6
MwHoQiBCxZmmYndbXEaXtwhsLoV7mlOYdYr1DiK94usT8g9kj4kymYsl0jPdM+1w
PPARBoPeqpHQBSYbAaB9Iqx1Wc/I5g/kwgrtoPM1Z3y7f2277BqeoxkwhVbC0aLC
cYkk+tzK4+sPfrW/onLfVuKsrVcoFnDsg/aa3rKdTcTsTwrJUF5S1hiK2CSkpJbx
60C4Zu7fUYFHbjcorsy+dSVX26+56sazS8o6Gpa+orPjLybFazCX806gPWmKQW48
xkIpIJG0nocvqesUBEbJX/FKuAJ/yLl5P9uGJtjNlK4j/uhm9f5M49KoTSSAQ4A1
Wm/2cBVPokGJ1c+SVGPHgu6QdBvVXznG/CI42cKwj4T0H4wwcah4lw3R9Vy6EqNW
13h20qs/2MnZ0GL2po3M9vYlV6x4dQPz+lXVlez2h1EkI4muffvz+Ql4zgkkAdUO
f+H+eC+m8Hf3/dtWhXdgoOFvMOROQIgYU+W+H6TXrGKwd7n/UkvxJEgNgXfcXlug
qICkuCVW2Z9BaIZiW9X12aNlEPUIgTxbNSYv/gZRXZ54St5DKmUJd5vuV1gm+xxA
+8Lr0uIAxknZGE1OAlADxwwXSJGjAC0m/13X/L/Kb5kd4DyiQvnyVcFfkwSp/xbd
Ihw=
-----END CERTIFICATE-----

52
orbit/pkg/certificate/testdata/test.key vendored Normal file
View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDIBOE/ZFXKtl3W
GeuOjPR/zt1PfELo8MUx/5IbnkDxiG2/KUcbc+FerxNnqgWFaaouOWUAYqV8lFxO
epj1/ea0MW823nJO7s63fZUvm+Jmhunl/DRZ9y4basoQ1nn/evqkTmIPahFTkLEa
x2eNGEp9XArVrIXrSFjbfFjlMT5FWfK+6qxExZTa9MPevHyT2DX+QnCS6MbR4vcr
c2yVqYznJbGxF1STQwJAMaWvXvrOpOhQiEypAtG+QrDWGb6b05Acefem5xR09tDK
dYxURQoIIXX3C2pbatRb086CQ8Phw9/cP332u8pjTxeHNeZLvhsBsNcEocIUBAUU
lSx5I0QOMgGf+B8wfopfTHWSJGLGP+F16X1V2Q51SX8BJx85ODvUPGNANun8Rb92
K581nBp1dsGOIp7KzxmL0hK3hZCZdzTwVNEJv1PquCF6cfZQE5P3NmfzIiirfxZ9
nZ1SYk+Eh0SHbsY5r962zWmw2DyWH5sQMqYOy13r9JeUdHg9+GrLhyqmkbT1n8eX
p+DKvlTj+x1Y3iCb9mRtMaA8YnZ9AS664SwVcLZRL1olwQLivDXTfI2vv/g/27dT
6IcZurzA4UIS1/iniaL/1LVPGy6md3+FuVraTZa81iHOpttq1/8DpqFzxV57zH5l
3985y6TSSFvx6+r9OcEkizDrtpc8QQIDAQABAoICAQCQau2Tzsce+TOhfc+VenKi
wWMLnAXXmNhYtFXzOP1dJ4tOBejAipfDvJc/NwSLgnKMs4YYlCE2ZQyM4XoFyxBD
NJo/PLe+BDEfTT5lRKFgM7M4CjRmbNFOnHlPVPx7GXlVHv8wb/4YnxUw5579wfcu
skFkbA+5hOAbgZnRyg9TFZHuhRRjB2HmAepWrTMpsGezsJcFi6raKo0aQ1m4adZB
ova8jcLUHQLIBNDDYcmnYGwYkYEAWvfD7vUtcSMt8cBQv0Ovr5MWrIquU9dBlSOc
IUTCXeyqGuU/dBvb9D+/h6TfqrwxZP/JWnb7elBQie+H9f4Z6w1tVEWyyc51nJ/q
dQk63N3Kji+hXetR6tjAADwMKvJKn1Fd4xzgPt0OPHQu1J+kt2LXpdt78ynoPivz
eH1Ejf43YzSf6Zm8zisyfgDt9XBauNjosrDhrzvtMNAIYEA1jkZ0f0YUjHT4lltF
K1R9nl3MQGEQ1AfG4XaCfMRwTSKgxf8HWNYxWexQGvZRz62QKW4neLWjPYBM+jgt
em70efwjtXyy34rE7t4ItkphyvpnN1WFKLCve/Jpg5Ds4QzImqnHGnDIIQ8F+cNS
TXFblNOHcB5MgQI1I4Z08Ibq6a/2nYEpZZtBElug22hTTuYdo5vfiMTPsFdOosV+
acbzawQScBCVUd5WhI1qnQKCAQEA6RJW9LbrUXxMEfZEbt9ucvS4Q0gkR/Fo7zGo
nEHc1MQlW93FPXOo/af41pN93V4c0k2M7cmB2iuGdqgxaPlRJDDHLFx4lF9gFNIS
GoxwKalMl+mTZrt9+f8zg/R/rQIwxpvYmpFhmtUbYHO37f+bwkMsZdbhw4bLEHSA
aPJup6LWJx6D+WnwgVABx2b4kaKGxlxVWBwr1a+NG0H18G4gnCgu3Cpx3Yaw/+cN
jLDy3ypPnkrVW72dyj24WtIfZc4rk9UkAJbwMtJXshYwbx+Ha0UD03dhlD1R50iz
ldvxSPkygNuJvgK34mFMS/Bga6PjRvlB9KHf7AeyE8ePsW56pwKCAQEA27Ildo1W
fmqS5fHXZ90lVzY37ZjKT9+OWsYq3dDievHtjdCXmsALAoXQKwE27i/C+a4AKSuU
SjsHCdV345ne0M3PrFR5OiCLVSRfqyBAetXkBic37W/xj7PTcBaWj/6u6iwgcQrq
/rEwXWdPCtT1Xp7ZI5kq38CuH0yAmQqnhzybTXj4g+f3mvgK+jxcG39KAJqQk18b
emXwsIwCbohSlvy7i5TQYJehy+IWRQFeD8oLkzQRtnGv+faht2xebi3i5SzJES1V
keUIgOTYIED03YpNyOWFNqBjRgiWHvfqcKOAP9CiUcsIqDNgTZoUHKcafuMDfLIc
a8hh3wsttHk21wKCAQEAgUBiaKNoLHA531wq5zGRFr8P0IAZXqxJ/RwU3VLJHFUK
Qr/hugqCFOkp3hU9H54pbZcEKHovQMYSc4sim4xnqyJB0iAV3nJl3iYBVCe9q9zv
VO97huVDH3ifIPZeN5uXYQzcOhuVfT/hRsPlpF60Ci8mV/Oqj7wYsK9q5shLBTwQ
dvE9TvupHI0571LzVhBDnY1m5s34oTARmKQjt4nbG75M/lureaZQUhnYMcWPaqMU
NDxbxdyJvLYtnnAYaWTEVd9Fb+5Elmp/p4sWoQljF+HWFVeHDaZT3Wc78Edgr49C
qqMU6AKp5yj+Hr7XOdpRF/Ly2K9MdqBt6PoqfcM+0QKCAQEAlcIQK4oZBb+cN0bt
8Q0coSCCa2IVtvDLVzFykxYK7IfxyRQB5Ck42BVjW41OZnsES7LCxU3BngAywg9T
1EBVVLyW7f//SxJYKEfNNxebHKCk+VTOmPoQDkckwGmFZM7VaSd/Tc+FdOxP7bu4
c8fLIx7hIZUNVs0/ZHJ/ztMmc9dqfhsSPMhpTqf6w61VlCDmzxaNqwfP8VWABrjK
B5LByc6qAXIFwXJbhmGtkIhoGBdAYK3DzTweGyR2BFTI4g5BdrtarGzcwcEAVOyq
LwJYaJQYhfN/JUebpGfB/YY9t16c0+NiYqMmWZH7+aooP4fhVdFl1SCMoyRLIwG5
vCZMrQKCAQBamPky1dKIfOehquwmcQnNZF0Wh/7tfJv2xhX6FswbPhFhAAepXmsY
4NKqqEy8pXgZ/7vXLawGAgQr2taPF4hx9QSmUNefZZpgOVVyfZgskfpMi8NcYGex
Ap4C52EiSWUooj+7vsaTvzIkUxnEUy18aRfxhTKkHcbv+d9bYOr6/BZrpetpFQEW
DzQ5dETUDhTaCyJknj3SLZRqoxOcyDkgUzFY9fJbzdlUYTtMLgnRmA37xTVuWfDR
MxLnssq56iONJylCox0sNkdhFe1f4JiLYyStHzerhCTNtSD53c6Q5hWmCFJXwllO
rdcA+RwiWNrxDmbPFs7HuiIft5MwbmdE
-----END PRIVATE KEY-----

View file

@ -0,0 +1,19 @@
package constant
const (
// DefaultDirMode is the default file mode to apply to created directories.
DefaultDirMode = 0o755
// DefaultFileMode is the default file mode to apply to created files.
DefaultFileMode = 0o600
)
// ExecutableExtension returns the extension used for executables on the
// provided platform.
func ExecutableExtension(platform string) string {
switch platform {
case "windows":
return ".exe"
default:
return ""
}
}

View file

@ -0,0 +1,5 @@
package constant
const (
PlatformName = "macos"
)

View file

@ -0,0 +1,5 @@
package constant
const (
PlatformName = "linux"
)

View file

@ -0,0 +1,9 @@
//+build !windows
package constant
const (
// DefaultExecutableMode is the default file mode to apply to created
// executable files.
DefaultExecutableMode = 0o755
)

View file

@ -0,0 +1,34 @@
//+build windows
package constant
import (
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
const (
PlatformName = "windows"
// DefaultExecutableMode is the default file mode to apply to created
// executable files. For Windows this doesn't do anything besides setting
// read-only. See https://golang.org/pkg/os/#Chmod.
DefaultExecutableMode = 0o700
)
var (
// These identifiers can be found in
// https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows
// and are used in the same fashion as in osquery. See
// https://github.com/osquery/osquery/blob/d2be385d71f401c85872f00d479df8f499164c5a/tools/deployment/chocolatey/tools/osquery_utils.ps1.
SystemSID = mustSID("S-1-5-18")
AdminSID = mustSID("S-1-5-32-544")
UserSID = mustSID("S-1-5-32-545")
)
func mustSID(identifier string) *windows.SID {
sid, err := windows.StringToSid(identifier)
if err != nil {
panic(errors.Wrap(err, "create sid"))
}
return sid
}

View file

@ -0,0 +1,97 @@
package database
import (
"time"
"github.com/dgraph-io/badger/v2"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
const (
compactionInterval = 5 * time.Minute
// This is the discard ratio recommended in Badger docs
// (https://pkg.go.dev/github.com/dgraph-io/badger#DB.RunValueLogGC)
compactionDiscardRatio = 0.5
)
// BadgerDB is a wrapper around the standard badger.DB that provides a
// background compaction routine.
type BadgerDB struct {
*badger.DB
closeChan chan struct{}
}
// Open opens (initializing if necessary) a Badger database at the specified
// path. Users must close the DB with Close().
func Open(path string) (*BadgerDB, error) {
// DefaultOptions sets synchronous writes to true (maximum data integrity).
// TODO implement logging?
db, err := badger.Open(badger.DefaultOptions(path).WithLogger(nil))
if err != nil {
return nil, errors.Wrapf(err, "open badger %s", path)
}
b := &BadgerDB{DB: db}
b.startBackgroundCompaction()
return b, nil
}
// OpenTruncate opens (initializing and/or truncating if necessary) a Badger
// database at the specified path. Users must close the DB with Close().
//
// Prefer Open in the general case, but after a bad shutdown it may be necessary
// to call OpenTruncate. This may cause data loss. Detect this situation by
// looking for badger.ErrTruncateNeeded.
func OpenTruncate(path string) (*BadgerDB, error) {
// DefaultOptions sets synchronous writes to true (maximum data integrity).
// TODO implement logging?
db, err := badger.Open(badger.DefaultOptions(path).WithLogger(nil).WithTruncate(true))
if err != nil {
return nil, errors.Wrapf(err, "open badger with truncate %s", path)
}
b := &BadgerDB{DB: db}
b.startBackgroundCompaction()
return b, nil
}
// startBackgroundCompaction starts a background loop that will call the
// compaction method on the database. Badger does not do this automatically, so
// we need to be sure to do so here (or elsewhere).
func (b *BadgerDB) startBackgroundCompaction() {
if b.closeChan != nil {
panic("background compaction already running")
}
b.closeChan = make(chan struct{})
go func() {
ticker := time.NewTicker(compactionInterval)
defer ticker.Stop()
for {
select {
case <-b.closeChan:
return
case <-ticker.C:
if err := b.DB.RunValueLogGC(compactionDiscardRatio); err != nil && !errors.Is(err, badger.ErrNoRewrite) {
log.Error().Err(err).Msg("compact badger")
}
}
}
}()
}
// stopBackgroundCompaction stops the background compaction routine.
func (b *BadgerDB) stopBackgroundCompaction() {
b.closeChan <- struct{}{}
b.closeChan = nil
}
// Close closes the database connection and releases the associated resources.
func (b *BadgerDB) Close() error {
b.stopBackgroundCompaction()
return b.DB.Close()
}

View file

@ -0,0 +1,80 @@
package database
import (
"io/ioutil"
"os"
"testing"
"github.com/dgraph-io/badger/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDatabase(t *testing.T) {
t.Parallel()
tmpDir, err := ioutil.TempDir("", "orbit-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
// Open and write
db, err := Open(tmpDir)
require.NoError(t, err)
err = db.Update(func(tx *badger.Txn) error {
require.NoError(t, tx.Set([]byte("key"), []byte("value")))
return nil
})
require.NoError(t, err)
require.NoError(t, db.Close())
// Reopen and read
db, err = Open(tmpDir)
require.NoError(t, err)
err = db.View(func(tx *badger.Txn) error {
item, err := tx.Get([]byte("key"))
require.NoError(t, err)
err = item.Value(func(val []byte) error {
assert.Equal(t, []byte("value"), val)
return nil
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
require.NoError(t, db.Close())
}
func TestCompactionPanic(t *testing.T) {
t.Parallel()
tmpDir, err := ioutil.TempDir("", "orbit-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
db, err := Open(tmpDir)
require.NoError(t, err)
// Try to start the compaction routine again
assert.Panics(t, func() { db.startBackgroundCompaction() })
}
func TestCompactionRestart(t *testing.T) {
t.Parallel()
tmpDir, err := ioutil.TempDir("", "orbit-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
db, err := Open(tmpDir)
require.NoError(t, err)
go func() {
require.NoError(t, db.Close())
}()
db.stopBackgroundCompaction()
assert.NotPanics(t, func() { db.startBackgroundCompaction() })
}

211
orbit/pkg/insecure/proxy.go Normal file
View file

@ -0,0 +1,211 @@
// Package insecure provides an insecure (if it were not obvious yet) TLS proxy
// that can be used for testing osquery enrollment with a Fleet (or other TLS)
// server in non-production environments.
//
// Functions in this package are NOT SUITABLE FOR PRODUCTION ENVIRONMENTS!
package insecure
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
"github.com/pkg/errors"
)
const (
// ServerCert is the certificate used by the proxy server.
ServerCert = `-----BEGIN CERTIFICATE-----
MIICpDCCAYwCCQCPnw3uINXlozANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjAxMjE5MDA0MDA0WhcNNDgwNTA1MDA0MDA0WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1
GuqbXIo767H1dPM9KS7Uz6bU3Jzh/4e5fxmgxTz2GY76UiKhBKvlWIy2PsFMKpQ1
kd3/MyANoOcUkdolPAX/6jMZc9qhlRaG80MqgZuBzX3KHnCnFN9vin1wOrTlyboW
NLjKCmKTCpa0knuya9hgOwCJ1cFMFByC29qRvYKtisQxRbpy/d/jN14dXsGeQiZW
KU6ncmFPBH8+uTnrQq4A3UBFMOu5C+Uk+hCSLNMu4ZbAUR41m0LpR5OaWk1t0q2O
ZbDg4zkSJzbBNeiVe+vCbKtevqtRgqAi4u4EGdasnlhEJ/UPfF9lvqCd1iRw7M9u
quPlsJs6tE4GFBpIUUMBAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA5KnpFoTnKW
B04G42v6a2AkY/ENEgoMhKr6JBeRkRKF6Itatiotb/RClgRYlDUn+ljow8/Tyds4
qqMl/MzjbbwI4xNcu9t+0bG2zmJj6ON4mbRH+GnBPX+t50/1eKSoPjtHDyT/UAbx
q3jyXp0nObaRzDqmYK/OUVg7vhAxQqQ9Cvvk819Ar8wFZGjE9Bc2YDObyCVQWCZz
qIfzr/Qh46tq0o+KdlaV2oHy4VLrLOFXeD5MKf6A7aOP7h9Yy9ywnScrobaSXwd8
kS/PZzVeJtwvKf+c1tBiJxHix2vLiFtS5IKdhNGKNvMyQNWgq046iTNeVJkxo+Qb
YP4a5WpD+aw=
-----END CERTIFICATE-----
`
// serverKey is the corresponding private key. This key is compromised by
// being in the source code, rendering any connection using this cert
// insecure.
serverKey = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1GuqbXIo767H1
dPM9KS7Uz6bU3Jzh/4e5fxmgxTz2GY76UiKhBKvlWIy2PsFMKpQ1kd3/MyANoOcU
kdolPAX/6jMZc9qhlRaG80MqgZuBzX3KHnCnFN9vin1wOrTlyboWNLjKCmKTCpa0
knuya9hgOwCJ1cFMFByC29qRvYKtisQxRbpy/d/jN14dXsGeQiZWKU6ncmFPBH8+
uTnrQq4A3UBFMOu5C+Uk+hCSLNMu4ZbAUR41m0LpR5OaWk1t0q2OZbDg4zkSJzbB
NeiVe+vCbKtevqtRgqAi4u4EGdasnlhEJ/UPfF9lvqCd1iRw7M9uquPlsJs6tE4G
FBpIUUMBAgMBAAECggEBAKUhQcEfA7vXEJBqbk7Z+iV4oPl9nl5CjBKK3WdF8GvE
qiV8Nq7yf3nC36pcVguI11JxCiXjC9rhV1HeGzXQIPhTJvySMksakUvDCv765jvY
jlV4o+b0lTYy5GUsYj0TTmVo9QTjqzW/deJ3nen1g3la0wbarEEeJVD7/bLdRQXN
9DcFtKpC+to0N7RE2cFLGIgWcU/W6IF+DoLkVKdd0h2EZ4FawRtythkfC2Z+B1W/
ayz645XIQcA2sI2z4G2CqR/k3gmv6eGzoQgTN46z2lYqPFMD7v8hwv9Z/LCIghF9
iJQy8Uxy8zRjmCDNP0oWRrlIUh2abqhgLlvvYsFJ3QECgYEA4Z0FJmsgf65NlBzQ
g5+x9pQLZX0tEpeuDZT+W9i+7u/d1nkISmD+urL+f1o5Jc8dg0QG9Y6XFodIsp8W
s6wbCcBR+V7q76fJONRLnh5mHNwBvvVGg5Iy/Bmc8gTboG7F4yJnrvCYeE4gIVXt
4c5c7B1hOC/eyXIGng5bbKwtCAkCgYEAzX9JSOwWlt3Y75SCL8d3WSEBVqgaH1UZ
4lSZ/Bi1F7uUToJGMnH2XinZ0tO7erPtisl/S9SZhMmIFho+p/23Mz4SWtLGzIBu
q8Zoo4gfCk2cP5YkidKad1Kc3ZG4LWQCiRr7+HYYmTXaUWe5PNznlp36IFliPlkK
Q2ztNOpq8TkCgYBa4SQ08IwbwnuPgPfhPU+zcrkQfZbNWXoMEItRNgLbPpYOkZxs
UZvqWrW3WQGSIFbUDG/9NB3aPk5jXUAIyffuOqEKoVhjhyPAF4wKOlaJo3m0kRqB
Xz/YWvzkZF6Pxm9B6hb32gSg2V+J7hIvli/KEJ+bwXStkpflzQS4xrYw+QKBgGqH
T8Bj0xoGi403WX3XU4F64KzBnDkd7rsrzF+pl0dkUG+ajTVdarBJ1ce7R3dGix/l
cP4oiiUSLF/43v5LQotn5C/9EF23PqgBxQDxcdXvgc5c0Tg5WyX8R6F9BxNQwxe8
S170KbBTAIgu0xJAGjY0UxQuAgX8NpvZfeZul13RAoGARVSBIo5MDodDw25j4fc7
YIC5ppouCT4VTz6IUOxYyw8TQBBm5Wes62JJ+9yTLLfIRNnkDkVwSX7Q3blva+1W
1lFBG1WzNhTRo0im9lRyVCGp2ZSm2UxsJS9jP1J6bkavkjK/jaPOC9J6dmWOZBWm
yJY8h/0WhN9XPcXFa4Qmfu0=
-----END PRIVATE KEY-----
`
)
// TLSProxy is the insecure TLS proxy implementation. This type should only be
// initialized via NewTLSProxy.
type TLSProxy struct {
// Port is the port the TLS proxy is listening on (always on localhost).
Port int
listener net.Listener
server *http.Server
}
// NewTLSProxy creates a new proxy implementation targeting the provided
// hostname.
func NewTLSProxy(targetURL string) (*TLSProxy, error) {
cert, err := tls.X509KeyPair([]byte(ServerCert), []byte(serverKey))
if err != nil {
return nil, errors.Wrap(err, "load keypair")
}
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
// Assign any available port
listener, err := tls.Listen("tcp", "localhost:0", cfg)
if err != nil {
return nil, errors.Wrap(err, "bind localhost")
}
addr, ok := listener.Addr().(*net.TCPAddr)
if !ok {
return nil, errors.New("listener is not *net.TCPAddr")
}
handler, err := newProxyHandler(targetURL)
if err != nil {
return nil, errors.Wrap(err, "make proxy handler")
}
proxy := &TLSProxy{
Port: addr.Port,
listener: listener,
server: &http.Server{Handler: handler},
}
return proxy, nil
}
// InsecureServeTLS will begin running the TLS proxy.
func (p *TLSProxy) InsecureServeTLS() error {
if p.listener == nil || p.server == nil {
return errors.New("listener and handler must not be nil -- initialize TLSProxy via NewTLSProxy")
}
err := p.server.Serve(p.listener)
return errors.Wrap(err, "servetls returned")
}
// Close the server and associated listener. The server may not be reused after
// calling Close().
func (p *TLSProxy) Close() error {
// err := p.listener.Close()
// if err != nil {
// return errors.Wrap(err, "close listener")
// }
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return p.server.Shutdown(ctx)
}
func newProxyHandler(targetURL string) (*httputil.ReverseProxy, error) {
target, err := url.Parse(targetURL)
if err != nil {
return nil, errors.Wrap(err, "parse target url")
}
reverseProxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.Host = target.Host
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
},
}
// Adapted from http.DefaultTransport
reverseProxy.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
return reverseProxy, nil
}
// Copied from Go source
// https://go.googlesource.com/go/+/go1.15.6/src/net/http/httputil/reverseproxy.go#114
func joinURLPath(a, b *url.URL) (path, rawpath string) {
if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), ""
}
// Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added
apath := a.EscapedPath()
bpath := b.EscapedPath()
aslash := strings.HasSuffix(apath, "/")
bslash := strings.HasPrefix(bpath, "/")
switch {
case aslash && bslash:
return a.Path + b.Path[1:], apath + bpath[1:]
case !aslash && !bslash:
return a.Path + "/" + b.Path, apath + "/" + bpath
}
return a.Path + b.Path, apath + bpath
}
// Copied from Go source
// https://go.googlesource.com/go/+/go1.15.6/src/net/http/httputil/reverseproxy.go#102
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}

View file

@ -0,0 +1,16 @@
package insecure
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProxy(t *testing.T) {
t.Parallel()
proxy, err := NewTLSProxy("localhost")
require.NoError(t, err)
assert.NotZero(t, proxy.Port)
}

View file

@ -0,0 +1,32 @@
package osquery
import (
"net/url"
"path"
)
// FleetFlags is the set of flags to pass to osquery when connecting to Fleet.
func FleetFlags(fleetURL *url.URL) []string {
hostname, prefix := fleetURL.Host, fleetURL.Path
return []string{
"--tls_hostname=" + hostname,
"--enroll_tls_endpoint=" + path.Join(prefix, "/api/v1/osquery/enroll"),
"--config_plugin=tls",
"--config_tls_endpoint=" + path.Join(prefix, "/api/v1/osquery/config"),
// Osquery defaults config_refresh to 0 which is probably not ideal for
// a client connected to Fleet. Users can always override this in the
// config they serve via Fleet.
"--config_refresh=60",
"--disable_distributed=false",
"--distributed_plugin=tls",
"--distributed_tls_max_attempts=10",
"--distributed_tls_read_endpoint=" + path.Join(prefix, "/api/v1/osquery/distributed/read"),
"--distributed_tls_write_endpoint=" + path.Join(prefix, "/api/v1/osquery/distributed/write"),
"--logger_plugin=tls",
"--logger_tls_endpoint=" + path.Join(prefix, "/api/v1/osquery/log"),
"--disable_carver=false",
"--carver_start_endpoint=" + path.Join(prefix, "/api/v1/osquery/carve/begin"),
"--carver_continue_endpoint=" + path.Join(prefix, "/api/v1/osquery/carve/block"),
"--carver_block_size=2000000",
}
}

View file

@ -0,0 +1,125 @@
// package osquery implements a runtime for osqueryd.
package osquery
import (
"context"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/process"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// Runner is a specialized runner for osquery. It is designed with Execute and
// Interrupt functions to be compatible with oklog/run.
type Runner struct {
proc *process.Process
cmd *exec.Cmd
cancel func()
}
// NewRunner creates a new osquery runner given the provided functional options.
func NewRunner(path string, options ...func(*Runner) error) (*Runner, error) {
r := &Runner{}
cmd := exec.Command(path)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
r.cmd = cmd
r.proc = process.NewWithCmd(cmd)
for _, option := range options {
err := option(r)
if err != nil {
return nil, errors.Wrap(err, "apply option")
}
}
return r, nil
}
// WithFlags adds additional flags to the osqueryd invocation.
func WithFlags(flags []string) func(*Runner) error {
return func(r *Runner) error {
r.cmd.Args = append(r.cmd.Args, flags...)
return nil
}
}
// WithEnv adds additional environment variables to the osqueryd invocation.
// Inputs should be in the form "KEY=VAL".
func WithEnv(env []string) func(*Runner) error {
return func(r *Runner) error {
r.cmd.Env = append(r.cmd.Env, env...)
return nil
}
}
// WithShell adds the -S flag to run an osqueryi shell.
func WithShell() func(*Runner) error {
return func(r *Runner) error {
r.cmd.Args = append(r.cmd.Args, "-S")
r.cmd.Stdin = os.Stdin
return nil
}
}
func WithDataPath(path string) func(*Runner) error {
return func(r *Runner) error {
if err := os.MkdirAll(filepath.Join(path, "logs"), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "initialize osquery data path")
}
r.cmd.Args = append(r.cmd.Args,
"--pidfile="+filepath.Join(path, "osquery.pid"),
"--database_path="+filepath.Join(path, "osquery.db"),
"--extensions_socket="+filepath.Join(path, "osquery.em"),
)
return nil
}
}
func WithLogPath(path string) func(*Runner) error {
return func(r *Runner) error {
if err := os.MkdirAll(path, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "initialize osquery log path")
}
r.cmd.Args = append(r.cmd.Args,
"--logger_path="+path,
)
return nil
}
}
// Execute begins running osqueryd and returns when the process exits. The
// process may not be restarted after exit. Instead create a new one with
// NewRunner.
func (r *Runner) Execute() error {
log.Info().Str("cmd", r.cmd.String()).Msg("run osqueryd")
ctx, cancel := context.WithCancel(context.Background())
r.cancel = cancel
if err := r.proc.Start(); err != nil {
return errors.Wrap(err, "start osqueryd")
}
if err := r.proc.WaitOrKill(ctx, 10*time.Second); err != nil {
return errors.Wrap(err, "osqueryd exited with error")
}
return nil
}
// Runner interrupts the running osquery process.
func (r *Runner) Interrupt(err error) {
log.Debug().Msg("interrupt osquery")
r.cancel()
}

View file

@ -0,0 +1,7 @@
package packaging
import "github.com/goreleaser/nfpm/v2/deb"
func BuildDeb(opt Options) error {
return buildNFPM(opt, deb.Default)
}

View file

@ -0,0 +1,237 @@
package packaging
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/update"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
func buildNFPM(opt Options, pkger nfpm.Packager) error {
// Initialize directories
tmpDir, err := ioutil.TempDir("", "orbit-package")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(tmpDir)
log.Debug().Str("path", tmpDir).Msg("created temp dir")
filesystemRoot := filepath.Join(tmpDir, "root")
if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create root dir")
}
orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "orbit")
if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create orbit dir")
}
// Initialize autoupdate metadata
updateOpt := update.DefaultOptions
updateOpt.Platform = "linux"
updateOpt.RootDirectory = orbitRoot
updateOpt.OrbitChannel = opt.OrbitChannel
updateOpt.OsquerydChannel = opt.OsquerydChannel
updateOpt.ServerURL = opt.UpdateURL
if opt.UpdateRoots != "" {
updateOpt.RootKeys = opt.UpdateRoots
}
if err := initializeUpdates(updateOpt); err != nil {
return errors.Wrap(err, "initialize updates")
}
// Write files
if err := writeSystemdUnit(opt, filesystemRoot); err != nil {
return errors.Wrap(err, "write systemd unit")
}
if err := writeEnvFile(opt, filesystemRoot); err != nil {
return errors.Wrap(err, "write env file")
}
postInstallPath := filepath.Join(tmpDir, "postinstall.sh")
if err := writePostInstall(opt, postInstallPath); err != nil {
return errors.Wrap(err, "write postinstall script")
}
if opt.FleetCertificate != "" {
if err := writeCertificate(opt, orbitRoot); err != nil {
return errors.Wrap(err, "write fleet certificate")
}
}
// Pick up all file contents
contents := files.Contents{
&files.Content{
Source: filepath.Join(filesystemRoot, "**"),
Destination: "/",
},
// Symlink current into /var/lib/orbit/bin/orbit/orbit
&files.Content{
Source: "/var/lib/orbit/bin/orbit/linux/" + opt.OrbitChannel + "/orbit",
Destination: "/var/lib/orbit/bin/orbit/orbit",
Type: "symlink",
FileInfo: &files.ContentFileInfo{
Mode: constant.DefaultExecutableMode | os.ModeSymlink,
},
},
// Symlink current into /usr/local/bin
&files.Content{
Source: "/var/lib/orbit/bin/orbit/orbit",
Destination: "/usr/local/bin/orbit",
Type: "symlink",
FileInfo: &files.ContentFileInfo{
Mode: constant.DefaultExecutableMode | os.ModeSymlink,
},
},
}
contents, err = files.ExpandContentGlobs(contents, false)
if err != nil {
return errors.Wrap(err, "glob contents")
}
for _, c := range contents {
log.Debug().Interface("file", c).Msg("added file")
}
// Build package
info := &nfpm.Info{
Name: "orbit-osquery",
Version: opt.Version,
Description: "Orbit osquery -- runtime and autoupdater by Fleet",
Arch: "amd64",
Maintainer: "Fleet Engineers <engineering@fleetdm.com>",
Homepage: "https://github.com/fleetdm/orbit",
Overridables: nfpm.Overridables{
Contents: contents,
EmptyFolders: []string{
"/var/log/osquery",
"/var/log/orbit",
},
Scripts: nfpm.Scripts{
PostInstall: postInstallPath,
},
},
}
filename := pkger.ConventionalFileName(info)
out, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, constant.DefaultFileMode)
if err != nil {
return errors.Wrap(err, "open output file")
}
defer out.Close()
if err := pkger.Package(info, out); err != nil {
return errors.Wrap(err, "write package")
}
log.Info().Str("path", filename).Msg("wrote package")
return nil
}
func writeSystemdUnit(opt Options, rootPath string) error {
systemdRoot := filepath.Join(rootPath, "usr", "lib", "systemd", "system")
if err := os.MkdirAll(systemdRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create systemd dir")
}
if err := ioutil.WriteFile(
filepath.Join(systemdRoot, "orbit.service"),
[]byte(`
[Unit]
Description=Orbit osquery
After=network.service syslog.service
StartLimitIntervalSec=0
[Service]
TimeoutStartSec=0
EnvironmentFile=/etc/default/orbit
ExecStart=/var/lib/orbit/bin/orbit/orbit
Restart=always
RestartSec=1
KillMode=control-group
KillSignal=SIGTERM
CPUQuota=20%
[Install]
WantedBy=multi-user.target
`),
constant.DefaultFileMode,
); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
var envTemplate = template.Must(template.New("env").Parse(`
ORBIT_UPDATE_URL={{ .UpdateURL }}
ORBIT_ORBIT_CHANNEL={{ .OrbitChannel }}
ORBIT_OSQUERYD_CHANNEL={{ .OsquerydChannel }}
{{ if .Insecure }}ORBIT_INSECURE=true{{ end }}
{{ if .FleetURL }}ORBIT_FLEET_URL={{.FleetURL}}{{ end }}
{{ if .FleetCertificate }}ORBIT_FLEET_CERTIFICATE=/var/lib/orbit/fleet.pem{{ end }}
{{ if .EnrollSecret }}ORBIT_ENROLL_SECRET={{.EnrollSecret}}{{ end }}
{{ if .Debug }}ORBIT_DEBUG=true{{ end }}
`))
func writeEnvFile(opt Options, rootPath string) error {
envRoot := filepath.Join(rootPath, "etc", "default")
if err := os.MkdirAll(envRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create env dir")
}
var contents bytes.Buffer
if err := envTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(
filepath.Join(envRoot, "orbit"),
contents.Bytes(),
constant.DefaultFileMode,
); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
var postInstallTemplate = template.Must(template.New("postinstall").Parse(`
#!/bin/sh
# Exit on error
set -e
# If we have a systemd, daemon-reload away now
if [ -x /bin/systemctl ] && pidof systemd ; then
/bin/systemctl daemon-reload 2>/dev/null 2>&1
{{ if .StartService -}}
/bin/systemctl restart orbit.service 2>&1
{{- end}}
fi
`))
func writePostInstall(opt Options, path string) error {
var contents bytes.Buffer
if err := postInstallTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}

View file

@ -0,0 +1,346 @@
package packaging
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/update"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// See helful docs in http://bomutils.dyndns.org/tutorial.html
// BuildPkg builds a macOS .pkg. So far this is tested only on macOS but in theory it works with bomutils on
// Linux.
func BuildPkg(opt Options) error {
// Initialize directories
tmpDir, err := ioutil.TempDir("", "orbit-package")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(tmpDir)
log.Debug().Str("path", tmpDir).Msg("created temp dir")
filesystemRoot := filepath.Join(tmpDir, "root")
if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create root dir")
}
orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "orbit")
if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create orbit dir")
}
// Initialize autoupdate metadata
updateOpt := update.DefaultOptions
updateOpt.Platform = "macos"
updateOpt.RootDirectory = orbitRoot
updateOpt.OrbitChannel = opt.OrbitChannel
updateOpt.OsquerydChannel = opt.OsquerydChannel
updateOpt.ServerURL = opt.UpdateURL
if opt.UpdateRoots != "" {
updateOpt.RootKeys = opt.UpdateRoots
}
if err := initializeUpdates(updateOpt); err != nil {
return errors.Wrap(err, "initialize updates")
}
// Write files
if err := writePackageInfo(opt, tmpDir); err != nil {
return errors.Wrap(err, "write PackageInfo")
}
if err := writeDistribution(opt, tmpDir); err != nil {
return errors.Wrap(err, "write Distribution")
}
if err := writeScripts(opt, tmpDir); err != nil {
return errors.Wrap(err, "write postinstall")
}
if err := writeSecret(opt, orbitRoot); err != nil {
return errors.Wrap(err, "write enroll secret")
}
if opt.StartService {
if err := writeLaunchd(opt, filesystemRoot); err != nil {
return errors.Wrap(err, "write launchd")
}
}
if opt.FleetCertificate != "" {
if err := writeCertificate(opt, orbitRoot); err != nil {
return errors.Wrap(err, "write fleet certificate")
}
}
// TODO gate behind a flag and allow copying a local orbit
// if err := copyFile(
// "./orbit",
// filepath.Join(orbitRoot, "bin", "orbit", "macos", "current", "orbit"),
// 0755,
// ); err != nil {
// return errors.Wrap(err, "write orbit")
// }
// Build package
if err := xarBom(opt, tmpDir); err != nil {
return errors.Wrap(err, "build pkg")
}
generatedPath := filepath.Join(tmpDir, "orbit.pkg")
if len(opt.SignIdentity) != 0 {
log.Info().Str("identity", opt.SignIdentity).Msg("productsign package")
if err := signPkg(generatedPath, opt.SignIdentity); err != nil {
return errors.Wrap(err, "productsign")
}
}
if opt.Notarize {
if err := notarizePkg(generatedPath); err != nil {
return err
}
}
filename := fmt.Sprintf("orbit-osquery_%s_amd64.pkg", opt.Version)
if err := os.Rename(generatedPath, filename); err != nil {
return errors.Wrap(err, "rename pkg")
}
log.Info().Str("path", filename).Msg("wrote pkg package")
return nil
}
func writePackageInfo(opt Options, rootPath string) error {
// PackageInfo is metadata for the pkg
path := filepath.Join(rootPath, "flat", "base.pkg", "PackageInfo")
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosPackageInfoTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func writeScripts(opt Options, rootPath string) error {
// Postinstall script
path := filepath.Join(rootPath, "scripts", "postinstall")
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosPostinstallTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0744); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func writeLaunchd(opt Options, rootPath string) error {
// launchd is the service mechanism on macOS
path := filepath.Join(rootPath, "Library", "LaunchDaemons", "com.fleetdm.orbit.plist")
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosLaunchdTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0644); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func writeDistribution(opt Options, rootPath string) error {
// Distribution file is metadata for the pkg
path := filepath.Join(rootPath, "flat", "Distribution")
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosDistributionTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func writeCertificate(opt Options, orbitRoot string) error {
// Fleet TLS certificate
dstPath := filepath.Join(orbitRoot, "fleet.pem")
if err := copyFile(opt.FleetCertificate, dstPath, 0644); err != nil {
return errors.Wrap(err, "write orbit")
}
return nil
}
// xarBom creates the actual .pkg format. It's a xar archive with a BOM (Bill of
// materials?). See http://bomutils.dyndns.org/tutorial.html.
func xarBom(opt Options, rootPath string) error {
// Adapted from BSD licensed
// https://github.com/go-flutter-desktop/hover/blob/v0.46.2/cmd/packaging/darwin-pkg.go
// Copy payload/scripts
if err := cpio(
filepath.Join(rootPath, "root"),
filepath.Join(rootPath, "flat", "base.pkg", "Payload"),
); err != nil {
return errors.Wrap(err, "cpio Payload")
}
if err := cpio(
filepath.Join(rootPath, "scripts"),
filepath.Join(rootPath, "flat", "base.pkg", "Scripts"),
); err != nil {
return errors.Wrap(err, "cpio Scripts")
}
// Make bom
var cmdMkbom *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmdMkbom = exec.Command("mkbom", filepath.Join(rootPath, "root"), filepath.Join("flat", "base.pkg", "Bom"))
case "linux":
cmdMkbom = exec.Command("mkbom", "-u", "0", "-g", "80", filepath.Join(rootPath, "flat", "root"), filepath.Join("flat", "base.pkg", "Bom"))
}
cmdMkbom.Dir = rootPath
cmdMkbom.Stdout = os.Stdout
cmdMkbom.Stderr = os.Stderr
if err := cmdMkbom.Run(); err != nil {
return errors.Wrap(err, "mkbom")
}
// List files for xar
var files []string
err := filepath.Walk(
filepath.Join(rootPath, "flat"),
func(path string, info os.FileInfo, _ error) error {
relativePath, err := filepath.Rel(filepath.Join(rootPath, "flat"), path)
if err != nil {
return err
}
files = append(files, relativePath)
return nil
},
)
if err != nil {
return errors.Wrap(err, "iterate files")
}
// Make xar
cmdXar := exec.Command("xar", append([]string{"--compression", "none", "-cf", filepath.Join("..", "orbit.pkg")}, files...)...)
cmdXar.Dir = filepath.Join(rootPath, "flat")
cmdXar.Stdout = os.Stdout
cmdXar.Stderr = os.Stderr
if err := cmdXar.Run(); err != nil {
return errors.Wrap(err, "run xar")
}
return nil
}
func cpio(srcPath, dstPath string) error {
// This is the compression routine that is expected for pkg files.
dst, err := os.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return errors.Wrap(err, "open dst")
}
defer dst.Close()
cmdFind := exec.Command("find", ".")
cmdFind.Dir = srcPath
cmdCpio := exec.Command("cpio", "-o", "--format", "odc", "-R", "0:80")
cmdCpio.Dir = srcPath
cmdGzip := exec.Command("gzip", "-c")
// Pipes like this: find | cpio | gzip > dstPath
cmdCpio.Stdin, err = cmdFind.StdoutPipe()
if err != nil {
return errors.Wrap(err, "pipe cpio")
}
cmdGzip.Stdin, err = cmdCpio.StdoutPipe()
if err != nil {
return errors.Wrap(err, "pipe gzip")
}
cmdGzip.Stdout = dst
err = cmdGzip.Start()
if err != nil {
return errors.Wrap(err, "start gzip")
}
err = cmdCpio.Start()
if err != nil {
return errors.Wrap(err, "start cpio")
}
err = cmdFind.Run()
if err != nil {
return errors.Wrap(err, "run find")
}
err = cmdCpio.Wait()
if err != nil {
return errors.Wrap(err, "wait cpio")
}
err = cmdGzip.Wait()
if err != nil {
return errors.Wrap(err, "wait gzip")
}
err = dst.Close()
if err != nil {
return errors.Wrap(err, "close dst")
}
return nil
}
func signPkg(pkgPath, identity string) error {
var outBuf bytes.Buffer
cmdProductsign := exec.Command(
"productsign",
"--sign", identity,
pkgPath,
pkgPath+".signed",
)
cmdProductsign.Stdout = &outBuf
cmdProductsign.Stderr = &outBuf
if err := cmdProductsign.Run(); err != nil {
fmt.Println(outBuf.String())
return errors.Wrap(err, "productsign")
}
if err := os.Rename(pkgPath+".signed", pkgPath); err != nil {
return errors.Wrap(err, "rename signed")
}
return nil
}

View file

@ -0,0 +1,88 @@
package packaging
import (
"context"
"os"
"sync"
"github.com/fatih/color"
"github.com/mitchellh/gon/notarize"
"github.com/mitchellh/gon/staple"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
func notarizePkg(pkgPath string) error {
username, ok := os.LookupEnv("AC_USERNAME")
if !ok {
return errors.New("AC_USERNAME must be set in environment")
}
password, ok := os.LookupEnv("AC_PASSWORD")
if !ok {
return errors.New("AC_PASSWORD must be set in environment")
}
info, err := notarize.Notarize(
context.Background(),
&notarize.Options{
File: pkgPath,
BundleId: "com.fleetdm.orbit",
Username: username,
Password: password,
Status: &statusHuman{
Lock: &sync.Mutex{},
},
},
)
if err != nil {
return errors.Wrap(err, "notarize")
}
log.Info().Str("logs", info.LogFileURL).Msg("notarization completed")
if err := staple.Staple(context.Background(), &staple.Options{File: pkgPath}); err != nil {
return errors.Wrap(err, "staple notarization")
}
return nil
}
// This status plugin copied from
// https://github.com/mitchellh/gon/blob/v0.2.3/cmd/gon/status_human.go since it
// is not exposed from the gon library.
// statusHuman implements notarize.Status and outputs information to
// the CLI for human consumption.
type statusHuman struct {
Prefix string
Lock *sync.Mutex
lastStatus string
}
func (s *statusHuman) Submitting() {
s.Lock.Lock()
defer s.Lock.Unlock()
color.New().Fprintf(os.Stdout, " %sSubmitting file for notarization...\n", s.Prefix)
}
func (s *statusHuman) Submitted(uuid string) {
s.Lock.Lock()
defer s.Lock.Unlock()
color.New().Fprintf(os.Stdout, " %sSubmitted. Request UUID: %s\n", s.Prefix, uuid)
color.New().Fprintf(
os.Stdout, " %sWaiting for results from Apple. This can take minutes to hours.\n", s.Prefix)
}
func (s *statusHuman) Status(info notarize.Info) {
s.Lock.Lock()
defer s.Lock.Unlock()
if info.Status != s.lastStatus {
s.lastStatus = info.Status
color.New().Fprintf(os.Stdout, " %sStatus: %s\n", s.Prefix, info.Status)
}
}

View file

@ -0,0 +1,83 @@
package packaging
import "text/template"
// Best reference I could find:
// http://s.sudre.free.fr/Stuff/Ivanhoe/FLAT.html
var macosPackageInfoTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
`<pkg-info format-version="2" identifier="{{.Identifier}}.base.pkg" version="{{.Version}}" install-location="/" auth="root">
<scripts>
<postinstall file="./postinstall"/>
</scripts>
<bundle-version>
</bundle-version>
</pkg-info>
`))
// Reference:
// https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html
var macosDistributionTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
`<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="2">
<title>Orbit</title>
<choices-outline>
<line choice="choiceBase"/>
</choices-outline>
<choice id="choiceBase" title="Orbit osquery" enabled="false" selected="true" description="Standard installation for Orbit osquery.">
<pkg-ref id="{{.Identifier}}.base.pkg"/>
</choice>
<pkg-ref id="{{.Identifier}}.base.pkg" version="{{.Version}}" auth="root">#base.pkg</pkg-ref>
</installer-gui-script>
`))
var macosPostinstallTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
`#!/bin/bash
ln -sf /var/lib/orbit/bin/orbit/macos/{{.OrbitChannel}}/orbit /var/lib/orbit/bin/orbit/orbit
ln -sf /var/lib/orbit/bin/orbit/orbit /usr/local/bin/orbit
{{ if .StartService -}}
launchctl unload /Library/LaunchDaemons/com.fleetdm.orbit.plist
launchctl load -w /Library/LaunchDaemons/com.fleetdm.orbit.plist
{{- end }}
`))
// TODO set Nice?
//
//Note it's important not to start the orbit binary in
// `/usr/local/bin/orbit` because this is a path that users usually have write
// access to, and running that binary with launchd can become a privilege
// escalation vector.
var macosLaunchdTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.fleetdm.orbit</string>
<key>ProgramArguments</key>
<array>
<string>/var/lib/orbit/bin/orbit/orbit</string>
</array>
<key>StandardOutPath</key>
<string>/var/log/orbit/orbit.stdout.log</string>
<key>StandardErrorPath</key>
<string>/var/log/orbit/orbit.stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>ORBIT_UPDATE_URL</key><string>{{ .UpdateURL }}</string>
<key>ORBIT_ORBIT_CHANNEL</key><string>{{ .OrbitChannel }}</string>
<key>ORBIT_OSQUERYD_CHANNEL</key><string>{{ .OsquerydChannel }}</string>
{{ if .Insecure }}<key>ORBIT_INSECURE</key><string>true</string>{{ end }}
{{ if .FleetURL }}<key>ORBIT_FLEET_URL</key><string>{{ .FleetURL }}</string>{{ end }}
{{ if .FleetCertificate }}<key>ORBIT_FLEET_CERTIFICATE</key><string>/var/lib/orbit/fleet.pem</string>{{ end }}
{{ if .EnrollSecret }}<key>ORBIT_ENROLL_SECRET_PATH</key><string>/var/lib/orbit/secret.txt</string>{{ end }}
{{ if .Debug }}<key>ORBIT_DEBUG</key><string>true</string>{{ end }}
</dict>
<key>KeepAlive</key><true/>
<key>RunAtLoad</key><true/>
<key>ThrottleInterval</key>
<integer>10</integer>
</dict>
</plist>
`))

View file

@ -0,0 +1,131 @@
// package packaging provides tools for buildin Orbit installation packages.
package packaging
import (
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/update"
"github.com/fleetdm/orbit/pkg/update/filestore"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// Options are the configurable options provided for the package.
type Options struct {
// FleetURL is the URL to the Fleet server.
FleetURL string
// EnrollSecret is the enroll secret used to authenticate to the Fleet
// server.
EnrollSecret string
// Version is the version number for this package.
Version string
// Identifier is the identifier (eg. com.fleetdm.orbit) for the package product.
Identifier string
// StartService is a boolean indicating whether to start a system-specific
// background service.
StartService bool
// Insecure enables insecure TLS connections for the generated package.
Insecure bool
// SignIdentity is the codesigning identity to use (only macOS at this time)
SignIdentity string
// Notarize sets whether macOS packages should be Notarized.
Notarize bool
// FleetCertificate is a path to a server certificate to include in the package.
FleetCertificate string
// OrbitChannel is the update channel to use for Orbit.
OrbitChannel string
// OsquerydChannel is the update channel to use for Osquery (osqueryd).
OsquerydChannel string
// UpdateURL is the base URL of the update server (TUF repository).
UpdateURL string
// UpdateRoots is the root JSON metadata for update server (TUF repository).
UpdateRoots string
// Debug determines whether to enable debug logging for the agent.
Debug bool
}
func copyFile(srcPath, dstPath string, perm os.FileMode) error {
src, err := os.Open(srcPath)
if err != nil {
return errors.Wrap(err, "open src for copy")
}
defer src.Close()
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
return errors.Wrap(err, "create dst dir for copy")
}
dst, err := os.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, perm)
if err != nil {
return errors.Wrap(err, "open dst for copy")
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return errors.Wrap(err, "copy src to dst")
}
return nil
}
func initializeUpdates(updateOpt update.Options) error {
localStore, err := filestore.New(filepath.Join(updateOpt.RootDirectory, "tuf-metadata.json"))
if err != nil {
return errors.Wrap(err, "failed to create local metadata store")
}
updateOpt.LocalStore = localStore
updater, err := update.New(updateOpt)
if err != nil {
return errors.Wrap(err, "failed to init updater")
}
if err := updater.UpdateMetadata(); err != nil {
return errors.Wrap(err, "failed to update metadata")
}
osquerydPath, err := updater.Get("osqueryd", updateOpt.OsquerydChannel)
if err != nil {
return errors.Wrap(err, "failed to get osqueryd")
}
log.Debug().Str("path", osquerydPath).Msg("got osqueryd")
orbitPath, err := updater.Get("orbit", updateOpt.OrbitChannel)
if err != nil {
return errors.Wrap(err, "failed to get orbit")
}
log.Debug().Str("path", orbitPath).Msg("got orbit")
return nil
}
func writeSecret(opt Options, orbitRoot string) error {
// Enroll secret
path := filepath.Join(orbitRoot, "secret.txt")
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "mkdir")
}
if err := ioutil.WriteFile(path, []byte(opt.EnrollSecret), 0600); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func chmodRecursive(path string, perm os.FileMode) error {
return filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return errors.Wrap(err, "walk error")
}
if err := os.Chmod(path, perm); err != nil {
return errors.Wrap(err, "chmod")
}
return nil
})
}

View file

@ -0,0 +1,7 @@
package packaging
import "github.com/goreleaser/nfpm/v2/rpm"
func BuildRPM(opt Options) error {
return buildNFPM(opt, rpm.Default)
}

View file

@ -0,0 +1,109 @@
package packaging
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/packaging/wix"
"github.com/fleetdm/orbit/pkg/update"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// BuildMSI builds a Windows .msi.
func BuildMSI(opt Options) error {
// Initialize directories
tmpDir, err := ioutil.TempDir("", "orbit-package")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(tmpDir)
log.Debug().Str("path", tmpDir).Msg("created temp dir")
filesystemRoot := filepath.Join(tmpDir, "root")
if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create root dir")
}
orbitRoot := filesystemRoot
if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "create orbit dir")
}
// Initialize autoupdate metadata
updateOpt := update.DefaultOptions
updateOpt.Platform = "windows"
updateOpt.RootDirectory = orbitRoot
updateOpt.OrbitChannel = opt.OrbitChannel
updateOpt.OsquerydChannel = opt.OsquerydChannel
updateOpt.ServerURL = opt.UpdateURL
if opt.UpdateRoots != "" {
updateOpt.RootKeys = opt.UpdateRoots
}
if err := initializeUpdates(updateOpt); err != nil {
return errors.Wrap(err, "initialize updates")
}
// Write files
if err := writeSecret(opt, orbitRoot); err != nil {
return errors.Wrap(err, "write enroll secret")
}
if err := writeWixFile(opt, tmpDir); err != nil {
return errors.Wrap(err, "write wix file")
}
// Make sure permissions are permissive so that the `wine` user in the Wix Docker container can access files.
if err := chmodRecursive(tmpDir, os.ModePerm); err != nil {
return err
}
if err := wix.Heat(tmpDir); err != nil {
return errors.Wrap(err, "package root files")
}
if err := wix.TransformHeat(filepath.Join(tmpDir, "heat.wxs")); err != nil {
return errors.Wrap(err, "transform heat")
}
if err := wix.Candle(tmpDir); err != nil {
return errors.Wrap(err, "build package")
}
if err := wix.Light(tmpDir); err != nil {
return errors.Wrap(err, "build package")
}
filename := fmt.Sprintf("orbit-osquery_%s.msi", opt.Version)
if err := os.Rename(filepath.Join(tmpDir, "orbit.msi"), filename); err != nil {
return errors.Wrap(err, "rename msi")
}
log.Info().Str("path", filename).Msg("wrote msi package")
return nil
}
func writeWixFile(opt Options, rootPath string) error {
// PackageInfo is metadata for the pkg
path := filepath.Join(rootPath, "main.wxs")
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := windowsWixTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0o666); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}

View file

@ -0,0 +1,87 @@
package packaging
import "text/template"
// Partially adapted from Launcher's wix XML in
// https://github.com/kolide/launcher/blob/master/pkg/packagekit/internal/assets/main.wxs.
var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
`<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product
Id="C2C2437D-0562-465E-A0BB-2C4484025BD6"
Name="Orbit osquery"
Language="1033"
Version="{{.Version}}"
Manufacturer="Fleet Device Management (fleetdm.com)"
UpgradeCode="B681CB20-107E-428A-9B14-2D3C1AFED244" >
<Package
Id="*"
Keywords='orbit osquery'
Description="Orbit osquery"
InstallerVersion="500"
Compressed="yes"
InstallScope="perMachine"
InstallPrivileges="elevated"
Languages="1033" />
<MediaTemplate EmbedCab="yes" />
<MajorUpgrade AllowDowngrades="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="ORBITROOT" Name="Orbit">
<Component Id="C_ORBITROOT" Guid="A7DFD09E-2D2B-4535-A04F-5D4DE90F3863">
<CreateFolder>
<PermissionEx Sddl="O:SYG:SYD:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;BU)" />
</CreateFolder>
</Component>
<Directory Id="ORBITBIN" Name="bin">
<Directory Id="ORBITBINORBIT" Name="orbit">
<Component Id="C_ORBITBIN" Guid="AF347B4E-B84B-4DD4-9C4D-133BE17B613D">
<CreateFolder>
<PermissionEx Sddl="O:SYG:SYD:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;BU)" />
</CreateFolder>
<File Source="root\bin\orbit\windows\stable\orbit.exe">
<PermissionEx Sddl="O:SYG:SYD:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;BU)" />
</File>
<ServiceInstall
Name="Orbit osquery"
Account="NT AUTHORITY\SYSTEM"
ErrorControl="ignore"
Start="auto"
Type="ownProcess"
Arguments='--root-dir "[ORBITROOT]." --log-file "[ORBITROOT]orbit-log.txt" {{ if .FleetURL }}--fleet-url "{{ .FleetURL }}"{{ end }} {{ if .EnrollSecret }}--enroll-secret-path "[ORBITROOT]secret.txt"{{ end }} {{if .Insecure }}--insecure{{ end }} {{ if .UpdateURL }}--update-url "{{ .UpdateURL }}" {{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}"'
>
<util:ServiceConfig
FirstFailureActionType="restart"
SecondFailureActionType="restart"
ThirdFailureActionType="restart"
ResetPeriodInDays="1"
RestartServiceDelayInSeconds="1"
/>
</ServiceInstall>
<ServiceControl
Id="StartOrbitService"
Name="Orbit osquery"
Start="install"
Stop="both"
Remove="uninstall"
/>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id="Orbit" Title="Orbit osquery" Level="1" Display="hidden">
<ComponentGroupRef Id="OrbitFiles" />
<ComponentRef Id="C_ORBITBIN" />
<ComponentRef Id="C_ORBITROOT" />
</Feature>
</Product>
</Wix>
`))

View file

@ -0,0 +1,125 @@
package wix
import (
"bytes"
"encoding/xml"
"io/ioutil"
"strings"
"github.com/pkg/errors"
)
type node struct {
XMLName xml.Name
Attrs attrs `xml:",any,attr"`
Content string `xml:",chardata"`
Children []*node `xml:",any"`
}
type attrs []*xml.Attr
// Get the value of the attr with the provided name, otherwise returning an
// empty string.
func (a attrs) Get(name string) string {
for _, attr := range a {
if attr.Name.Local == name {
return attr.Value
}
}
return ""
}
func xmlAttr(name, value string) *xml.Attr {
return &xml.Attr{Name: xml.Name{Local: name}, Value: value}
}
func xmlNode(name string, attrs ...*xml.Attr) *node {
return &node{
XMLName: xml.Name{Local: name},
Attrs: attrs,
}
}
func TransformHeat(path string) error {
contents, err := ioutil.ReadFile(path)
if err != nil {
return errors.Wrap(err, "read file")
}
// Eliminate line feeds (they cause extra junk in the result)
contents = bytes.ReplaceAll(contents, []byte("\r"), []byte(""))
var n node
if err := xml.Unmarshal(contents, &n); err != nil {
return errors.Wrap(err, "unmarshal xml")
}
stack := []*node{}
if err := transform(&n, &stack); err != nil {
return errors.Wrap(err, "in transform")
}
contents, err = xml.MarshalIndent(n, "", " ")
if err != nil {
return errors.Wrap(err, "marshal xml")
}
if err := ioutil.WriteFile(path, contents, 0o600); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func transform(cur *node, stack *[]*node) error {
// Clear namespace on all elements (generates unnecessarily noisy output if
// this is not done).
cur.XMLName.Space = ""
// Change permissions for all files
if cur.XMLName.Local == "File" {
// This SDDL copied directly from osqueryd.exe after a regular
// osquery MSI install. We assume that osquery is getting the
// permissions correct and use exactly the same for our files.
// Using this cryptic string seems to be the only way to disable
// permission inheritance in a WiX package, so we may not have
// any option for something more readable.
//
// Permissions:
// Disable inheritance
// SYSTEM: read/write/execute
// Administrators: read/write/execute
// Users: read/execute
sddl := "O:SYG:SYD:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;BU)"
if strings.HasSuffix(cur.Attrs.Get("Source"), "secret.txt") {
// This SDDL copied from properly configured file on a Windows 10
// machine. Permissions are same as above but with access removed
// for regular users.
//
// Permissions:
// Disable inheritance
// SYSTEM: read/write/execute
// Administrators: read/write/execute
sddl = "O:SYG:SYD:PAI(A;;FA;;;SY)(A;;FA;;;BA)"
}
cur.Children = append(cur.Children, xmlNode(
"PermissionEx",
xmlAttr("Sddl", sddl),
))
}
// push current node onto stack
*stack = append(*stack, cur)
// Recursively walk the children
for _, child := range cur.Children {
if err := transform(child, stack); err != nil {
return err
}
}
// pop current node from stack
*stack = (*stack)[:len(*stack)-1]
return nil
}

View file

@ -0,0 +1,88 @@
// Package wix runs the WiX packaging tools via Docker.
//
// WiX's documentation is available at https://wixtoolset.org/.
package wix
import (
"os"
"os/exec"
"github.com/pkg/errors"
)
const (
directoryReference = "ORBITROOT"
)
// Heat runs the WiX Heat command on the provided directory.
//
// The Heat command creates XML fragments allowing WiX to include the entire
// directory. See
// https://wixtoolset.org/documentation/manual/v3/overview/heat.html.
func Heat(path string) error {
cmd := exec.Command(
"docker", "run", "--rm", "--platform", "linux/386",
"--volume", path+":/wix", // mount volume
"dactiv/wix:latest", // image name
"heat", "dir", "root", // command in image
"-out", "heat.wxs",
"-gg", "-g1", // generate UUIDs (required by wix)
"-cg", "OrbitFiles", // set ComponentGroup name
"-scom", "-sfrag", "-srd", "-sreg", // suppress unneccesary generated items
"-dr", directoryReference, // set reference name
"-ke", // keep empty directories
)
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "heat failed")
}
return nil
}
// Candle runs the WiX Candle command on the provided directory.
//
// See
// https://wixtoolset.org/documentation/manual/v3/overview/candle.html.
func Candle(path string) error {
cmd := exec.Command(
"docker", "run", "--rm", "--platform", "linux/386",
"--volume", path+":/wix", // mount volume
"dactiv/wix:latest", // image name
"candle", "heat.wxs", "main.wxs", // command in image
"-ext", "WixUtilExtension",
"-arch", "x64",
)
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "candle failed")
}
return nil
}
// Light runs the WiX Light command on the provided directory.
//
// See
// https://wixtoolset.org/documentation/manual/v3/overview/light.html.
func Light(path string) error {
cmd := exec.Command(
"docker", "run", "--rm", "--platform", "linux/386",
"--volume", path+":/wix", // mount volume
"dactiv/wix:latest", // image name
"light", "heat.wixobj", "main.wixobj", // command in image
"-ext", "WixUtilExtension",
"-b", "root", // Set directory for finding heat files
"-out", "orbit.msi",
"-sval", // skip validation (otherwise Wine crashes)
)
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "light failed")
}
return nil
}

View file

@ -0,0 +1,28 @@
//+build !windows
package platform
import (
"os"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/pkg/errors"
)
// ChmodExecutableDirectory sets the appropriate permissions on an executable
// file. On POSIX this is a normal chmod call.
func ChmodExecutableDirectory(path string) error {
if err := os.Chmod(path, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "chmod executable directory")
}
return nil
}
// ChmodExecutable sets the appropriate permissions on the parent directory of
// an executable file. On POSIX this is a regular chmod call.
func ChmodExecutable(path string) error {
if err := os.Chmod(path, constant.DefaultExecutableMode); err != nil {
return errors.Wrap(err, "chmod executable")
}
return nil
}

View file

@ -0,0 +1,50 @@
//+build windows
package platform
import (
"github.com/fleetdm/orbit/pkg/constant"
"github.com/pkg/errors"
"github.com/hectane/go-acl"
)
const (
fullControl = uint32(2032127)
readAndExecute = uint32(131241)
)
// ChmodExecutableDirectory sets the appropriate permissions on the parent
// directory of an executable file. On Windows this involves setting the
// appropriate ACLs.
func ChmodExecutableDirectory(path string) error {
if err := acl.Apply(
path,
true,
false,
acl.GrantSid(fullControl, constant.SystemSID),
acl.GrantSid(fullControl, constant.AdminSID),
acl.GrantSid(readAndExecute, constant.UserSID),
); err != nil {
return errors.Wrap(err, "apply ACLs")
}
return nil
}
// ChmodExecutable sets the appropriate permissions on an executable file. On
// Windows this involves setting the appropriate ACLs.
func ChmodExecutable(path string) error {
if err := acl.Apply(
path,
true,
false,
acl.GrantSid(fullControl, constant.SystemSID),
acl.GrantSid(fullControl, constant.AdminSID),
acl.GrantSid(readAndExecute, constant.UserSID),
); err != nil {
return errors.Wrap(err, "apply ACLs")
}
return nil
}

View file

@ -0,0 +1,115 @@
package process
import (
"context"
"fmt"
"os"
"os/exec"
"runtime"
"time"
)
type ExecCmd interface {
Start() error
Wait() error
OsProcess() OsProcess
}
type OsProcess interface {
Signal(os.Signal) error
Kill() error
}
type execCmdWrapper struct {
*exec.Cmd
}
func (e *execCmdWrapper) OsProcess() OsProcess {
return e.Process
}
type Process struct {
ExecCmd
}
func NewWithCmd(cmd *exec.Cmd) *Process {
return &Process{ExecCmd: &execCmdWrapper{Cmd: cmd}}
}
func newWithMock(cmd ExecCmd) *Process {
return &Process{ExecCmd: cmd}
}
// WaitOrKill waits for the already-started process by calling its Wait method.
//
// If the process does not return before ctx is done, WaitOrKill sends it the
// given interrupt signal. If killDelay is positive, WaitOrKill waits that
// additional period for Wait to return before sending os.Kill.
//
// Adapted from Go core:
// https://github.com/golang/go/blob/8981092d71aee273d27b0e11cf932a34d4d365c1/src/cmd/go/script_test.go#L1131-L1190
func (p *Process) WaitOrKill(ctx context.Context, killDelay time.Duration) error {
if p.OsProcess() == nil {
return fmt.Errorf("WaitOrKill requires a non-nil OsProcess - missing Start call?")
}
errc := make(chan error)
go func() {
select {
case errc <- nil:
return
case <-ctx.Done():
}
err := p.OsProcess().Signal(stopSignal())
if err == nil {
err = ctx.Err() // Report ctx.Err() as the reason we interrupted.
} else if err.Error() == "os: process already finished" {
errc <- nil
return
}
if killDelay > 0 {
timer := time.NewTimer(killDelay)
select {
// Report ctx.Err() as the reason we interrupted the process...
case errc <- ctx.Err():
timer.Stop()
return
// ...but after killDelay has elapsed, fall back to a stronger signal.
case <-timer.C:
}
// Wait still hasn't returned.
// Kill the process harder to make sure that it exits.
//
// Ignore any error: if cmd.Process has already terminated, we still
// want to send ctx.Err() (or the error from the Interrupt call)
// to properly attribute the signal that may have terminated it.
_ = p.OsProcess().Kill()
}
errc <- err
}()
waitErr := p.Wait()
if interruptErr := <-errc; interruptErr != nil {
return interruptErr
}
return waitErr
}
// stopSignal returns the appropriate signal to use to request that a process
// stop execution.
//
// Copied from Go core:
// https://github.com/golang/go/blob/8981092d71aee273d27b0e11cf932a34d4d365c1/src/cmd/go/script_test.go#L1119-L1129
func stopSignal() os.Signal {
if runtime.GOOS == "windows" {
// Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
// Windows; using it with os.Process.Signal will return an error.”
// Fall back to Kill instead.
return os.Kill
}
return os.Interrupt
}

View file

@ -0,0 +1,62 @@
package process
import (
"os"
"github.com/stretchr/testify/mock"
)
type mockOsProcess struct {
mock.Mock
OsProcess
}
func (m *mockOsProcess) Signal(sig os.Signal) error {
args := m.Called(sig)
err := args.Error(0)
if err == nil {
return nil
}
return err.(error)
}
func (m *mockOsProcess) Kill() error {
args := m.Called()
err := args.Error(0)
if err == nil {
return nil
}
return err.(error)
}
type mockExecCmd struct {
mock.Mock
ExecCmd
}
func (m *mockExecCmd) Start() error {
args := m.Called()
err := args.Error(0)
if err == nil {
return nil
}
return err.(error)
}
func (m *mockExecCmd) Wait() error {
args := m.Called()
err := args.Error(0)
if err == nil {
return nil
}
return err.(error)
}
func (m *mockExecCmd) OsProcess() OsProcess {
args := m.Called()
proc := args.Get(0)
if proc == nil {
return nil
}
return proc.(OsProcess)
}

View file

@ -0,0 +1,102 @@
package process
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestWaitOrKillNilProcess(t *testing.T) {
// Process not started
mockCmd := &mockExecCmd{}
defer mock.AssertExpectationsForObjects(t, mockCmd)
mockCmd.On("OsProcess").Return(nil)
p := newWithMock(mockCmd)
err := p.WaitOrKill(context.Background(), 1*time.Second)
require.Error(t, err)
assert.Contains(t, err.Error(), "non-nil OsProcess")
}
func TestWaitOrKillProcessCompleted(t *testing.T) {
// Process already completed
mockCmd := &mockExecCmd{}
mockProcess := &mockOsProcess{}
defer mock.AssertExpectationsForObjects(t, mockCmd, mockProcess)
mockCmd.On("OsProcess").Return(mockProcess)
mockCmd.On("Wait").Return(nil)
p := newWithMock(mockCmd)
err := p.WaitOrKill(context.Background(), 10*time.Millisecond)
require.NoError(t, err)
}
func TestWaitOrKillProcessCompletedError(t *testing.T) {
// Process already completed with error
mockCmd := &mockExecCmd{}
mockProcess := &mockOsProcess{}
defer mock.AssertExpectationsForObjects(t, mockCmd, mockProcess)
mockCmd.On("OsProcess").Return(mockProcess)
mockCmd.On("Wait").After(10 * time.Millisecond).Return(fmt.Errorf("super bad"))
p := newWithMock(mockCmd)
err := p.WaitOrKill(context.Background(), 10*time.Millisecond)
require.Error(t, err)
assert.Contains(t, err.Error(), "super bad")
}
func TestWaitOrKillWait(t *testing.T) {
// Process completes after the wait call and after the signal is sent
mockCmd := &mockExecCmd{}
mockProcess := &mockOsProcess{}
defer mock.AssertExpectationsForObjects(t, mockCmd, mockProcess)
mockCmd.On("OsProcess").Return(mockProcess)
mockCmd.On("Wait").After(5 * time.Millisecond).Return(nil)
mockProcess.On("Signal", stopSignal()).Return(nil)
p := newWithMock(mockCmd)
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := p.WaitOrKill(ctx, 10*time.Millisecond)
require.Error(t, err)
assert.Contains(t, err.Error(), "context canceled")
}
func TestWaitOrKillWaitSignalCompleted(t *testing.T) {
// Process completes after the wait call and before the signal is sent
mockCmd := &mockExecCmd{}
mockProcess := &mockOsProcess{}
defer mock.AssertExpectationsForObjects(t, mockCmd, mockProcess)
mockCmd.On("OsProcess").Return(mockProcess)
mockCmd.On("Wait").After(10 * time.Millisecond).Return(nil)
mockProcess.On("Signal", stopSignal()).Return(fmt.Errorf("os: process already finished"))
p := newWithMock(mockCmd)
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := p.WaitOrKill(ctx, 5*time.Millisecond)
require.NoError(t, err)
}
func TestWaitOrKillWaitKilled(t *testing.T) {
// Process is killed after the wait call and signal
mockCmd := &mockExecCmd{}
mockProcess := &mockOsProcess{}
defer mock.AssertExpectationsForObjects(t, mockCmd, mockProcess)
mockCmd.On("OsProcess").Return(mockProcess)
mockCmd.On("Wait").After(10 * time.Millisecond).Return(fmt.Errorf("killed"))
mockProcess.On("Signal", stopSignal()).Return(nil)
mockProcess.On("Kill").Return(nil)
p := newWithMock(mockCmd)
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := p.WaitOrKill(ctx, 5*time.Millisecond)
require.Error(t, err)
assert.Contains(t, err.Error(), "context canceled")
}

View file

@ -0,0 +1,69 @@
// package badgerstore implements the go-tuf LocalStore interface using Badger
// as a backing store.
package badgerstore
import (
"encoding/json"
"strings"
"github.com/dgraph-io/badger/v2"
"github.com/theupdateframework/go-tuf/client"
)
const (
keyPrefix = ":tuf-metadata:"
)
type badgerStore struct {
db *badger.DB
}
// New creates the new store given the badger DB instance.
func New(db *badger.DB) client.LocalStore {
return &badgerStore{db: db}
}
// SetMeta stores the provided metadata.
func (b *badgerStore) SetMeta(name string, meta json.RawMessage) error {
if err := b.db.Update(func(tx *badger.Txn) error {
if err := tx.Set([]byte(keyPrefix+name), meta); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// GetMeta returns all of the saved metadata.
func (b *badgerStore) GetMeta() (map[string]json.RawMessage, error) {
res := make(map[string]json.RawMessage)
// Iterate all keys with matching prefix
// Adapted from Badger docs
if err := b.db.View(func(txn *badger.Txn) error {
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
prefix := []byte(keyPrefix)
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
item := it.Item()
k := item.Key()
if err := item.Value(func(v []byte) error {
// Remember to strip prefix
strippedKey := strings.TrimPrefix(string(k), keyPrefix)
res[strippedKey] = json.RawMessage(v)
return nil
}); err != nil {
return err
}
}
return nil
}); err != nil {
return res, err
}
return res, nil
}

View file

@ -0,0 +1,31 @@
package badgerstore
import (
"encoding/json"
"testing"
"github.com/dgraph-io/badger/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBadgerStore(t *testing.T) {
badgerClient, err := badger.Open(badger.DefaultOptions("").WithInMemory(true))
require.NoError(t, err)
store := New(badgerClient)
expected := map[string]json.RawMessage{
"test": json.RawMessage("json"),
"test2": json.RawMessage("json2"),
"root.json": json.RawMessage(`[{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"0994148e5242118d1d6a9a397a3646e0423545a37794a791c28aa39de3b0c523"}}]`),
}
for k, v := range expected {
require.NoError(t, store.SetMeta(k, v))
}
res, err := store.GetMeta()
require.NoError(t, err)
assert.Equal(t, expected, res)
}

16
orbit/pkg/update/file.go Normal file
View file

@ -0,0 +1,16 @@
package update
import "os"
// fileDestination wraps the standard os.File with a Delete method for
// compatibility with the go-tuf Destination interface.
// Adapted from
// https://github.com/theupdateframework/go-tuf/blob/master/cmd/tuf-client/get.go
type fileDestination struct {
*os.File
}
func (f *fileDestination) Delete() error {
_ = f.Close()
return os.Remove(f.Name())
}

View file

@ -0,0 +1,94 @@
package filestore
import (
"encoding/json"
"os"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/pkg/errors"
"github.com/theupdateframework/go-tuf/client"
)
type fileStore struct {
filename string
metadata metadataMap
}
type metadataMap map[string]json.RawMessage
func New(filename string) (client.LocalStore, error) {
store := &fileStore{filename: filename}
if err := store.readData(); err != nil {
return nil, err
}
return store, nil
}
// SetMeta stores the provided metadata.
func (s *fileStore) SetMeta(name string, meta json.RawMessage) error {
if s.metadata == nil {
if err := s.readData(); err != nil {
return err
}
}
s.metadata[name] = meta
if err := s.writeData(); err != nil {
return err
}
return nil
}
// GetMeta returns all of the saved metadata.
func (s *fileStore) GetMeta() (map[string]json.RawMessage, error) {
if s.metadata == nil {
if err := s.readData(); err != nil {
return nil, err
}
}
return s.metadata, nil
}
func (s *fileStore) readData() error {
stat, err := os.Stat(s.filename)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return errors.Wrap(err, "stat file store")
} else if errors.Is(err, os.ErrNotExist) {
// initialize empty
s.metadata = metadataMap{}
return nil
} else if !stat.Mode().IsRegular() {
return errors.New("expected file store to be regular file")
}
f, err := os.Open(s.filename)
if err != nil {
return errors.Wrap(err, "open file store")
}
defer f.Close()
var meta metadataMap
if err := json.NewDecoder(f).Decode(&meta); err != nil {
return errors.Wrap(err, "read file store")
}
s.metadata = meta
return nil
}
func (s *fileStore) writeData() error {
f, err := os.OpenFile(s.filename, os.O_RDWR|os.O_CREATE, constant.DefaultFileMode)
if err != nil {
return errors.Wrap(err, "open file store")
}
defer f.Close()
if err := json.NewEncoder(f).Encode(s.metadata); err != nil {
return errors.Wrap(err, "read file store")
}
return nil
}

View file

@ -0,0 +1,66 @@
package filestore
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFileStorePathError(t *testing.T) {
t.Parallel()
tmpDir, err := ioutil.TempDir("", "filestore-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "metadata.json"), constant.DefaultDirMode))
store, err := New(filepath.Join(tmpDir, "metadata.json"))
assert.Error(t, err)
assert.Nil(t, store)
}
func TestFileStore(t *testing.T) {
t.Parallel()
tmpDir, err := ioutil.TempDir("", "filestore-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
store, err := New(filepath.Join(tmpDir, "metadata.json"))
require.NoError(t, err)
expected := map[string]json.RawMessage{
"test": json.RawMessage("{}"),
"test2": json.RawMessage("{}"),
"root.json": json.RawMessage(`[{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"0994148e5242118d1d6a9a397a3646e0423545a37794a791c28aa39de3b0c523"}}]`),
}
for k, v := range expected {
require.NoError(t, store.SetMeta(k, v))
}
res, err := store.GetMeta()
require.NoError(t, err)
assert.Equal(t, expected, res)
// Reopen and check
store, err = New(filepath.Join(tmpDir, "metadata.json"))
require.NoError(t, err)
res, err = store.GetMeta()
require.NoError(t, err)
assert.Equal(t, expected, res)
// Update and check
expected["test"] = json.RawMessage("[]")
require.NoError(t, store.SetMeta("test", expected["test"]))
res, err = store.GetMeta()
require.NoError(t, err)
assert.Equal(t, expected, res)
}

59
orbit/pkg/update/hash.go Normal file
View file

@ -0,0 +1,59 @@
package update
import (
"bytes"
"crypto/sha256"
"crypto/sha512"
"hash"
"io"
"os"
"github.com/pkg/errors"
"github.com/theupdateframework/go-tuf/data"
)
// CheckFileHash checks the file at the local path against the provided hash
// functions.
func CheckFileHash(meta *data.TargetFileMeta, localPath string) error {
hashFunc, hashVal, err := selectHashFunction(meta)
if err != nil {
return err
}
f, err := os.Open(localPath)
if err != nil {
return errors.Wrap(err, "open file for hash")
}
defer f.Close()
if _, err := io.Copy(hashFunc, f); err != nil {
return errors.Wrap(err, "read file for hash")
}
if !bytes.Equal(hashVal, hashFunc.Sum(nil)) {
return errors.Errorf("hash %s does not match expected: %s", data.HexBytes(hashFunc.Sum(nil)), data.HexBytes(hashVal))
}
return nil
}
// selectHashFunction returns the first matching hash function and expected
// hash, otherwise returning an error if not matching hash can be found.
//
// SHA512 is preferred, and SHA256 is returned if 512 is not available.
func selectHashFunction(meta *data.TargetFileMeta) (hash.Hash, []byte, error) {
for hashName, hashVal := range meta.Hashes {
if hashName == "sha512" {
return sha512.New(), hashVal, nil
}
}
for hashName, hashVal := range meta.Hashes {
if hashName == "sha256" {
return sha256.New(), hashVal, nil
}
}
return nil, nil, errors.Errorf("no matching hash function found: %v", meta.HashAlgorithms())
}

View file

@ -0,0 +1,23 @@
// +build !windows
package update
import (
"github.com/fleetdm/orbit/pkg/constant"
"github.com/theupdateframework/go-tuf/client"
)
var (
// DefaultOptions are the default options to use when creating an update
// client.
DefaultOptions = Options{
RootDirectory: "/var/lib/orbit",
ServerURL: defaultURL,
RootKeys: defaultRootKeys,
LocalStore: client.MemoryLocalStore(),
InsecureTransport: false,
Platform: constant.PlatformName,
OrbitChannel: "stable",
OsquerydChannel: "stable",
}
)

View file

@ -0,0 +1,31 @@
package update
import (
"os"
"path/filepath"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/theupdateframework/go-tuf/client"
)
var (
// DefaultOptions are the default options to use when creating an update
// client.
DefaultOptions = Options{
RootDirectory: `C:\Program Files\Orbit`,
ServerURL: defaultURL,
RootKeys: defaultRootKeys,
LocalStore: client.MemoryLocalStore(),
InsecureTransport: false,
Platform: constant.PlatformName,
OrbitChannel: "stable",
OsquerydChannel: "stable",
}
)
func init() {
// Set root directory to value of ProgramFiles environment variable if not set
if dir := os.Getenv("ProgramFiles"); dir != "" {
DefaultOptions.RootDirectory = filepath.Join(dir, "Orbit")
}
}

153
orbit/pkg/update/runner.go Normal file
View file

@ -0,0 +1,153 @@
package update
import (
"bytes"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// RunnerOptions is options provided for the update runner.
type RunnerOptions struct {
// CheckInterval is the interval to check for updates.
CheckInterval time.Duration
// Targets is the names of the artifacts to watch for updates.
Targets map[string]string
}
// Runner is a specialized runner for the updater. It is designed with Execute and
// Interrupt functions to be compatible with oklog/run.
type Runner struct {
client *Updater
opt RunnerOptions
cancel chan struct{}
hashCache map[string][]byte
}
// NewRunner creates a new runner with the provided options. The runner must be
// started with Execute.
func NewRunner(client *Updater, opt RunnerOptions) (*Runner, error) {
if opt.CheckInterval <= 0 {
return nil, errors.New("Runner must be configured with interval greater than 0")
}
if len(opt.Targets) == 0 {
return nil, errors.New("Runner must have nonempty subscriptions")
}
// Initialize hash cache
cache := make(map[string][]byte)
for target, channel := range opt.Targets {
meta, err := client.Lookup(target, channel)
if err != nil {
return nil, errors.Wrap(err, "initialize update cache")
}
_, hash, err := selectHashFunction(meta)
if err != nil {
return nil, errors.Wrap(err, "select hash for cache")
}
cache[target] = hash
}
return &Runner{
client: client,
opt: opt,
// chan gets capacity of 1 so we don't end up hung if Interrupt is
// called after Execute has already returned.
cancel: make(chan struct{}, 1),
hashCache: cache,
}, nil
}
// Execute begins a loop checking for updates.
func (r *Runner) Execute() error {
ticker := time.NewTicker(r.opt.CheckInterval)
defer ticker.Stop()
// Run until cancel or returning an error
for {
select {
case <-r.cancel:
return nil
case <-ticker.C:
// On each tick, check for updates
didUpdate, err := r.updateAction()
if err != nil {
log.Info().Err(err).Msg("update failed")
}
if didUpdate {
log.Info().Msg("exiting due to successful update")
return nil
}
}
}
}
func (r *Runner) updateAction() (bool, error) {
var didUpdate bool
if err := r.client.UpdateMetadata(); err != nil {
// Consider this a non-fatal error since it will be common to be offline
// or otherwise unable to retrieve the metadata.
return didUpdate, errors.Wrap(err, "update metadata")
}
for target, channel := range r.opt.Targets {
meta, err := r.client.Lookup(target, channel)
if err != nil {
return didUpdate, errors.Wrapf(err, "lookup failed")
}
// Check whether the hash has changed
_, hash, err := selectHashFunction(meta)
if err != nil {
return didUpdate, errors.Wrap(err, "select hash for cache")
}
if !bytes.Equal(r.hashCache[target], hash) {
// Update detected
log.Info().Str("target", target).Str("channel", channel).Msg("update detected")
if err := r.updateTarget(target, channel); err != nil {
return didUpdate, errors.Wrapf(err, "update %s@%s", target, channel)
}
log.Info().Str("target", target).Str("channel", channel).Msg("update completed")
didUpdate = true
} else {
log.Debug().Str("target", target).Str("channel", channel).Msg("no update")
}
}
return didUpdate, nil
}
func (r *Runner) updateTarget(target, channel string) error {
path, err := r.client.Get(target, channel)
if err != nil {
return errors.Wrap(err, "get binary")
}
if target != "orbit" {
return nil
}
// Symlink Orbit binary
linkPath := filepath.Join(r.client.opt.RootDirectory, "bin", "orbit", filepath.Base(path))
// Rename the old file otherwise overwrite fails
if err := os.Rename(linkPath, linkPath+".old"); err != nil {
return errors.Wrap(err, "move old symlink current")
}
if err := os.Symlink(path, linkPath); err != nil {
return errors.Wrap(err, "symlink current")
}
return nil
}
func (r *Runner) Interrupt(err error) {
r.cancel <- struct{}{}
log.Debug().Msg("interrupt updater")
}

265
orbit/pkg/update/update.go Normal file
View file

@ -0,0 +1,265 @@
// package update contains the types and functions used by the update system.
package update
import (
"crypto/tls"
"encoding/json"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"github.com/fleetdm/orbit/pkg/constant"
"github.com/fleetdm/orbit/pkg/platform"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/theupdateframework/go-tuf/client"
"github.com/theupdateframework/go-tuf/data"
)
const (
binDir = "bin"
stagingDir = "staging"
defaultURL = "https://tuf.fleetctl.com"
defaultRootKeys = `[{"keytype":"ed25519","scheme":"ed25519","keyid_hash_algorithms":["sha256","sha512"],"keyval":{"public":"6d71d3beac3b830be929f2b10d513448d49ec6bb62a680176b89ffdfca180eb4"}}]`
)
// Updater is responsible for managing update state.
type Updater struct {
opt Options
client *client.Client
}
// Options are the options that can be provided when creating an Updater.
type Options struct {
// RootDirectory is the root directory from which other directories should be referenced.
RootDirectory string
// ServerURL is the URL of the update server.
ServerURL string
// InsecureTransport skips TLS certificate verification in the transport if
// set to true. Best to leave this on, but due to the file signing any
// tampering by a MitM should be detectable.
InsecureTransport bool
// RootKeys is the JSON encoded root keys to use to bootstrap trust.
RootKeys string
// LocalStore is the local metadata store.
LocalStore client.LocalStore
// Platform is the target of the platform to update for. In the default
// options this is the current platform.
Platform string
// OrbitChannel is the update channel to use for Orbit.
OrbitChannel string
// OsquerydChannel is the update channel to use for osquery (osqueryd).
OsquerydChannel string
}
// New creates a new updater given the provided options. All the necessary
// directories are initialized.
func New(opt Options) (*Updater, error) {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: opt.InsecureTransport,
}
httpClient := &http.Client{Transport: transport}
remoteStore, err := client.HTTPRemoteStore(opt.ServerURL, nil, httpClient)
if err != nil {
return nil, errors.Wrap(err, "init remote store")
}
tufClient := client.NewClient(opt.LocalStore, remoteStore)
var rootKeys []*data.Key
if err := json.Unmarshal([]byte(opt.RootKeys), &rootKeys); err != nil {
return nil, errors.Wrap(err, "unmarshal root keys")
}
meta, err := opt.LocalStore.GetMeta()
if err != nil || meta["root.json"] == nil {
var rootKeys []*data.Key
if err := json.Unmarshal([]byte(opt.RootKeys), &rootKeys); err != nil {
return nil, errors.Wrap(err, "unmarshal root keys")
}
if err := tufClient.Init(rootKeys, 1); err != nil {
return nil, errors.Wrap(err, "init tuf client")
}
}
updater := &Updater{
opt: opt,
client: tufClient,
}
if err := updater.initializeDirectories(); err != nil {
return nil, err
}
return updater, nil
}
func (u *Updater) UpdateMetadata() error {
if _, err := u.client.Update(); err != nil {
// An error is returned if we are already up-to-date. We can ignore that
// error.
if !client.IsLatestSnapshot(errors.Cause(err)) {
return errors.Wrap(err, "update metadata")
}
}
return nil
}
func (u *Updater) RepoPath(target, channel string) string {
return path.Join(target, u.opt.Platform, channel, target+constant.ExecutableExtension(u.opt.Platform))
}
func (u *Updater) LocalPath(target, channel string) string {
return u.pathFromRoot(filepath.Join(binDir, target, u.opt.Platform, channel, target+constant.ExecutableExtension(u.opt.Platform)))
}
// Lookup looks up the provided target in the local target metadata. This should
// be called after UpdateMetadata.
func (u *Updater) Lookup(target, channel string) (*data.TargetFileMeta, error) {
t, err := u.client.Target(u.RepoPath(target, channel))
if err != nil {
return nil, errors.Wrapf(err, "lookup %s@%s", target, channel)
}
return &t, nil
}
// Targets gets all of the known targets
func (u *Updater) Targets() (data.TargetFiles, error) {
targets, err := u.client.Targets()
if err != nil {
return nil, errors.Wrapf(err, "get targets")
}
return targets, nil
}
// Get returns the local path to the specified target. The target is downloaded
// if it does not yet exist locally or the hash does not match.
func (u *Updater) Get(target, channel string) (string, error) {
if target == "" {
return "", errors.New("target is required")
}
if channel == "" {
return "", errors.New("channel is required")
}
localPath := u.LocalPath(target, channel)
repoPath := u.RepoPath(target, channel)
stat, err := os.Stat(localPath)
if err != nil {
log.Debug().Err(err).Msg("stat file")
return localPath, u.Download(repoPath, localPath)
}
if !stat.Mode().IsRegular() {
return "", errors.Errorf("expected %s to be regular file", localPath)
}
meta, err := u.Lookup(target, channel)
if err != nil {
return "", err
}
if err := CheckFileHash(meta, localPath); err != nil {
log.Debug().Str("info", err.Error()).Msg("change detected")
return localPath, u.Download(repoPath, localPath)
}
log.Debug().Str("path", localPath).Str("target", target).Str("channel", channel).Msg("found expected target locally")
return localPath, nil
}
// Download downloads the target to the provided path. The file is deleted and
// an error is returned if the hash does not match.
func (u *Updater) Download(repoPath, localPath string) error {
staging := filepath.Join(u.opt.RootDirectory, stagingDir)
if err := os.MkdirAll(staging, constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "initialize download dir")
}
// Additional chmod only necessary on Windows, effectively a no-op on other
// platforms.
if err := platform.ChmodExecutableDirectory(staging); err != nil {
return err
}
tmp, err := os.OpenFile(
filepath.Join(staging, filepath.Base(localPath)),
os.O_CREATE|os.O_WRONLY,
constant.DefaultExecutableMode,
)
if err != nil {
return errors.Wrap(err, "open temp file for download")
}
defer func() {
tmp.Close()
os.Remove(tmp.Name())
}()
if err := platform.ChmodExecutable(tmp.Name()); err != nil {
return errors.Wrap(err, "chmod download")
}
if err := os.MkdirAll(filepath.Dir(localPath), constant.DefaultDirMode); err != nil {
return errors.Wrap(err, "initialize download dir")
}
// Additional chmod only necessary on Windows, effectively a no-op on other
// platforms.
if err := platform.ChmodExecutableDirectory(filepath.Dir(localPath)); err != nil {
return err
}
// The go-tuf client handles checking of max size and hash.
if err := u.client.Download(repoPath, &fileDestination{tmp}); err != nil {
return errors.Wrapf(err, "download target %s", repoPath)
}
if err := tmp.Close(); err != nil {
return errors.Wrap(err, "close tmp file")
}
// Attempt to exec the new binary only if the platform matches. This will
// always fail if the binary doesn't match the platform, so there's not
// really anything we can check.
if u.opt.Platform == constant.PlatformName {
out, err := exec.Command(tmp.Name(), "--version").CombinedOutput()
if err != nil {
return errors.Wrapf(err, "exec new version: %s", string(out))
}
}
if constant.PlatformName == "windows" {
// Remove old file first
if err := os.Rename(localPath, localPath+".old"); err != nil && !errors.Is(err, os.ErrNotExist) {
return errors.Wrap(err, "rename old")
}
}
if err := os.Rename(tmp.Name(), localPath); err != nil {
return errors.Wrap(err, "move download")
}
return nil
}
func (u *Updater) pathFromRoot(parts ...string) string {
return filepath.Join(append([]string{u.opt.RootDirectory}, parts...)...)
}
func (u *Updater) initializeDirectories() error {
for _, dir := range []string{
u.pathFromRoot(binDir),
} {
err := os.MkdirAll(dir, constant.DefaultDirMode)
if err != nil {
return errors.Wrap(err, "initialize directories")
}
}
return nil
}

View file

@ -0,0 +1,59 @@
package update
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInitializeDirectories(t *testing.T) {
t.Parallel()
tmpDir, err := ioutil.TempDir("", "orbit-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
opt := DefaultOptions
opt.RootDirectory = tmpDir
updater := Updater{opt: opt}
err = updater.initializeDirectories()
require.NoError(t, err)
assertDir(t, filepath.Join(tmpDir, binDir))
}
func assertDir(t *testing.T, path string) {
info, err := os.Stat(path)
assert.NoError(t, err, "stat should succeed")
assert.True(t, info.IsDir())
}
func TestMakeRepoPath(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
version string
platform string
expected string
}{
{platform: "linux", name: "osqueryd", version: "4.6.0", expected: "osqueryd/linux/4.6.0/osqueryd"},
{platform: "linux", name: "osqueryd", version: "3.3.2", expected: "osqueryd/linux/3.3.2/osqueryd"},
{platform: "macos", name: "osqueryd", version: "4.6.0", expected: "osqueryd/macos/4.6.0/osqueryd"},
{platform: "macos", name: "osqueryd", version: "3.3.2", expected: "osqueryd/macos/3.3.2/osqueryd"},
{platform: "windows", name: "osqueryd", version: "4.6.0", expected: "osqueryd/windows/4.6.0/osqueryd.exe"},
{platform: "windows", name: "osqueryd", version: "3.3.2", expected: "osqueryd/windows/3.3.2/osqueryd.exe"},
}
for _, tt := range testCases {
t.Run(tt.expected, func(t *testing.T) {
t.Parallel()
u := Updater{opt: Options{Platform: tt.platform}}
assert.Equal(t, tt.expected, u.RepoPath(tt.name, tt.version))
})
}
}

25
orbit/tools/build/sign-macos.sh Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -eo pipefail
if [ -z "$CODESIGN_IDENTITY" ]
then
echo 'Must set CODESIGN_IDENTITY in environment'
exit 1
fi
if [ ! -f "$1" ]
then
echo 'First argument must be path to binary'
exit 1
fi
# Skip if not a macOS Mach-O executable
if ! ( file "$1" | grep Mach-O )
then
echo 'Skip macOS signing'
exit 0
fi
codesign -s "$CODESIGN_IDENTITY" -i com.fleetdm.orbit -f -v --timestamp --options runtime "$1"
echo "Signed successfully"

View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
sudo launchctl stop com.fleetdm.orbit
sudo launchctl unload /Library/LaunchDaemons/com.fleetdm.orbit.plist
sudo rm -rf /Library/LaunchDaemons/com.fleetdm.orbit.plist /var/lib/orbit/ /usr/local/bin/orbit /var/log/orbit

View file

@ -0,0 +1,5 @@
FROM ubuntu
RUN apt-get update &&\
apt-get install -y ca-certificates &&\
rm -rf /var/lib/apt/lists/*