Improve osquery perf (#2503)

* Improve osquery perf

* Update dependencies

* Embed template so this can be run from the root of the repo

* Fix lint

* Address review comments
This commit is contained in:
Tomas Touceda 2021-10-14 10:09:58 -03:00 committed by GitHub
parent 4d6956b6cb
commit 825939e3dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 250 additions and 106 deletions

View file

@ -3,29 +3,131 @@ package main
import (
"bytes"
"crypto/tls"
"embed"
_ "embed"
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"strings"
"sync"
"text/template"
"time"
"github.com/google/uuid"
"github.com/valyala/fasthttp"
)
//go:embed *.tmpl
var templatesFS embed.FS
type Stats struct {
errors int
enrollments int
distributedwrites int
l sync.Mutex
}
func (s *Stats) RecordStats(errors int, enrollments int, distributedwrites int) {
s.l.Lock()
defer s.l.Unlock()
s.errors += errors
s.enrollments += enrollments
s.distributedwrites += distributedwrites
}
func (s *Stats) Log() {
s.l.Lock()
defer s.l.Unlock()
fmt.Printf(
"%s :: error rate: %.2f \t enrollments: %d \t writes: %d\n",
time.Now().String(),
float64(s.errors)/float64(s.enrollments),
s.enrollments,
s.distributedwrites,
)
}
func (s *Stats) runLoop() {
ticker := time.Tick(10 * time.Second)
for range ticker {
s.Log()
}
}
type NodeKeyManager struct {
filepath string
l sync.Mutex
nodekeys []string
}
func (n *NodeKeyManager) LoadKeys() {
if n.filepath == "" {
return
}
n.l.Lock()
defer n.l.Unlock()
data, err := os.ReadFile(n.filepath)
if err != nil {
fmt.Println("WARNING (ignore if creating a new node key file): error loading nodekey file:", err)
return
}
n.nodekeys = strings.Split(string(data), "\n")
fmt.Printf("loaded %d node keys\n", len(n.nodekeys))
}
func (n *NodeKeyManager) Get(i int) string {
n.l.Lock()
defer n.l.Unlock()
if len(n.nodekeys) > i {
return n.nodekeys[i]
}
return ""
}
func (n *NodeKeyManager) Add(nodekey string) {
if n.filepath == "" {
return
}
// we lock just to make sure we write one at a time
n.l.Lock()
defer n.l.Unlock()
f, err := os.OpenFile(n.filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
fmt.Println("error opening nodekey file:", err.Error())
return
}
defer f.Close()
if _, err := f.WriteString(nodekey + "\n"); err != nil {
fmt.Println("error writing nodekey file:", err)
}
}
type Agent struct {
ServerAddress string
EnrollSecret string
NodeKey string
UUID string
FastClient fasthttp.Client
Client http.Client
ConfigInterval time.Duration
QueryInterval time.Duration
Templates *template.Template
strings map[string]string
Stats *Stats
NodeKeyManager *NodeKeyManager
}
func NewAgent(serverAddress, enrollSecret string, templates *template.Template, configInterval, queryInterval time.Duration) *Agent {
@ -39,8 +141,11 @@ func NewAgent(serverAddress, enrollSecret string, templates *template.Template,
ConfigInterval: configInterval,
QueryInterval: queryInterval,
UUID: uuid.New().String(),
Client: http.Client{Transport: transport},
strings: make(map[string]string),
FastClient: fasthttp.Client{
TLSConfig: &tls.Config{InsecureSkipVerify: true},
},
Client: http.Client{Transport: transport},
strings: make(map[string]string),
}
}
@ -52,8 +157,10 @@ type distributedReadResponse struct {
Queries map[string]string `json:"queries"`
}
func (a *Agent) runLoop() {
a.Enroll()
func (a *Agent) runLoop(i int, onlyAlreadyEnrolled bool) {
if err := a.Enroll(i, onlyAlreadyEnrolled); err != nil {
return
}
a.Config()
resp, err := a.DistributedRead()
@ -84,6 +191,89 @@ func (a *Agent) runLoop() {
}
}
func (a *Agent) waitingDo(req *fasthttp.Request, res *fasthttp.Response) {
err := a.FastClient.Do(req, res)
for err != nil || res.StatusCode() != http.StatusOK {
fmt.Println(err, res.StatusCode())
a.Stats.RecordStats(1, 0, 0)
<-time.Tick(time.Duration(rand.Intn(120)+1) * time.Second)
err = fasthttp.Do(req, res)
}
}
func (a *Agent) Enroll(i int, onlyAlreadyEnrolled bool) error {
a.NodeKey = a.NodeKeyManager.Get(i)
if a.NodeKey != "" {
a.Stats.RecordStats(0, 1, 0)
return nil
}
if onlyAlreadyEnrolled {
return fmt.Errorf("not enrolled")
}
var body bytes.Buffer
if err := a.Templates.ExecuteTemplate(&body, "enroll", a); err != nil {
log.Println("execute template:", err)
return err
}
req := fasthttp.AcquireRequest()
req.SetBody(body.Bytes())
req.Header.SetMethod("POST")
req.Header.SetContentType("application/json")
req.Header.Add("User-Agent", "osquery/4.6.0")
req.SetRequestURI(a.ServerAddress + "/api/v1/osquery/enroll")
res := fasthttp.AcquireResponse()
a.waitingDo(req, res)
fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(res)
if res.StatusCode() != http.StatusOK {
log.Println("enroll status:", res.StatusCode())
return fmt.Errorf("status code: %d", res.StatusCode())
}
var parsedResp enrollResponse
if err := json.Unmarshal(res.Body(), &parsedResp); err != nil {
log.Println("json parse:", err)
return err
}
a.NodeKey = parsedResp.NodeKey
a.Stats.RecordStats(0, 1, 0)
a.NodeKeyManager.Add(a.NodeKey)
return nil
}
func (a *Agent) Config() {
body := bytes.NewBufferString(`{"node_key": "` + a.NodeKey + `"}`)
req := fasthttp.AcquireRequest()
req.SetBody(body.Bytes())
req.Header.SetMethod("POST")
req.Header.SetContentType("application/json")
req.Header.Add("User-Agent", "osquery/4.6.0")
req.SetRequestURI(a.ServerAddress + "/api/v1/osquery/config")
res := fasthttp.AcquireResponse()
a.waitingDo(req, res)
fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(res)
if res.StatusCode() != http.StatusOK {
log.Println("config status:", res.StatusCode())
return
}
// No need to read the config body
}
const stringVals = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
func (a *Agent) randomString(n int) string {
@ -104,94 +294,24 @@ func (a *Agent) CachedString(key string) string {
return val
}
func (a *Agent) Enroll() {
var body bytes.Buffer
if err := a.Templates.ExecuteTemplate(&body, "enroll", a); err != nil {
log.Println("execute template:", err)
return
}
req, err := http.NewRequest("POST", a.ServerAddress+"/api/v1/osquery/enroll", &body)
if err != nil {
log.Println("create request:", err)
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", "osquery/4.6.0")
resp, err := a.Client.Do(req)
if err != nil {
log.Println("do request:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Println("status:", resp.Status)
return
}
var parsedResp enrollResponse
if err := json.NewDecoder(resp.Body).Decode(&parsedResp); err != nil {
log.Println("json parse:", err)
return
}
a.NodeKey = parsedResp.NodeKey
}
func (a *Agent) Config() {
body := bytes.NewBufferString(`{"node_key": "` + a.NodeKey + `"}`)
req, err := http.NewRequest("POST", a.ServerAddress+"/api/v1/osquery/config", body)
if err != nil {
log.Println("create config request:", err)
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", "osquery/4.6.0")
resp, err := a.Client.Do(req)
if err != nil {
log.Println("do config request:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Println("config status:", resp.Status)
return
}
// No need to read the config body
}
func (a *Agent) DistributedRead() (*distributedReadResponse, error) {
body := bytes.NewBufferString(`{"node_key": "` + a.NodeKey + `"}`)
req, err := http.NewRequest("POST", a.ServerAddress+"/api/v1/osquery/distributed/read", body)
if err != nil {
return nil, fmt.Errorf("create distributed read request: %s", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req := fasthttp.AcquireRequest()
req.SetBody([]byte(`{"node_key": "` + a.NodeKey + `"}`))
req.Header.SetMethod("POST")
req.Header.SetContentType("application/json")
req.Header.Add("User-Agent", "osquery/4.6.0")
req.SetRequestURI(a.ServerAddress + "/api/v1/osquery/distributed/read")
res := fasthttp.AcquireResponse()
resp, err := a.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("do distributed read request: %s", err)
}
defer resp.Body.Close()
a.waitingDo(req, res)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("distributed read status: %s", resp.Status)
}
fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(res)
var parsedResp distributedReadResponse
if err := json.NewDecoder(resp.Body).Decode(&parsedResp); err != nil {
return nil, fmt.Errorf("json parse distributed read response: %s", err)
if err := json.Unmarshal(res.Body(), &parsedResp); err != nil {
log.Println("json parse:", err)
return nil, err
}
return &parsedResp, nil
@ -228,26 +348,20 @@ func (a *Agent) DistributedWrite(queries map[string]string) {
json.NewEncoder(&body).Encode(req)
}
req, err := http.NewRequest("POST", a.ServerAddress+"/api/v1/osquery/distributed/write", &body)
if err != nil {
log.Println("create distributed write request:", err)
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req := fasthttp.AcquireRequest()
req.SetBody(body.Bytes())
req.Header.SetMethod("POST")
req.Header.SetContentType("application/json")
req.Header.Add("User-Agent", "osquery/4.6.0")
req.SetRequestURI(a.ServerAddress + "/api/v1/osquery/distributed/write")
res := fasthttp.AcquireResponse()
resp, err := a.Client.Do(req)
if err != nil {
log.Println("do distributed write request:", err)
return
}
defer resp.Body.Close()
a.waitingDo(req, res)
if resp.StatusCode != http.StatusOK {
log.Println("distributed write status:", resp.Status)
return
}
fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(res)
a.Stats.RecordStats(0, 0, 1)
// No need to read the distributed write body
}
@ -260,23 +374,37 @@ func main() {
startPeriod := flag.Duration("start_period", 10*time.Second, "Duration to spread start of hosts over")
configInterval := flag.Duration("config_interval", 1*time.Minute, "Interval for config requests")
queryInterval := flag.Duration("query_interval", 10*time.Second, "Interval for live query requests")
onlyAlreadyEnrolled := flag.Bool("only_already_enrolled", false, "Only start agents that are already enrolled")
nodeKeyFile := flag.String("node_key_file", "", "File with node keys to use")
flag.Parse()
rand.Seed(*randSeed)
tmpl, err := template.ParseGlob("*.tmpl")
tmpl, err := template.ParseFS(templatesFS, "*.tmpl")
if err != nil {
log.Fatal("parse templates: ", err)
}
// Spread starts over the interval to prevent thunering herd
sleepTime := *startPeriod / time.Duration(*hostCount)
stats := &Stats{}
go stats.runLoop()
nodeKeyManager := &NodeKeyManager{}
if nodeKeyFile != nil {
nodeKeyManager.filepath = *nodeKeyFile
nodeKeyManager.LoadKeys()
}
var agents []*Agent
for i := 0; i < *hostCount; i++ {
a := NewAgent(*serverURL, *enrollSecret, tmpl, *configInterval, *queryInterval)
a.Stats = stats
a.NodeKeyManager = nodeKeyManager
agents = append(agents, a)
go a.runLoop()
go a.runLoop(i, onlyAlreadyEnrolled != nil && *onlyAlreadyEnrolled)
time.Sleep(sleepTime)
}

3
go.mod
View file

@ -69,7 +69,8 @@ require (
github.com/theupdateframework/go-tuf v0.0.0-20201230183259-aee6270feb55
github.com/throttled/throttled/v2 v2.8.0
github.com/urfave/cli/v2 v2.3.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
github.com/valyala/fasthttp v1.31.0 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/tools v0.1.5 // indirect
google.golang.org/grpc v1.38.0

15
go.sum
View file

@ -93,6 +93,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
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/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
github.com/andybalholm/brotli v1.0.2/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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@ -319,6 +321,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
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/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/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=
@ -515,6 +519,8 @@ 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/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/kolide/launcher v0.0.0-20180427153757-cb412b945cf7 h1:6E3WfyhTsIOTL7aP+TnLnh3cH2y6bd5mL0ssuYuJ8xo=
github.com/kolide/launcher v0.0.0-20180427153757-cb412b945cf7/go.mod h1:EGgVr4L+g8f041t5OrmY/KV431GlIWhBxsHsT+qc2Mg=
github.com/kolide/osquery-go v0.0.0-20190904034940-a74aa860032d h1:alVW+rIOMejarlhjLl4AYaQaGAyfvIUDg9NyyKRhSek=
@ -836,10 +842,14 @@ 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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE=
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
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/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
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=
@ -899,6 +909,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
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=
@ -984,6 +996,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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=
@ -1077,6 +1091,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=