Add Kolide osquery tables

This commit is contained in:
Sharon Katz 2023-11-01 22:11:35 -04:00 committed by GitHub
parent f8652a1b68
commit ab7717009e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
127 changed files with 14429 additions and 213 deletions

View File

@ -14,6 +14,7 @@ on:
env:
ORBIT_VERSION: 1.17.0
CGO_ENABLED: 1
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:

3
.gitignore vendored
View File

@ -100,3 +100,6 @@ fleetd_tables_*
tools/test_extensions/hello_world/macos
tools/test_extensions/hello_world/windows
tools/test_extensions/hello_world/linux
# Residual files when building fleet_tables extension.
fleet_tables_*.ext

View File

@ -21,7 +21,7 @@ linters-settings:
main:
deny:
- pkg: github.com/pkg/errors
msg: "use ctxerr if a context.Context is available or stdlib errors.New / fmt.Errorf with the %w verb"
desc: "use ctxerr if a context.Context is available or stdlib errors.New / fmt.Errorf with the %w verb"
errcheck:
check-type-assertions: false

View File

@ -244,11 +244,11 @@ fleetd-tables-darwin:
GOOS=darwin GOARCH=amd64 go build -o fleetd_tables_darwin.ext ./orbit/cmd/fleetd_tables
fleetd-tables-darwin_arm:
GOOS=darwin GOARCH=arm64 go build -o fleetd_tables_darwin_arm.ext ./orbit/cmd/fleetd_tables
fleetd-tables-darwin-universal:
$(MAKE) fleetd-tables-darwin fleetd-tables-darwin_arm
fleetd-tables-darwin-universal: fleetd-tables-darwin fleetd-tables-darwin_arm
lipo -create fleetd_tables_darwin.ext fleetd_tables_darwin_arm.ext -output fleetd_tables_darwin_universal.ext
fleetd-tables-all:
$(MAKE) fleetd-tables-windows fleetd-tables-linux fleetd-tables-darwin-universal
fleetd-tables-all: fleetd-tables-windows fleetd-tables-linux fleetd-tables-darwin-universal
fleetd-tables-clean:
rm -f fleetd_tables_windows.exe fleetd_tables_linux.ext fleetd_tables_darwin.ext fleetd_tables_darwin_arm.ext fleetd_tables_darwin_universal.ext
.pre-binary-arch:
ifndef GOOS

View File

@ -0,0 +1,11 @@
- added tables to the fleetd extension:
- app_icons
- falconctl_options
- falcon_kernel_check
- cryptoinfo
- cryptsetup_status
- filevault_status
- firefox_preferences
- firmwarepasswd
- ioreg
- windows_updates

90
go.mod
View File

@ -3,7 +3,7 @@ module github.com/fleetdm/fleet/v4
go 1.21
require (
cloud.google.com/go/pubsub v1.30.0
cloud.google.com/go/pubsub v1.33.0
github.com/AbGuthrie/goquery/v2 v2.0.1
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver v1.5.0
@ -17,7 +17,8 @@ require (
github.com/beevik/ntp v0.3.0
github.com/briandowns/spinner v1.13.0
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cenkalti/backoff/v4 v4.2.0
github.com/cenkalti/backoff/v4 v4.2.1
github.com/clbanning/mxj v1.8.4
github.com/davecgh/go-spew v1.1.1
github.com/dgraph-io/badger/v2 v2.2007.2
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e
@ -32,6 +33,7 @@ require (
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8
github.com/getsentry/sentry-go v0.18.0
github.com/ghodss/yaml v1.0.0
github.com/go-ini/ini v1.67.0
github.com/go-kit/kit v0.12.0
github.com/go-kit/log v0.2.1
github.com/go-ole/go-ole v1.2.6
@ -56,8 +58,8 @@ require (
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5
github.com/josephspurrier/goversioninfo v1.4.0
github.com/kevinburke/go-bindata v3.24.0+incompatible
github.com/kolide/kit v0.0.0-20191023141830-6312ecc11c23
github.com/kolide/launcher v0.11.25-0.20220321235155-c3e9480037d2
github.com/kolide/kit v0.0.0-20221107170827-fb85e3d59eab
github.com/kolide/launcher v1.1.2
github.com/macadmins/osquery-extension v0.0.15
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e
github.com/mattn/go-sqlite3 v1.14.13
@ -68,13 +70,14 @@ require (
github.com/mitchellh/go-ps v1.0.0
github.com/mitchellh/gon v0.2.3
github.com/mna/redisc v1.3.2
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/ngrok/sqlmw v0.0.0-20211220175533-9d16fdc47b31
github.com/nukosuke/go-zendesk v0.13.1
github.com/oklog/run v1.1.0
github.com/olekukonko/tablewriter v0.0.5
github.com/open-policy-agent/opa v0.44.0
github.com/oschwald/geoip2-golang v1.8.0
github.com/osquery/osquery-go v0.0.0-20230603132358-d2e851b3991b
github.com/osquery/osquery-go v0.0.0-20231006172600-d6f325f636a9
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
@ -83,13 +86,13 @@ require (
github.com/russellhaering/goxmldsig v1.2.0
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9
github.com/sethvargo/go-password v0.2.0
github.com/shirou/gopsutil/v3 v3.22.8
github.com/shirou/gopsutil/v3 v3.23.3
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cast v1.4.1
github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.10.0
github.com/stretchr/testify v1.8.4
github.com/theupdateframework/go-tuf v0.5.0
github.com/theupdateframework/go-tuf v0.5.2
github.com/throttled/throttled/v2 v2.8.0
github.com/tj/assert v0.0.3
github.com/ulikunitz/xz v0.5.10
@ -100,32 +103,34 @@ require (
go.elastic.co/apm/v2 v2.4.3
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.40.0
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
go.opentelemetry.io/otel/sdk v1.14.0
golang.org/x/crypto v0.11.0
go.opentelemetry.io/otel v1.19.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0
go.opentelemetry.io/otel/sdk v1.19.0
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3
golang.org/x/net v0.12.0
golang.org/x/oauth2 v0.6.0
golang.org/x/image v0.10.0
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.12.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.10.0
golang.org/x/text v0.11.0
google.golang.org/grpc v1.54.0
golang.org/x/sys v0.13.0
golang.org/x/text v0.13.0
google.golang.org/grpc v1.58.3
gopkg.in/guregu/null.v3 v3.5.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.4.0
howett.net/plist v1.0.0
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
)
require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.0 // indirect
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/kms v1.10.0 // indirect
cloud.google.com/go/storage v1.28.1 // indirect
cloud.google.com/go/iam v1.1.2 // indirect
cloud.google.com/go/kms v1.15.2 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
code.gitea.io/sdk/gitea v0.15.0 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
@ -188,16 +193,16 @@ require (
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elastic/go-sysinfo v1.7.1 // indirect
github.com/elastic/go-windows v1.0.1 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
github.com/getlantern/errors v1.0.1 // indirect
github.com/getlantern/golog v0.0.0-20211223150227-d4d95a44d873 // indirect
@ -214,7 +219,7 @@ require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
@ -222,13 +227,14 @@ require (
github.com/google/go-github/v39 v39.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/rpmpack v0.0.0-20210518075352-dc539ef4f2ea // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/goreleaser/chglog v0.1.2 // indirect
github.com/goreleaser/fileglob v1.2.0 // indirect
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.0.0 // indirect
@ -270,10 +276,10 @@ require (
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.5.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/slack-go/slack v0.9.4 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@ -282,8 +288,8 @@ require (
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/trivago/tgo v1.0.7 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vartanbeno/go-reddit/v2 v2.0.0 // indirect
@ -297,22 +303,24 @@ require (
go.elastic.co/apm/module/apmhttp/v2 v2.3.0 // indirect
go.elastic.co/fastjson v1.1.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
gocloud.dev v0.24.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.114.0 // indirect
google.golang.org/api v0.128.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
@ -320,7 +328,7 @@ require (
gotest.tools/v3 v3.0.3 // indirect
)
replace github.com/kolide/kit => github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c
replace github.com/kolide/kit => github.com/fleetdm/kolide-kit v0.0.0-20230519160117-86cc9441f9c1
replace github.com/micromdm/nanomdm => github.com/fleetdm/nanomdm v0.3.1-0.20230710170238-fd0813187f24

275
go.sum
View File

@ -35,16 +35,16 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@ -52,21 +52,19 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4=
cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
cloud.google.com/go/kms v0.1.0/go.mod h1:8Qp8PCAypHg4FdmlyW1QRAv09BGQ9Uzh7JnmIZxPk+c=
cloud.google.com/go/kms v1.10.0 h1:Imrtp8792uqNP9bdfPrjtUkjjqOMBcAJ2bdFaAnLhnk=
cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
cloud.google.com/go/kms v1.15.2 h1:lh6qra6oC4AyWe5fUUUBe/S27k12OHAleOOOw6KakdE=
cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w=
cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/pubsub v1.16.0/go.mod h1:6A8EfoWZ/lUvCWStKGwAWauJZSiuV0Mkmu6WilK/TxQ=
cloud.google.com/go/pubsub v1.30.0 h1:vCge8m7aUKBJYOgrZp7EsNDf6QMd2CAlXZqWTn3yq6s=
cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=
cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g=
cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc=
cloud.google.com/go/secretmanager v0.1.0/go.mod h1:3nGKHvnzDUVit7U0S9KAKJ4aOsO1xtwRG+7ey5LK1bM=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
@ -74,8 +72,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4=
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.15.0 h1:tsNhxDM/2N1Ohv1Xq5UWrht/esg0WmtRj4wsHVHriTg=
@ -159,7 +157,6 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1K
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
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=
@ -167,8 +164,6 @@ github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030I
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.9/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
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/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
@ -188,12 +183,10 @@ github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6
github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
github.com/RobotsAndPencils/buford v0.14.0 h1:+d18IMEisYlRZZYfe6uFlmQGbT07kWro25V35fGptZM=
github.com/RobotsAndPencils/buford v0.14.0/go.mod h1:F5FvdB/nkMby8Pge6HFpPHgLOeUZne/iE5wKzvx64Y0=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0=
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0=
github.com/WatchBeam/clock v0.0.0-20161028195133-dc1b57477882/go.mod h1:N5eJIl14rhNCrE5I3O10HIyhZ1HpjaRHT9WDg1eXxtI=
github.com/WatchBeam/clock v0.0.0-20170901150240-b08e6b4da7ea h1:C9Xwp9fZf9BFJMsTqs8P+4PETXwJPUOuJZwBfVci+4A=
github.com/WatchBeam/clock v0.0.0-20170901150240-b08e6b4da7ea/go.mod h1:N5eJIl14rhNCrE5I3O10HIyhZ1HpjaRHT9WDg1eXxtI=
github.com/XSAM/otelsql v0.10.0 h1:y8o7q4NaZEV0dBiUC7TuNTHNKyDaX3Z4anntNu7dfYw=
@ -202,7 +195,6 @@ github.com/aai/gocrypto v0.0.0-20160205191751-93df0c47f8b8/go.mod h1:nE/FnVUmtbP
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
@ -229,8 +221,6 @@ github.com/antchfx/xmlquery v1.3.14/go.mod h1:yPRBXRdd2Xqz9c2Z61qvMKbK+u3NXXydp6
github.com/antchfx/xpath v1.2.2 h1:fsKX4sHfxhsGpDMYjsvCmGC0EGdiT7XA0af/6PP6Oa0=
github.com/antchfx/xpath v1.2.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.13.1-0.20200603211036-eac4d0c79a5f/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/apache/thrift v0.18.1 h1:lNhK/1nqjbwbiOPDBPFJVKxgDEGSepKuTh6OLiXW8kg=
github.com/apache/thrift v0.18.1/go.mod h1:rdQn/dCcDKEWjjylUeueum4vQEjG2v8v2PqriUnbr+I=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
@ -301,8 +291,6 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/briandowns/spinner v1.13.0 h1:q/Y9LtpwtvL0CRzXrAMj0keVXqNhBYUFg6tBOUiY8ek=
github.com/briandowns/spinner v1.13.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/bugsnag/bugsnag-go v1.3.2/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/bytecodealliance/wasmtime-go v0.36.0 h1:B6thr7RMM9xQmouBtUqm1RpkJjuLS37m6nxX+iwsQSc=
github.com/bytecodealliance/wasmtime-go v0.36.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI=
github.com/c-bata/go-prompt v0.2.3 h1:jjCS+QhG/sULBhAaBdjb2PlMRVaKXQgn+4yzaauvs2s=
@ -319,12 +307,11 @@ github.com/caarlos0/testfs v0.4.3 h1:q1zEM5hgsssqWanAfevJYYa0So60DdK6wlJeTc/yfUE
github.com/caarlos0/testfs v0.4.3/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
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/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@ -338,9 +325,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20181102015659-ea4033a214e7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@ -399,21 +386,19 @@ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:Y
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY=
github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY=
github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ=
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/e-dard/netbug v0.0.0-20151029172837-e64d308a0b20 h1:eDPsdileewX4H5a2Jph4gS8mFf749gzIrzpbnPy1oRs=
github.com/e-dard/netbug v0.0.0-20151029172837-e64d308a0b20/go.mod h1:WXFUXJ0Y/SzNqXmhUU7VkE7a2Pag0zZnE2b6I87YWIs=
github.com/elastic/go-licenser v0.4.0/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
@ -451,6 +436,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fleetdm/goose v0.0.0-20221011170007-06aacf8ac547 h1:3Vlgx6mJYFlj3GPB3CgoQrR7URgE0GQGnKYNfoXxuUo=
github.com/fleetdm/goose v0.0.0-20221011170007-06aacf8ac547/go.mod h1:d7Q+0eCENnKQUhkfAUVLfGnD4QcgJMF/uB9WRTN9TDI=
github.com/fleetdm/kolide-kit v0.0.0-20230519160117-86cc9441f9c1 h1:9JGbRO6QKpHr5HO5t6g3/5EspV5eDWaLKbRH7xKqL/c=
github.com/fleetdm/kolide-kit v0.0.0-20230519160117-86cc9441f9c1/go.mod h1:HHtqF91JHl66L+Ms8aswzqVb2eEU5O3DRNiFmUzOf60=
github.com/fleetdm/nanodep v0.1.1-0.20221221202251-71b67ab1da24 h1:XhczaxKV3J4NjztroidSnYKyq5xtxF+amBYdBWeik58=
github.com/fleetdm/nanodep v0.1.1-0.20221221202251-71b67ab1da24/go.mod h1:QzQrCUTmSr9HotzKZAcfmy+czbEGK8Mq26hA+0DN4ag=
github.com/fleetdm/nanomdm v0.3.1-0.20230710170238-fd0813187f24 h1:oP0kOAFDzu646/TXDCcvOGdDgSHJgk+X0Pfr60kp7Jg=
@ -466,8 +453,8 @@ github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
@ -495,7 +482,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
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-bindata/go-bindata v1.0.0/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
@ -514,9 +500,9 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ini/ini v1.61.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.7.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
@ -546,13 +532,11 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.7.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
@ -570,7 +554,6 @@ github.com/gocarina/gocsv v0.0.0-20220310154401-d4df709ca055 h1:UfcDMw41lSx3XM7U
github.com/gocarina/gocsv v0.0.0-20220310154401-d4df709ca055/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@ -580,8 +563,8 @@ github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQA
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -629,10 +612,8 @@ github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
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/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/fscrypt v0.3.0/go.mod h1:1mPhM/LudtWD88Lzv4Fwb6aAzih0J3f/B/zmFa4sTiA=
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=
@ -690,9 +671,9 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.0.0-20210518075352-dc539ef4f2ea h1:Fv9Ni1vIq9+Gv4Sm0Xq+NnPYcnsMbdNhJ4Cu4rkbPBM=
github.com/google/rpmpack v0.0.0-20210518075352-dc539ef4f2ea/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -700,14 +681,14 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4=
github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
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/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
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=
@ -731,17 +712,14 @@ github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda h1:5ikpG9mYCMFiZX0nkxoV6aU2IpCHPdws3gCNgdZeEV0=
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c=
github.com/groob/plist v0.0.0-20190114192801-a99fbe489d03/go.mod h1:qg2Nek0ND/hIr+nY8H1oVqEW2cLzVVNaAQ0QexOyjyc=
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5 h1:saaSiB25B1wgaxrshQhurfPKUGJ4It3OxNJUy0rdOjU=
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw=
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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@ -825,15 +803,12 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v0.0.0-20180406164412-2aeb6a910c2b/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 h1:lrdPtrORjGv1HbbEvKWDUAy97mPpFm4B8hp77tcCUJY=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
@ -856,7 +831,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kevinburke/go-bindata v3.24.0+incompatible h1:qajFA3D0pH94OTLU4zcCCKCDgR+Zr2cZK/RPJHDdFoY=
github.com/kevinburke/go-bindata v3.24.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
@ -871,12 +845,9 @@ github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/knightsc/system_policy v1.1.1-0.20211029142728-5f4c0d5419cc/go.mod h1:5e34JEkxWsOeAd9jvcxkz01tAY/JAGFuabGnNBJ6TT4=
github.com/kolide/launcher v0.11.25-0.20220321235155-c3e9480037d2 h1:PozvR7w1/Gd5X5xVn71il8vU68Pqwl3beQVF6k9fCm4=
github.com/kolide/launcher v0.11.25-0.20220321235155-c3e9480037d2/go.mod h1:4dUso03feHUqlHfUJ+6dHXRuyPFFTB7+dcxgTNr1qUY=
github.com/kolide/updater v0.0.0-20190315001611-15bbc19b5b80/go.mod h1:x3dEGYbZovhD1t8OwEgdyu/4ZCvrn9QvkbPtOZnul8k=
github.com/kolide/launcher v1.1.2 h1:fJnGrofLMM+PaKKFrskCW0UeaIwYkMIo6gRSyi8Y+ls=
github.com/kolide/launcher v1.1.2/go.mod h1:4h1FwciY5mWaNlG8ckHl3TU5yVN4+NkZtnrAvP65x1A=
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.2/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=
@ -887,7 +858,6 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.2/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=
@ -909,7 +879,6 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mat/besticon v3.9.0+incompatible/go.mod h1:mA1auQYHt6CW5e7L9HJLmqVQC8SzNk2gVwouO0AbiEU=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
@ -939,7 +908,6 @@ github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
@ -957,7 +925,6 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/pkcs11 v0.0.0-20180208123018-5f6e0d0dad6f/go.mod h1:WCBAbTOdfhHhz7YXujeZMF7owC4tPb1naKFsgfUISjo=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@ -966,7 +933,6 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
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 v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
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=
@ -976,7 +942,6 @@ github.com/mitchellh/gon v0.2.3/go.mod h1:Ua18ZhqjZHg8VyqZo8kNHAY331ntV6nNJ9mT3s
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.0.0/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.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -986,7 +951,6 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mixer/clock v0.0.0-20170901150240-b08e6b4da7ea/go.mod h1:U8TDygO2XZh1RtBCgX7oRbJ7gmSH4C6FROsBdQ6QyCc=
github.com/mna/redisc v1.3.2 h1:sc9C+nj6qmrTFnsXb70xkjAHpXKtjjBuE6v2UcQV0ZE=
github.com/mna/redisc v1.3.2/go.mod h1:CplIoaSTDi5h9icnj4FLbRgHoNKCHDNJDVRztWDGeSQ=
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0=
@ -1000,42 +964,34 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/ngrok/sqlmw v0.0.0-20211220175533-9d16fdc47b31 h1:FFHgfAIoAXCCL4xBoAugZVpekfGmZ/fBBueneUKBv7I=
github.com/ngrok/sqlmw v0.0.0-20211220175533-9d16fdc47b31/go.mod h1:E26fwEtRNigBfFfHDWsklmo0T7Ixbg0XXgck+Hq4O9k=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nukosuke/go-zendesk v0.13.1 h1:EdYpn+FxROLguADEJK5reOHcpysM8wyWPOWO96SIc0A=
github.com/nukosuke/go-zendesk v0.13.1/go.mod h1:86Cg7RhSvPfOqZOtQXteJEV9yIQVQsy2HVDk++Yf3jA=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
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 v0.3.0/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
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.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/open-policy-agent/opa v0.44.0 h1:sEZthsrWBqIN+ShTMJ0Hcz6a3GkYsY4FaB2S/ou2hZk=
github.com/open-policy-agent/opa v0.44.0/go.mod h1:YpJaFIk5pq89n/k72c1lVvfvR5uopdJft2tMg1CW/yU=
github.com/opencensus-integrations/ocsql v0.1.1/go.mod h1:ozPYpNVBHZsX33jfoQPO5TlI5lqh0/3R36kirEqJKAM=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/osquery/osquery-go v0.0.0-20210622151333-99b4efa62ec5/go.mod h1:JKR5QhjsYdnIPY7hakgas5sxf8qlA/9wQnLqaMfWdcg=
github.com/osquery/osquery-go v0.0.0-20220317165851-954ac78f381f/go.mod h1:0KzmMhe0PL19cdYq6nd1cT9/5bMMJBTssAfuEgM2i34=
github.com/osquery/osquery-go v0.0.0-20230603132358-d2e851b3991b h1:kPna3NDVHKquM7hGLWcztO6eH+NTbTprHfGKrClGJqk=
github.com/osquery/osquery-go v0.0.0-20230603132358-d2e851b3991b/go.mod h1:OSR0OKXZZ+mnt08q14OndgHjJJ9/1koA2dDO3jzYr/I=
github.com/osquery/osquery-go v0.0.0-20231006172600-d6f325f636a9 h1:+7IDjPDpcEwVqphCBCi/VWMF6sSSrqzJ3lq09K9cnAU=
github.com/osquery/osquery-go v0.0.0-20231006172600-d6f325f636a9/go.mod h1:mLJRc1Go8uP32LRALGvWj2lVJ+hDYyIfxDzVa+C5Yo8=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -1043,11 +999,9 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/ff/v3 v3.0.0/go.mod h1:UILIFjRH5a/ar8TjXYLTkIvSvekZqPm5Eb/qbGk6CT0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -1065,7 +1019,6 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
@ -1080,7 +1033,6 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
@ -1090,7 +1042,6 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -1111,8 +1062,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
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=
@ -1125,26 +1076,29 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e/go.mod h1:9Tc1SKnfACJb9N7cw2eyuI6xzy845G7uZONBsi5uPEA=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
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/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/secure-systems-lab/go-securesystemslib v0.5.0 h1:oTiNu0QnulMQgN/hLK124wJD/r2f9ZhIUuKIeBsCBT8=
github.com/secure-systems-lab/go-securesystemslib v0.5.0/go.mod h1:uoCqUC0Ap7jrBSEanxT+SdACYJTVplRXWLkGMuDjXqk=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
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.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@ -1169,7 +1123,6 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
@ -1186,7 +1139,6 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
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.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
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/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
@ -1198,8 +1150,6 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -1210,6 +1160,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
@ -1218,9 +1169,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/theupdateframework/go-tuf v0.5.0 h1:aQ7i9CBw4q9QEZifCaW6G8qGQwoN23XGaZkOA+F50z4=
github.com/theupdateframework/go-tuf v0.5.0/go.mod h1:vAqWV3zEs89byeFsAYoh/Q14vJTgJkHwnnRCWBBBINY=
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
github.com/theupdateframework/go-tuf v0.5.2 h1:habfDzTmpbzBLIFGWa2ZpVhYvFBoK0C1onC3a4zuPRA=
github.com/theupdateframework/go-tuf v0.5.2/go.mod h1:SyMV5kg5n4uEclsyxXJZI2UxPFJNDc4Y+r7wv+MlvTA=
github.com/throttled/throttled/v2 v2.8.0 h1:B5VfdM8BE+ClI2Ji238SbNOTWfYcocvuAhgT27lvwrE=
github.com/throttled/throttled/v2 v2.8.0/go.mod h1:q1QyZVQXxb2NUfJ+Hjucmlrsrz9s/jt2ilMwSMo7a2I=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
@ -1230,10 +1180,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
@ -1244,7 +1194,6 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw=
github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -1255,7 +1204,6 @@ github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7Fw
github.com/vartanbeno/go-reddit/v2 v2.0.0 h1:fxYMqx5lhbmJ3yYRN1nnQC/gecRB3xpUS2BbG7GLpsk=
github.com/vartanbeno/go-reddit/v2 v2.0.0/go.mod h1:758/S10hwZSLm43NPtwoNQdZFSg3sjB5745Mwjb0ANI=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM=
github.com/xanzy/go-gitlab v0.50.3 h1:M7ncgNhCN4jaFNyXxarJhCLa9Qi6fdmCxFFhMTQPZiY=
github.com/xanzy/go-gitlab v0.50.3/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
@ -1287,8 +1235,6 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c h1:TWQ2UvXPkhPxI2KmApKBOCaV6yD2N4mlvqFQ/DlPtpQ=
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c/go.mod h1:OYYulo9tUqRadRLwB0+LE914sa1ui2yL7OrcU3Q/1XY=
go.elastic.co/apm/module/apmgorilla/v2 v2.3.0 h1:jHw8N252UTwKTk945+Am8AaawhHC6DWpFVeTXQO8Gko=
go.elastic.co/apm/module/apmgorilla/v2 v2.3.0/go.mod h1:2LXDBbVhFf9rF65jZecvl78IZMuvSRldQ+9A/fjfIo0=
go.elastic.co/apm/module/apmhttp/v2 v2.3.0 h1:yGZyp26uJXUCfRTwvMmDt1d1jJrHgTBBncZfpYAxR8s=
@ -1301,7 +1247,6 @@ go.elastic.co/apm/v2 v2.4.3/go.mod h1:+CiBUdrrAGnGCL9TNx7tQz3BrfYV23L8Ljvotoc87s
go.elastic.co/fastjson v1.1.0 h1:3MrGBWWVIxe/xvsbpghtkFoPciPhOCmjsR/HfwEeQR4=
go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
@ -1310,7 +1255,6 @@ go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvS
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -1322,23 +1266,23 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.40.0 h1:KToMJH0+5VxWBGtfeluRmWR3wLtE7nP+80YrxNI5FGs=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.40.0/go.mod h1:RK3vgddjxVcF1q7IBVppzG6k2cW/NBnZHQ3X4g+EYBQ=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
@ -1389,8 +1333,9 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
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=
@ -1405,6 +1350,8 @@ golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3 h1:fJwx88sMf5RXwDwziL0/Mn9Wq
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
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/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
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=
@ -1431,6 +1378,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1454,7 +1402,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -1497,8 +1444,9 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1520,8 +1468,8 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
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=
@ -1534,13 +1482,13 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20180906133057-8cf3aee42992/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=
@ -1561,7 +1509,6 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1573,7 +1520,6 @@ golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1596,7 +1542,6 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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=
@ -1634,32 +1579,34 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
@ -1669,10 +1616,13 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1700,7 +1650,6 @@ golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191025023517-2077df36852e/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-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -1747,6 +1696,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1790,14 +1740,13 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg=
google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@ -1872,8 +1821,12 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0=
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI=
google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a h1:a2MQQVoTo96JC9PMGtGBymLp7+/RzpFc2yX/9WfFg1c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
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=
@ -1901,8 +1854,9 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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=
@ -1917,8 +1871,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
@ -1928,15 +1882,11 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
gopkg.in/guregu/null.v3 v3.5.0 h1:xTcasT8ETfMcUHn0zTvIYtQud/9Mx5dJqD554SZct0o=
gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
@ -1983,4 +1933,5 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=

View File

@ -0,0 +1,34 @@
// Package cryptoinfo is designed to examine keys and certificates on
// disk, and return information about them. It is designed to work
// with dataflatten, and may eventually it may replace pkg/keyidentifier
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
// identifierSignature is an internal type to denote the identification functions. It's
// used to add a small amount of clarity to the array of possible identifiers.
type identifierSignature func(data []byte, password string) (results []*KeyInfo, err error)
var defaultIdentifiers = []identifierSignature{
tryP12,
tryDer,
tryPem,
}
// Identify examines a []byte and attempts to descern what
// cryptographic material is contained within.
func Identify(data []byte, password string) ([]*KeyInfo, error) {
// Try the identifiers. Some future work might be to allow
// callers to specify identifier order, or to try to discern
// it from the file extension. But meanwhile, just try everything.
for _, fn := range defaultIdentifiers {
res, err := fn(data, password)
if err == nil {
return res, nil
}
}
// If we can't parse anything, return nothing. It's not a fatal error, and it's
// somewhat obvious from context that nothing was parsed.
return nil, nil
}

View File

@ -0,0 +1,111 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIdentify(t *testing.T) {
t.Parallel()
var tests = []struct {
in []string
password string
expectedCount int
expectedError bool
expectedSubjects []string
}{
{
in: []string{filepath.Join("testdata", "test_crt.pem")},
expectedCount: 1,
expectedSubjects: []string{"www.example.com"},
},
{
in: []string{filepath.Join("testdata", "test_crt.pem"), filepath.Join("testdata", "test_crt.pem")},
expectedCount: 2,
expectedSubjects: []string{"www.example.com", "www.example.com"},
},
{
in: []string{filepath.Join("testdata", "test_crt.der")},
expectedCount: 1,
expectedSubjects: []string{"www.example.com"},
},
{
in: []string{filepath.Join("testdata", "empty")},
expectedCount: 0,
},
{
in: []string{filepath.Join("testdata", "sslcerts.pem")},
expectedCount: 129,
expectedSubjects: []string{
"Autoridad de Certificacion Firmaprofesional CIF A62634068",
"Chambers of Commerce Root - 2008",
"Global Chambersign Root - 2008",
"ACCVRAIZ1",
"Actalis Authentication Root CA",
},
},
{
in: []string{filepath.Join("testdata", "test-unenc.p12")},
expectedCount: 2,
expectedSubjects: []string{"www.example.com"},
},
{
in: []string{filepath.Join("testdata", "test-enc.p12")}, //password is test123
password: "test123",
expectedCount: 2,
expectedSubjects: []string{"www.example.com"},
},
}
for _, tt := range tests {
tt := tt
t.Run(strings.Join(tt.in, ","), func(t *testing.T) {
t.Parallel()
in := []byte{}
for _, file := range tt.in {
fileBytes, err := os.ReadFile(file)
require.NoError(t, err, "reading input %s for setup", file)
in = bytes.Join([][]byte{in, fileBytes}, nil)
}
results, err := Identify(in, tt.password)
if tt.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Len(t, results, tt.expectedCount)
// If we have expected subjects, do they match?
count := 0
for _, returnedCert := range results {
// Some things aren't certs, just skep them for the expectedSubject test
cert, ok := returnedCert.Data.(*certExtract)
if !ok {
continue
}
count++
// If we don't have any more expected subjects, just break
if count > len(tt.expectedSubjects) {
break
}
assert.Equal(t, tt.expectedSubjects[count-1], cert.Subject.CommonName)
}
})
}
}

View File

@ -0,0 +1,119 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
import (
"encoding/json"
)
type KeyInfo struct {
Type kiType
Encoding kiEncoding
Data interface{}
DataName kiDataNames
Error error
Headers map[string]string
}
// kiDataNames is an internal type. It's used to help provide uniformity in the returned data.
type kiDataNames string
const (
kiCaCertificate kiDataNames = "certificate"
kiCertificate = "certificate"
kiKey = "key"
)
// kiType is an internal type to denote what an indentified blob is. It is ultimately presented as a string
type kiType string
const (
kiCACERTIFICATE kiType = "CA-CERTIFICATE" // Not totally sure what the correct string is here
kiCERTIFICATE = "CERTIFICATE"
kiKEY = "KEY"
)
// kiType is an internal type to denote what encoding was used. It is ultimately presented as a string
type kiEncoding string
const (
kiPEM kiEncoding = "PEM"
kiDER = "DER"
kiP12 = "P12"
)
func NewKey(encoding kiEncoding) *KeyInfo {
return &KeyInfo{
DataName: kiKey,
Encoding: encoding,
Type: kiKEY,
}
}
func NewCertificate(encoding kiEncoding) *KeyInfo {
return &KeyInfo{
DataName: kiCertificate,
Encoding: encoding,
Type: kiCERTIFICATE,
}
}
func NewCaCertificate(encoding kiEncoding) *KeyInfo {
return &KeyInfo{
DataName: kiCaCertificate,
Encoding: encoding,
Type: kiCACERTIFICATE,
}
}
func NewError(encoding kiEncoding, err error) *KeyInfo {
return &KeyInfo{
Encoding: encoding,
Error: err,
}
}
func (ki *KeyInfo) SetHeaders(headers map[string]string) *KeyInfo {
ki.Headers = headers
return ki
}
func (ki *KeyInfo) SetDataName(name kiDataNames) *KeyInfo {
ki.DataName = name
return ki
}
func (ki *KeyInfo) SetData(data interface{}, err error) *KeyInfo {
ki.Data = data
ki.Error = err
return ki
}
// MarshalJSON is used by the go json marshaller. Using a custom one here
// allows us a high degree of control over the resulting output. For example,
// it allows us to use the same struct here to encapsulate both keys and
// certificate, and still have somewhat differenciated output
func (ki *KeyInfo) MarshalJSON() ([]byte, error) {
// this feels somewhat inefficient WRT to allocations and shoving maps around. But it
// also feels the simplest way to get consistent behavior without needing to push
// the key/value pairs everywhere.
ret := map[string]interface{}{
"type": ki.Type,
"encoding": ki.Encoding,
}
if ki.Error != nil {
ret["error"] = ki.Error.Error()
} else {
if ki.DataName != "" {
ret[string(ki.DataName)] = ki.Data
} else {
ret["error"] = "No data name"
}
}
if len(ki.Headers) != 0 {
ret["headers"] = ki.Headers
}
return json.Marshal(ret)
}

View File

@ -0,0 +1,100 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"net"
"net/url"
"time"
)
type certExtract struct {
CRLDistributionPoints []string
DNSNames []string
EmailAddresses []string
ExcludedDNSDomains []string
ExcludedEmailAddresses []string
ExcludedIPRanges []*net.IPNet
ExcludedURIDomains []string
IPAddresses []net.IP
Issuer pkix.Name
IssuerParsed string
IssuingCertificateURL []string
KeyUsage int
KeyUsageParsed []string
NotBefore, NotAfter time.Time
OCSPServer []string
PermittedDNSDomains []string
PermittedDNSDomainsCritical bool
PermittedEmailAddresses []string
PermittedIPRanges []*net.IPNet
PermittedURIDomains []string
PublicKeyAlgorithm string
SerialNumber string
SignatureAlgorithm string
Subject pkix.Name
SubjectParsed string
URIs []*url.URL
Version int
}
// parseCertificate parses a certificate from a stream of bytes. We use this, instead of a bare x509.ParseCertificate, to handle some
// string conversions, and bitfield enumerations.
func parseCertificate(certBytes []byte) (interface{}, error) {
c, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, fmt.Errorf("parsing certificate: %w", err)
}
return extractCert(c)
}
func extractCert(c *x509.Certificate) (interface{}, error) {
return &certExtract{
CRLDistributionPoints: c.CRLDistributionPoints,
DNSNames: c.DNSNames,
EmailAddresses: c.EmailAddresses,
IPAddresses: c.IPAddresses,
Issuer: c.Issuer,
IssuerParsed: c.Issuer.String(),
IssuingCertificateURL: c.IssuingCertificateURL,
KeyUsage: int(c.KeyUsage),
KeyUsageParsed: keyUsageToStrings(c.KeyUsage),
NotAfter: c.NotAfter,
NotBefore: c.NotBefore,
OCSPServer: c.OCSPServer,
PublicKeyAlgorithm: c.PublicKeyAlgorithm.String(),
SerialNumber: c.SerialNumber.String(),
SignatureAlgorithm: c.SignatureAlgorithm.String(),
Subject: c.Subject,
SubjectParsed: c.Subject.String(),
URIs: c.URIs,
Version: c.Version,
}, nil
}
var keyUsageBits = map[x509.KeyUsage]string{
x509.KeyUsageDigitalSignature: "Digital Signature",
x509.KeyUsageContentCommitment: "Content Commitment",
x509.KeyUsageKeyEncipherment: "Key Encipherment",
x509.KeyUsageDataEncipherment: "Data Encipherment",
x509.KeyUsageKeyAgreement: "Key Agreement",
x509.KeyUsageCertSign: "Certificate Sign",
x509.KeyUsageCRLSign: "CRL Sign",
x509.KeyUsageEncipherOnly: "Encipher Only",
x509.KeyUsageDecipherOnly: "Decipher Only",
}
func keyUsageToStrings(k x509.KeyUsage) []string {
var usage []string
for usageBit, usageMeaning := range keyUsageBits {
if k&usageBit != 0 {
usage = append(usage, usageMeaning)
}
}
return usage
}

0
orbit/pkg/cryptoinfo/testdata/empty vendored Normal file
View File

6306
orbit/pkg/cryptoinfo/testdata/sslcerts.pem vendored Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

15
orbit/pkg/cryptoinfo/testdata/test.key vendored Normal file
View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC/ISgV6QxEurKeU+N4gtcyIBxw8ztUWZVZll6yh+BXcSrUGvz1
JC5nas8Mbdk7QNkwka1rrH4MEJ7EnmN35ffmzO6j09p9RFy9Ez1AmMtF7/AYO66H
rRH/BS+L+fq3iBlxjZEYjijWEHdfqIpactADbnqj8Y0UXXxjyY6qx9xUwwIDAQAB
AoGAHs42SsnEK3O4BGLa//p+utqIGwBpKKBDvSvKWZYi55Ua5RLwgIZzYEHL22H9
KFq8ZuKkA/3KVyF6pZAt0g5j1S0Bl80p9pcd836Ym4Y6N0SQ0mEeFWnpzTZ3n+2J
kXaPBf1P386nmRpyxFvar8BtqbqSSRGbqsrbayWKxYDh2ukCQQDoDhP4GSEfzdTn
MVgdDpBtUG5x6PHW+JDPb53FMbslzwIYBoFt0ouLFdtMI/jKfmD7m+jOInlp9t6i
S+/nc3Z1AkEA0tn/r+UKZPaSXW+ibCLAvJGVbom8IOjDMo34sj2PDepBZ0tO9tw4
+S51Ggq7coe5d3+p6NJAP8kjHx6X/F5nVwJAHOSj1+BJH4yhVafvMK7/jJzXI5e9
hOauISXknwjyJGMB/7vPobz1Yvv1siVIdO4HZUykUAY619bFIbASzt6xgQJAfpWT
9FSMPfrt+hxYJZVjopHAZaFZCWTUM1iact+UL6VwaIQEvx2NMsPaV60TxfmHth81
sWnwWpr1c+xZEJDYdwJBAN0GqCFHpBAiDN6dmkSGS2R8Lk5CIuolWN5awuWSO02p
wOAISlrer0ltgsI5+jCaSubG8fepXo6sDXiDi+NTlSs=
-----END RSA PRIVATE KEY-----

Binary file not shown.

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB3zCCAUgCCQD+JONnvOs4tTANBgkqhkiG9w0BAQsFADA0MQswCQYDVQQGEwJV
UzELMAkGA1UECAwCTUExGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0yMjAy
MDMxNTU1MzFaFw0yMjAzMDUxNTU1MzFaMDQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
DAJNQTEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQC/ISgV6QxEurKeU+N4gtcyIBxw8ztUWZVZll6yh+BXcSrUGvz1
JC5nas8Mbdk7QNkwka1rrH4MEJ7EnmN35ffmzO6j09p9RFy9Ez1AmMtF7/AYO66H
rRH/BS+L+fq3iBlxjZEYjijWEHdfqIpactADbnqj8Y0UXXxjyY6qx9xUwwIDAQAB
MA0GCSqGSIb3DQEBCwUAA4GBAI55YBLrEaaRwIxWhmbLZ1gB+MkliVa/OV8FOnFc
Q/bnfP0L7gmN3kuDV9DD2QLFz/0ElRWftBlxnCo1/OqlGA+XEYFLmaq2icROW0N8
4JUDVgYLaVI5QJnUQCgNOZXq/mPfFHQ9x50uXpvNtdTJkis0F1EJwdqGcB5hbYwH
2+YR
-----END CERTIFICATE-----

View File

@ -0,0 +1,11 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
func tryDer(data []byte, _password string) ([]*KeyInfo, error) {
cert, err := parseCertificate(data)
if err != nil {
return nil, err
}
return []*KeyInfo{NewCertificate(kiDER).SetData(cert, err)}, nil
}

View File

@ -0,0 +1,29 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
import (
p12 "software.sslmate.com/src/go-pkcs12"
)
func tryP12(data []byte, password string) ([]*KeyInfo, error) {
privateKey, cert, caCerts, err := p12.DecodeChain(data, password)
if err != nil {
return nil, err
}
results := []*KeyInfo{}
if privateKey != nil {
results = append(results, NewKey(kiP12))
}
if cert != nil {
results = append(results, NewCertificate(kiP12).SetData(extractCert(cert)))
}
for _, c := range caCerts {
results = append(results, NewCaCertificate(kiP12).SetData(extractCert(c)))
}
return results, nil
}

View File

@ -0,0 +1,40 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfo
import (
"encoding/pem"
"errors"
"fmt"
)
func tryPem(pemBytes []byte, _password string) ([]*KeyInfo, error) {
expanded := []*KeyInfo{}
// Loop over the bytes, reading pem blocks
var block *pem.Block
for len(pemBytes) > 0 {
block, pemBytes = pem.Decode(pemBytes)
if block == nil {
// When pem.Decode finds no pem, it returns a nil block, and the input as rest.
// In that case, we stop parsing, as anything else would land in an infinite loop
break
}
expanded = append(expanded, expandPem(block))
}
if len(expanded) == 0 {
return nil, errors.New("No pem decoded")
}
return expanded, nil
}
func expandPem(block *pem.Block) *KeyInfo {
switch block.Type {
case "CERTIFICATE":
return NewCertificate(kiPEM).SetHeaders(block.Headers).SetData(parseCertificate(block.Bytes))
}
return NewError(kiPEM, fmt.Errorf("Unknown block type: %s", block.Type))
}

View File

@ -0,0 +1,531 @@
// Package dataflatten contains tools to flatten complex data
// structures.
//
// On macOS, many plists use an array of maps, these can be tricky to
// filter. This package knows how to flatten that structure, as well
// as rewriting it as a nested array, or filtering it. It is akin to
// xpath, though simpler.
//
// This tool works primarily through string interfaces, so type
// information may be lost.
//
// # Query Syntax
//
// The query syntax handles both filtering and basic rewriting. It is
// not perfect. The idea behind it, is that we descend through an data
// structure, specifying what matches at each level.
//
// Each level of query can do:
// - specify a filter, this is a simple string match with wildcard support. (prefix and/or postfix, but not infix)
// - If the data is an array, specify an index
// - For array-of-maps, specify a key to rewrite as a nested map
//
// Each query term has 3 parts: [#]string[=>kvmatch]
//
// 1. An optional `#` This denotes a key to rewrite an array-of-maps with
//
// 2. A search term. If this is an integer, it is interpreted as an array index.
//
// 3. a key/value match string. For a map, this is to match the value of a key.
//
// Some examples:
// * data/users Return everything under { data: { users: { ... } } }
// * data/users/0 Return the first item in the users array
// * data/users/name=>A* Return users whose name starts with "A"
// * data/users/#id Return the users, and rewrite the users array to be a map with the id as the key
//
// See the test suite for extensive examples.
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"bytes"
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/groob/plist"
howett "howett.net/plist"
)
// Flattener is an interface to flatten complex, nested, data
// structures. It recurses through them, and returns a simplified
// form. At the simplest level, this rewrites:
//
// { foo: { bar: { baz: 1 } } }
//
// To:
//
// [ { path: foo/bar/baz, value: 1 } ]
//
// It can optionally filtering and rewriting.
type Flattener struct {
debugLogging bool
expandNestedPlist bool
includeNestedRaw bool
includeNils bool
logger log.Logger
query []string
queryKeyDenoter string
queryWildcard string
rows []Row
}
type FlattenOpts func(*Flattener)
// IncludeNulls indicates that Flatten should return null values,
// instead of skipping over them.
func IncludeNulls() FlattenOpts {
return func(fl *Flattener) {
fl.includeNils = true
}
}
// WithNestedPlist indicates that nested plists should be expanded
func WithNestedPlist() FlattenOpts {
return func(fl *Flattener) {
fl.expandNestedPlist = true
}
}
// WithLogger sets the logger to use
func WithLogger(logger log.Logger) FlattenOpts {
if logger == nil {
return func(_ *Flattener) {}
}
return func(fl *Flattener) {
fl.logger = logger
}
}
// WithDebugLogging enables debug logging. With debug logs,
// dataflatten is very verbose. This can overwhelm the other launcher
// logs. As we're not generally debugging this library, the default is
// to not enable debug logging.
func WithDebugLogging() FlattenOpts {
return func(fl *Flattener) {
fl.debugLogging = true
}
}
// WithQuery Specifies a query to flatten with. This is used both for
// re-writing arrays into maps, and for filtering. See "Query
// Specification" for docs.
func WithQuery(q []string) FlattenOpts {
if q == nil || len(q) == 0 || (len(q) == 1 && q[0] == "") {
return func(_ *Flattener) {}
}
return func(fl *Flattener) {
fl.query = q
}
}
// Flatten is the entry point to the Flattener functionality.
func Flatten(data interface{}, opts ...FlattenOpts) ([]Row, error) {
fl := &Flattener{
rows: []Row{},
logger: log.NewNopLogger(),
queryWildcard: `*`,
queryKeyDenoter: `#`,
}
for _, opt := range opts {
opt(fl)
}
if !fl.debugLogging {
fl.logger = level.NewFilter(fl.logger, level.AllowInfo())
}
if err := fl.descend([]string{}, data, 0); err != nil {
return nil, err
}
return fl.rows, nil
}
// descend recurses through a given data structure flattening along the way.
func (fl *Flattener) descend(path []string, data interface{}, depth int) error {
queryTerm, isQueryMatched := fl.queryAtDepth(depth)
logger := log.With(fl.logger,
"caller", "descend",
"depth", depth,
"rows-so-far", len(fl.rows),
"query", queryTerm,
"path", strings.Join(path, "/"),
)
switch v := data.(type) {
case []interface{}:
for i, e := range v {
pathKey := strconv.Itoa(i)
level.Debug(logger).Log("msg", "checking an array", "indexStr", pathKey)
// If the queryTerm starts with
// queryKeyDenoter, then we want to rewrite
// the path based on it. Note that this does
// no sanity checking. Multiple values will
// re-write. If the value isn't there, you get
// nothing. Etc.
//
// keyName == "name"
// keyValue == "alex" (need to test this againsty queryTerm
// pathKey == What we descend with
if strings.HasPrefix(queryTerm, fl.queryKeyDenoter) {
keyQuery := strings.SplitN(strings.TrimPrefix(queryTerm, fl.queryKeyDenoter), "=>", 2)
keyName := keyQuery[0]
innerlogger := log.With(logger, "arraykeyname", keyName)
level.Debug(logger).Log("msg", "attempting to coerce array into map")
e, ok := e.(map[string]interface{})
if !ok {
level.Debug(innerlogger).Log("msg", "can't coerce into map")
continue
}
// Is keyName in this array?
val, ok := e[keyName]
if !ok {
level.Debug(innerlogger).Log("msg", "keyName not in map")
continue
}
pathKey, ok = val.(string)
if !ok {
level.Debug(innerlogger).Log("msg", "can't coerce pathKey val into string")
continue
}
// Looks good to descend. we're overwritten both e and pathKey. Exit this conditional.
}
if !(isQueryMatched || fl.queryMatchArrayElement(e, i, queryTerm)) {
level.Debug(logger).Log("msg", "query not matched")
continue
}
if err := fl.descend(append(path, pathKey), e, depth+1); err != nil {
return fmt.Errorf("flattening array: %w", err)
}
}
case map[string]interface{}:
level.Debug(logger).Log("msg", "checking a map")
for k, e := range v {
// Check that the key name matches. If not, skip this entire
// branch of the map
if !(isQueryMatched || fl.queryMatchString(k, queryTerm)) {
continue
}
if err := fl.descend(append(path, k), e, depth+1); err != nil {
return fmt.Errorf("flattening map: %w", err)
}
}
case []map[string]interface{}:
level.Debug(logger).Log("msg", "checking an array of maps")
for i, e := range v {
if err := fl.descend(append(path, strconv.Itoa(i)), e, depth+1); err != nil {
return fmt.Errorf("flattening array of maps: %w", err)
}
}
case nil:
// Because we want to filter nils out, we do _not_ examine isQueryMatched here
if !(fl.queryMatchNil(queryTerm)) {
level.Debug(logger).Log("msg", "query not matched")
return nil
}
fl.rows = append(fl.rows, NewRow(path, ""))
case string:
return fl.descendMaybePlist(path, []byte(v), depth)
case []byte:
// Most string like data comes in this way
return fl.descendMaybePlist(path, v, depth)
default:
if err := fl.handleStringLike(logger, path, v, depth); err != nil {
return fmt.Errorf("flattening at path %v: %w", path, err)
}
}
return nil
}
// handleStringLike is called when we finally have an object we think
// can be converted to a string. It uses the depth to compare against
// the query, and returns a stringify'ed value
func (fl *Flattener) handleStringLike(logger log.Logger, path []string, v interface{}, depth int) error {
queryTerm, isQueryMatched := fl.queryAtDepth(depth)
stringValue, err := stringify(v)
if err != nil {
return err
}
if !(isQueryMatched || fl.queryMatchString(stringValue, queryTerm)) {
level.Debug(logger).Log("msg", "query not matched")
return nil
}
fl.rows = append(fl.rows, NewRow(path, stringValue))
return nil
}
// descendMaybePlist optionally tries to decode []byte data as an
// embedded plist. In the case of failures, it falls back to treating
// it like a plain string.
func (fl *Flattener) descendMaybePlist(path []string, data []byte, depth int) error {
logger := log.With(fl.logger,
"caller", "descendMaybePlist",
"depth", depth,
"rows-so-far", len(fl.rows),
"path", strings.Join(path, "/"),
)
// Skip if we're not expanding nested plists
if !fl.expandNestedPlist {
return fl.handleStringLike(logger, path, data, depth)
}
// Skip if this doesn't look like a plist.
if !isPlist(data) {
return fl.handleStringLike(logger, path, data, depth)
}
// Looks like a plist. Try parsing it
level.Debug(logger).Log("msg", "Parsing inner plist")
var innerData interface{}
if err := plist.Unmarshal(data, &innerData); err != nil {
level.Info(logger).Log("msg", "plist parsing failed", "err", err)
return fl.handleStringLike(logger, path, data, depth)
}
// have a parsed plist. Descend and return from here.
if fl.includeNestedRaw {
if err := fl.handleStringLike(logger, append(path, "_raw"), data, depth); err != nil {
level.Error(logger).Log("msg", "Failed to add _raw key", "err", err)
}
}
if err := fl.descend(path, innerData, depth); err != nil {
return fmt.Errorf("flattening plist data: %w", err)
}
return nil
}
func (fl *Flattener) queryMatchNil(queryTerm string) bool {
// TODO: If needed, we could use queryTerm for optional nil filtering
return fl.includeNils
}
// queryMatchArrayElement matches arrays. This one is magic.
//
// Syntax:
//
// #i -- Match index i. For example `#0`
// k=>queryTerm -- If this is a map, it should have key k, that matches queryTerm
//
// We use `=>` as something that is reasonably intuitive, and not very
// likely to occur on it's own. Unfortunately, `==` shows up in base64
func (fl *Flattener) queryMatchArrayElement(data interface{}, arrIndex int, queryTerm string) bool {
logger := log.With(fl.logger,
"caller", "queryMatchArrayElement",
"rows-so-far", len(fl.rows),
"query", queryTerm,
"arrIndex", arrIndex,
)
// strip off the key re-write denotation before trying to match
queryTerm = strings.TrimPrefix(queryTerm, fl.queryKeyDenoter)
if queryTerm == fl.queryWildcard {
return true
}
// If the queryTerm is an int, then we expect to match the index
if queryIndex, err := strconv.Atoi(queryTerm); err == nil {
level.Debug(logger).Log("msg", "using numeric index comparison")
return queryIndex == arrIndex
}
level.Debug(logger).Log("msg", "checking data type")
switch dataCasted := data.(type) {
case []interface{}:
// fails. We can't match an array that has arrays as elements. Use a wildcard
return false
case map[string]interface{}:
kvQuery := strings.SplitN(queryTerm, "=>", 2)
// If this is one long, then we're testing for whether or not there's a key with this name,
if len(kvQuery) == 1 {
_, ok := dataCasted[kvQuery[0]]
return ok
}
// Else see if the value matches
for k, v := range dataCasted {
// Since this needs to check against _every_
// member, return true. Or fall through to the
// false.
if fl.queryMatchString(k, kvQuery[0]) && fl.queryMatchStringify(v, kvQuery[1]) {
return true
}
}
return false
default:
// non-iterable. stringify and be done
return fl.queryMatchStringify(dataCasted, queryTerm)
}
}
func (fl *Flattener) queryMatchStringify(data interface{}, queryTerm string) bool {
// strip off the key re-write denotation before trying to match
queryTerm = strings.TrimPrefix(queryTerm, fl.queryKeyDenoter)
if queryTerm == fl.queryWildcard {
return true
}
if data == nil {
return fl.queryMatchNil(queryTerm)
}
stringValue, err := stringify(data)
if err != nil {
return false
}
return fl.queryMatchString(stringValue, queryTerm)
}
func (fl *Flattener) queryMatchString(v, queryTerm string) bool {
if queryTerm == fl.queryWildcard {
return true
}
// Some basic string manipulations to handle prefix and suffix operations
switch {
case strings.HasPrefix(queryTerm, fl.queryWildcard) && strings.HasSuffix(queryTerm, fl.queryWildcard):
queryTerm = strings.TrimPrefix(queryTerm, fl.queryWildcard)
queryTerm = strings.TrimSuffix(queryTerm, fl.queryWildcard)
return strings.Contains(v, queryTerm)
case strings.HasPrefix(queryTerm, fl.queryWildcard):
queryTerm = strings.TrimPrefix(queryTerm, fl.queryWildcard)
return strings.HasSuffix(v, queryTerm)
case strings.HasSuffix(queryTerm, fl.queryWildcard):
queryTerm = strings.TrimSuffix(queryTerm, fl.queryWildcard)
return strings.HasPrefix(v, queryTerm)
}
return v == queryTerm
}
// queryAtDepth returns the query parameter for a given depth, and
// boolean indicating we've run out of queries. If we've run out of
// queries, than we can start checking, everything is a match.
func (fl *Flattener) queryAtDepth(depth int) (string, bool) {
// if we're nil, there's an implied wildcard
//
// This works because:
// []string is len 0, and nil
// []string{} is len 0, but not nil
if fl.query == nil {
return fl.queryWildcard, true
}
// If there's no query for this depth, then there's an implied
// wildcard. This allows the query to specify prefixes.
if depth+1 > len(fl.query) {
return fl.queryWildcard, true
}
q := fl.query[depth]
return q, q == fl.queryWildcard
}
// stringify takes an arbitrary piece of data, and attempst to coerce
// it into a string.
func stringify(data interface{}) (string, error) {
switch v := data.(type) {
case nil:
return "", nil
case string:
return v, nil
case []byte:
s := string(v)
if utf8.ValidString(s) {
return s, nil
}
return base64.StdEncoding.EncodeToString(v), nil
case uint8:
return strconv.FormatUint(uint64(v), 10), nil
case uint16:
return strconv.FormatUint(uint64(v), 10), nil
case uint32:
return strconv.FormatUint(uint64(v), 10), nil
case uint64:
return strconv.FormatUint(v, 10), nil
case float32:
return strconv.FormatFloat(float64(v), 'f', -1, 32), nil
case float64:
return strconv.FormatFloat(v, 'f', -1, 64), nil
case int:
return strconv.Itoa(v), nil
case int8:
return strconv.FormatInt(int64(v), 10), nil
case int16:
return strconv.FormatInt(int64(v), 10), nil
case int32:
return strconv.FormatInt(int64(v), 10), nil
case int64:
return strconv.FormatInt(v, 10), nil
case bool:
return strconv.FormatBool(v), nil
case time.Time:
return strconv.FormatInt(v.Unix(), 10), nil
case howett.UID:
return strconv.FormatUint(uint64(v), 10), nil
case fmt.Stringer:
return v.String(), nil
default:
// spew.Dump(data)
return "", fmt.Errorf("unknown type on %v", data)
}
}
// isPlist returns whether or not something looks like it might be a
// plist. It uses Contains, instead of HasPrefix, as some encodings
// have a leading character.
func isPlist(data []byte) bool {
var dataPrefix []byte
if len(data) <= 30 {
dataPrefix = data
} else {
dataPrefix = data[0:30]
}
if bytes.Contains(dataPrefix, []byte("bplist0")) {
return true
}
if bytes.Contains(dataPrefix, []byte(`xml version="1.0"`)) && bytes.Contains(data, []byte(`<!DOCTYPE plist PUBLIC`)) {
return true
}
return false
}

View File

@ -0,0 +1,625 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"sort"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
type flattenTestCase struct {
in string
out []Row
options []FlattenOpts
comment string
err bool
}
func TestFlatten_Complex2(t *testing.T) {
t.Parallel()
dataRaw, err := os.ReadFile(filepath.Join("testdata", "complex2.json"))
require.NoError(t, err, "reading file")
var dataIn interface{}
require.NoError(t, json.Unmarshal(dataRaw, &dataIn), "unmarshalling json")
var tests = []flattenTestCase{
{
out: []Row{
{Path: []string{"addons", "0", "bool1"}, Value: "true"},
{Path: []string{"addons", "0", "nest2", "0", "string2"}, Value: "foo"},
{Path: []string{"addons", "0", "nest3", "string6"}, Value: "null"},
{Path: []string{"addons", "0", "nest3", "string7"}, Value: "A Very Long Sentence"},
{Path: []string{"addons", "0", "nest3", "string8"}, Value: "short"},
{Path: []string{"addons", "0", "string1"}, Value: "hello"},
},
},
{
out: []Row{
{Path: []string{"addons", "0", "bool1"}, Value: "true"},
{Path: []string{"addons", "0", "nest2", "0", "null3"}, Value: ""},
{Path: []string{"addons", "0", "nest2", "0", "null4"}, Value: ""},
{Path: []string{"addons", "0", "nest2", "0", "string2"}, Value: "foo"},
{Path: []string{"addons", "0", "nest3", "string3"}, Value: ""},
{Path: []string{"addons", "0", "nest3", "string4"}, Value: ""},
{Path: []string{"addons", "0", "nest3", "string5"}, Value: ""},
{Path: []string{"addons", "0", "nest3", "string6"}, Value: "null"},
{Path: []string{"addons", "0", "nest3", "string7"}, Value: "A Very Long Sentence"},
{Path: []string{"addons", "0", "nest3", "string8"}, Value: "short"},
{Path: []string{"addons", "0", "null1"}, Value: ""},
{Path: []string{"addons", "0", "null2"}, Value: ""},
{Path: []string{"addons", "0", "string1"}, Value: "hello"},
},
options: []FlattenOpts{IncludeNulls()},
comment: "includes null",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Flatten(dataIn, tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
func TestFlatten_NestingBug(t *testing.T) {
t.Parallel()
dataRaw, err := os.ReadFile(filepath.Join("testdata", "nested.json"))
require.NoError(t, err, "reading file")
var dataIn interface{}
require.NoError(t, json.Unmarshal(dataRaw, &dataIn), "unmarshalling json")
var tests = []flattenTestCase{
{
out: []Row{
{Path: []string{"addons", "0", "name"}, Value: "Nested Strings"},
{Path: []string{"addons", "0", "nest1", "string3"}, Value: "string3"},
{Path: []string{"addons", "0", "nest1", "string4"}, Value: "string4"},
{Path: []string{"addons", "0", "nest1", "string5"}, Value: "string5"},
{Path: []string{"addons", "0", "nest1", "string6"}, Value: "string6"},
},
},
{
out: []Row{
{Path: []string{"addons", "0", "name"}, Value: "Nested Strings"},
{Path: []string{"addons", "0", "nest1", "string1"}, Value: ""},
{Path: []string{"addons", "0", "nest1", "string2"}, Value: ""},
{Path: []string{"addons", "0", "nest1", "string3"}, Value: "string3"},
{Path: []string{"addons", "0", "nest1", "string4"}, Value: "string4"},
{Path: []string{"addons", "0", "nest1", "string5"}, Value: "string5"},
{Path: []string{"addons", "0", "nest1", "string6"}, Value: "string6"},
},
options: []FlattenOpts{IncludeNulls()},
comment: "includes null",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Flatten(dataIn, tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
func TestFlatten_Jsonl_Complex(t *testing.T) {
t.Parallel()
// Do the unmarshaling here, so we don't keep doing it again and again
dataRaw, err := os.ReadFile(filepath.Join("testdata", "animals.jsonl"))
require.NoError(t, err, "reading file")
// We do a bunch of tests to select this user. So we'll pull
// this out here and make the testcases more DRY
testdataUser0 := []Row{
{Path: []string{"2", "users", "0", "favorites", "0"}, Value: "ants"},
{Path: []string{"2", "users", "0", "name"}, Value: "Alex Aardvark"},
{Path: []string{"2", "users", "0", "uuid"}, Value: "abc123"},
{Path: []string{"2", "users", "0", "id"}, Value: "1"},
}
var tests = []flattenTestCase{
{
out: []Row{
{Path: []string{"0", "metadata", "testing"}, Value: "true"},
{Path: []string{"0", "metadata", "version"}, Value: "1.0.1"},
{Path: []string{"1", "system"}, Value: "users demo"},
{Path: []string{"2", "users", "0", "favorites", "0"}, Value: "ants"},
{Path: []string{"2", "users", "0", "id"}, Value: "1"},
{Path: []string{"2", "users", "0", "name"}, Value: "Alex Aardvark"},
{Path: []string{"2", "users", "0", "uuid"}, Value: "abc123"},
{Path: []string{"2", "users", "1", "favorites", "0"}, Value: "mice"},
{Path: []string{"2", "users", "1", "favorites", "1"}, Value: "birds"},
{Path: []string{"2", "users", "1", "id"}, Value: "2"},
{Path: []string{"2", "users", "1", "name"}, Value: "Bailey Bobcat"},
{Path: []string{"2", "users", "1", "uuid"}, Value: "def456"},
{Path: []string{"2", "users", "2", "favorites", "0"}, Value: "seeds"},
{Path: []string{"2", "users", "2", "id"}, Value: "3"},
{Path: []string{"2", "users", "2", "name"}, Value: "Cam Chipmunk"},
{Path: []string{"2", "users", "2", "uuid"}, Value: "ghi789"},
{Path: []string{"3", "0"}, Value: "array-item-A"},
{Path: []string{"3", "1"}, Value: "array-item-B"},
{Path: []string{"3", "2"}, Value: "array-item-C"},
},
comment: "all together",
},
{
comment: "query metadata",
options: []FlattenOpts{WithQuery([]string{"*", "metadata"})},
out: []Row{
{Path: []string{"0", "metadata", "testing"}, Value: "true"},
{Path: []string{"0", "metadata", "version"}, Value: "1.0.1"},
},
},
{
comment: "array by #",
options: []FlattenOpts{WithQuery([]string{"*", "users", "0"})},
out: testdataUser0,
},
{
comment: "array by id value",
options: []FlattenOpts{WithQuery([]string{"*", "users", "id=>1"})},
out: testdataUser0,
},
{
comment: "array by uuid",
options: []FlattenOpts{WithQuery([]string{"*", "users", "uuid=>abc123"})},
out: testdataUser0,
},
{
comment: "array by name with suffix wildcard",
options: []FlattenOpts{WithQuery([]string{"*", "users", "name=>Al*"})},
out: testdataUser0,
},
{
comment: "array by name with prefix wildcard",
options: []FlattenOpts{WithQuery([]string{"*", "users", "name=>*Aardvark"})},
out: testdataUser0,
},
{
comment: "array by name with suffix and prefix",
options: []FlattenOpts{WithQuery([]string{"*", "users", "name=>*Aardv*"})},
out: testdataUser0,
},
{
comment: "who likes ants, array re-written",
options: []FlattenOpts{WithQuery([]string{"*", "users", "#name", "favorites", "ants"})},
out: []Row{
{Path: []string{"2", "users", "Alex Aardvark", "favorites", "0"}, Value: "ants"},
},
},
{
comment: "rewritten and filtered",
options: []FlattenOpts{WithQuery([]string{"*", "users", "#name=>Al*", "id"})},
out: []Row{
{Path: []string{"2", "users", "Alex Aardvark", "id"}, Value: "1"},
},
},
{
comment: "bad key name",
options: []FlattenOpts{WithQuery([]string{"*", "users", "#nokey"})},
out: []Row{},
},
{
comment: "rewrite array to map",
options: []FlattenOpts{WithQuery([]string{"*", "users", "#name", "id"})},
out: []Row{
{Path: []string{"2", "users", "Alex Aardvark", "id"}, Value: "1"},
{Path: []string{"2", "users", "Bailey Bobcat", "id"}, Value: "2"},
{Path: []string{"2", "users", "Cam Chipmunk", "id"}, Value: "3"},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Jsonl(bytes.NewReader(dataRaw), tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
func TestFlatten_Complex(t *testing.T) {
t.Parallel()
// Do the unmarshaling here, so we don't keep doing it again and again
dataRaw, err := os.ReadFile(filepath.Join("testdata", "animals.json"))
require.NoError(t, err, "reading file")
var dataIn interface{}
require.NoError(t, json.Unmarshal(dataRaw, &dataIn), "unmarshalling json")
// We do a bunch of tests to select this user. So we'll pull
// this out here and make the testcases more DRY
testdataUser0 := []Row{
{Path: []string{"users", "0", "favorites", "0"}, Value: "ants"},
{Path: []string{"users", "0", "id"}, Value: "1"},
{Path: []string{"users", "0", "name"}, Value: "Alex Aardvark"},
{Path: []string{"users", "0", "uuid"}, Value: "abc123"},
}
var tests = []flattenTestCase{
{
out: []Row{
{Path: []string{"metadata", "testing"}, Value: "true"},
{Path: []string{"metadata", "version"}, Value: "1.0.1"},
{Path: []string{"system"}, Value: "users demo"},
{Path: []string{"users", "0", "favorites", "0"}, Value: "ants"},
{Path: []string{"users", "0", "id"}, Value: "1"},
{Path: []string{"users", "0", "name"}, Value: "Alex Aardvark"},
{Path: []string{"users", "0", "uuid"}, Value: "abc123"},
{Path: []string{"users", "1", "favorites", "0"}, Value: "mice"},
{Path: []string{"users", "1", "favorites", "1"}, Value: "birds"},
{Path: []string{"users", "1", "id"}, Value: "2"},
{Path: []string{"users", "1", "name"}, Value: "Bailey Bobcat"},
{Path: []string{"users", "1", "uuid"}, Value: "def456"},
{Path: []string{"users", "2", "favorites", "0"}, Value: "seeds"},
{Path: []string{"users", "2", "id"}, Value: "3"},
{Path: []string{"users", "2", "name"}, Value: "Cam Chipmunk"},
{Path: []string{"users", "2", "uuid"}, Value: "ghi789"},
},
comment: "all together",
},
{
options: []FlattenOpts{WithQuery([]string{"metadata"})},
out: []Row{
{Path: []string{"metadata", "testing"}, Value: "true"},
{Path: []string{"metadata", "version"}, Value: "1.0.1"},
},
},
{
comment: "array by #",
options: []FlattenOpts{WithQuery([]string{"users", "0"})},
out: testdataUser0,
},
{
comment: "array by id value",
options: []FlattenOpts{WithQuery([]string{"users", "id=>1"})},
out: testdataUser0,
},
{
comment: "array by uuid",
options: []FlattenOpts{WithQuery([]string{"users", "uuid=>abc123"})},
out: testdataUser0,
},
{
comment: "array by name with suffix wildcard",
options: []FlattenOpts{WithQuery([]string{"users", "name=>Al*"})},
out: testdataUser0,
},
{
comment: "array by name with prefix wildcard",
options: []FlattenOpts{WithQuery([]string{"users", "name=>*Aardvark"})},
out: testdataUser0,
},
{
comment: "array by name with suffix and prefix",
options: []FlattenOpts{WithQuery([]string{"users", "name=>*Aardv*"})},
out: testdataUser0,
},
{
comment: "who likes ants, array re-written",
options: []FlattenOpts{WithQuery([]string{"users", "#name", "favorites", "ants"})},
out: []Row{
{Path: []string{"users", "Alex Aardvark", "favorites", "0"}, Value: "ants"},
},
},
{
comment: "rewritten and filtered",
options: []FlattenOpts{WithQuery([]string{"users", "#name=>Al*", "id"})},
out: []Row{
{Path: []string{"users", "Alex Aardvark", "id"}, Value: "1"},
},
},
{
comment: "bad key name",
options: []FlattenOpts{WithQuery([]string{"users", "#nokey"})},
out: []Row{},
},
{
comment: "rewrite array to map",
options: []FlattenOpts{WithQuery([]string{"users", "#name", "id"})},
out: []Row{
{Path: []string{"users", "Alex Aardvark", "id"}, Value: "1"},
{Path: []string{"users", "Bailey Bobcat", "id"}, Value: "2"},
{Path: []string{"users", "Cam Chipmunk", "id"}, Value: "3"},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Flatten(dataIn, tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
func TestFlatten_ArrayMaps(t *testing.T) {
t.Parallel()
var tests = []flattenTestCase{
{
in: `{"data": [{"v":1,"id":"a"},{"v":2,"id":"b"},{"v":3,"id":"c"}]}`,
out: []Row{
{Path: []string{"data", "0", "id"}, Value: "a"},
{Path: []string{"data", "0", "v"}, Value: "1"},
{Path: []string{"data", "1", "id"}, Value: "b"},
{Path: []string{"data", "1", "v"}, Value: "2"},
{Path: []string{"data", "2", "id"}, Value: "c"},
{Path: []string{"data", "2", "v"}, Value: "3"},
},
comment: "nested array as array",
},
{
in: `{"data": [{"v":1,"id":"a"},{"v":2,"id":"b"},{"v":3,"id":"c"}]}`,
out: []Row{
{Path: []string{"data", "a", "id"}, Value: "a"},
{Path: []string{"data", "a", "v"}, Value: "1"},
{Path: []string{"data", "b", "id"}, Value: "b"},
{Path: []string{"data", "b", "v"}, Value: "2"},
{Path: []string{"data", "c", "id"}, Value: "c"},
{Path: []string{"data", "c", "v"}, Value: "3"},
},
options: []FlattenOpts{WithQuery([]string{"data", "#id"})},
comment: "nested array as map",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Json([]byte(tt.in), tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
func TestFlatten(t *testing.T) {
t.Parallel()
var tests = []flattenTestCase{
{
in: "a",
err: true,
},
{
in: `["a", null]`,
out: []Row{
{Path: []string{"0"}, Value: "a"},
},
comment: "skip null",
},
{
in: `["a", "b", null]`,
out: []Row{
{Path: []string{"0"}, Value: "a"},
{Path: []string{"1"}, Value: "b"},
{Path: []string{"2"}, Value: ""},
},
options: []FlattenOpts{IncludeNulls()},
comment: "includes null",
},
{
in: `["1"]`,
out: []Row{
{Path: []string{"0"}, Value: "1"},
},
},
{
in: `["a", true, false, "1", 2, 3.3]`,
out: []Row{
{Path: []string{"0"}, Value: "a"},
{Path: []string{"1"}, Value: "true"},
{Path: []string{"2"}, Value: "false"},
{Path: []string{"3"}, Value: "1"},
{Path: []string{"4"}, Value: "2"},
{Path: []string{"5"}, Value: "3.3"},
},
comment: "mixed types",
},
{
in: `{"a": 1, "b": "2.2", "c": [1,2,3]}`,
out: []Row{
{Path: []string{"a"}, Value: "1"},
{Path: []string{"b"}, Value: "2.2"},
{Path: []string{"c", "0"}, Value: "1"},
{Path: []string{"c", "1"}, Value: "2"},
{Path: []string{"c", "2"}, Value: "3"},
},
comment: "nested types",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Json([]byte(tt.in), tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
func TestFlattenJsonlErrors(t *testing.T) {
t.Parallel()
var tests = []flattenTestCase{
{
in: "a",
err: true,
},
{
// this test case was left over from attempting to parse json that
// is contained within a file that is not stricly jsonl
// it should error, maybe look at this again in the future?
comment: "valid json inline text",
in: `valid json is hidden["a"]in me`,
err: true,
},
{
// this test case was left over from attempting to parse json that
// is contained within a file that is not stricly jsonl
// it should error, maybe look at this again in the future?
comment: "valid json sandwich",
in: `
there is some json under me
["a"]
there is some json above me
`,
err: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Jsonl(bytes.NewBuffer([]byte(tt.in)), tt.options...)
testFlattenCase(t, tt, actual, err)
})
}
}
// add mutext due to data races when running locally, don't seem to appear in CI
// maybe remove if slows down CI too much
var testFlattenCaseMutex sync.Mutex
// testFlattenCase runs tests for a single test case. Normally this
// would be in a for loop, instead it's abstracted here to make it
// simpler to split up a giant array of test cases.
func testFlattenCase(t *testing.T, tt flattenTestCase, actual []Row, actualErr error) {
testFlattenCaseMutex.Lock()
defer testFlattenCaseMutex.Unlock()
if tt.err {
require.Error(t, actualErr, "test %s %s", tt.in, tt.comment)
return
}
require.NoError(t, actualErr, "test %s %s", tt.in, tt.comment)
// Despite being an array. data is returned
// unordered. This greatly complicates our testing. We
// can either sort it, or use an unordered comparison
// operator. The `require.ElementsMatch` produces much
// harder to read diffs, so instead we'll sort things.
sort.SliceStable(tt.out, func(i, j int) bool { return tt.out[i].StringPath("/") < tt.out[j].StringPath("/") })
sort.SliceStable(actual, func(i, j int) bool { return actual[i].StringPath("/") < actual[j].StringPath("/") })
require.EqualValues(t, tt.out, actual, "test %s %s", tt.in, tt.comment)
}
func TestFlattenSliceOfMaps(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in interface{}
opts []FlattenOpts
out []Row
wantErr bool
}{
{
name: "single",
in: []map[string]interface{}{
{
"id": "a",
"v": 1,
},
},
opts: []FlattenOpts{},
out: []Row{
{Path: []string{"0", "id"}, Value: "a"},
{Path: []string{"0", "v"}, Value: "1"},
},
wantErr: false,
},
{
name: "multiple",
in: []map[string]interface{}{
{
"id": "a",
"v": 1,
},
{
"id": "b",
"v": 2,
},
{
"id": "c",
"v": 3,
},
},
opts: []FlattenOpts{},
out: []Row{
{Path: []string{"0", "id"}, Value: "a"},
{Path: []string{"0", "v"}, Value: "1"},
{Path: []string{"1", "id"}, Value: "b"},
{Path: []string{"1", "v"}, Value: "2"},
{Path: []string{"2", "id"}, Value: "c"},
{Path: []string{"2", "v"}, Value: "3"},
},
wantErr: false,
},
{
name: "error",
in: []map[string]interface{}{
{
"id": []string{"this should cause an error"},
},
},
opts: []FlattenOpts{},
out: nil,
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := Flatten(tt.in, tt.opts...)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.ElementsMatch(t, tt.out, got)
})
}
}

View File

@ -0,0 +1,63 @@
// toml parsing won't work -- ini files don't quote the string and
// tend to have random spaces. Bummer, since
// https://github.com/pelletier/go-toml/pull/433 was right
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"github.com/go-ini/ini"
)
func IniFile(file string, opts ...FlattenOpts) ([]Row, error) {
return flattenIni(file, opts...)
}
func Ini(rawdata []byte, opts ...FlattenOpts) ([]Row, error) {
return flattenIni(rawdata, opts...)
}
// flattenIni uses go-ini to flatten ini data. The underlying library
// accepts both files and []byte via the interface{} type. It also
// makes heavy use of reflect, so this does some manual iteration to
// extract things.
func flattenIni(in interface{}, opts ...FlattenOpts) ([]Row, error) {
v := map[string]interface{}{}
iniFile, err := ini.Load(in)
if err != nil {
return nil, err
}
for _, section := range iniFile.Sections() {
// While we can use section.KeysHash() directly, instead we
// iterate. This allows us to canonicalize the value to handle
// booleans. Everything else we leave as string
sectionMap := make(map[string]interface{})
for _, key := range section.Keys() {
//fmt.Println(section.Name(), key.Name(), key.Value())
asBool, ok := iniToBool(key.Value())
if ok {
sectionMap[key.Name()] = asBool
} else {
sectionMap[key.Name()] = key.Value()
}
}
v[section.Name()] = sectionMap
}
return Flatten(v, opts...)
}
// iniToBool attempts to convert an ini value to a boolean. It returns
// the converted value, and ok. The list of strings comes from go-ini
func iniToBool(val string) (bool, bool) {
switch val {
case "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
return true, true
case "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
return false, true
}
return false, false
}

View File

@ -0,0 +1,109 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
)
var (
iniTestFilePath = path.Join("testdata", "secdata.ini")
iniTestFileLen = 87
)
func TestIniToBool(t *testing.T) {
t.Parallel()
var tests = []struct {
in string
expected bool
isBool bool
}{
{
in: "hello world",
},
{
in: "Yes",
expected: true,
isBool: true,
},
{
in: "No",
expected: false,
isBool: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.in, func(t *testing.T) {
t.Parallel()
asBool, ok := iniToBool(tt.in)
if tt.isBool {
require.True(t, ok)
require.Equal(t, tt.expected, asBool)
} else {
require.False(t, ok)
}
})
}
}
func TestIniFile(t *testing.T) {
t.Parallel()
rows, err := IniFile(iniTestFilePath)
require.NoError(t, err)
require.Len(t, rows, iniTestFileLen)
}
func TestIni(t *testing.T) {
t.Parallel()
fileBytes, err := os.ReadFile(iniTestFilePath)
require.NoError(t, err)
rows, err := Ini(fileBytes)
require.NoError(t, err)
require.Len(t, rows, iniTestFileLen)
}
func TestIniSecedit(t *testing.T) {
t.Parallel()
rows, err := IniFile(path.Join("testdata", "secdata.ini"))
require.NoError(t, err)
var tests = []struct {
name string
expected Row
}{
{
name: "converted boolean",
expected: Row{Path: []string{"Unicode", "Unicode"}, Value: "true"},
},
{
name: "string value",
expected: Row{Path: []string{"System Access", "NewAdministratorName"}, Value: "Administrator"},
},
{
// We're not casting this to false
name: "number value",
expected: Row{Path: []string{"Event Audit", "AuditDSAccess"}, Value: "0"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Contains(t, rows, tt.expected)
})
}
}

View File

@ -0,0 +1,26 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"encoding/json"
"fmt"
"os"
)
func JsonFile(file string, opts ...FlattenOpts) ([]Row, error) {
rawdata, err := os.ReadFile(file)
if err != nil {
return nil, err
}
return Json(rawdata, opts...)
}
func Json(rawdata []byte, opts ...FlattenOpts) ([]Row, error) {
var data interface{}
if err := json.Unmarshal(rawdata, &data); err != nil {
return nil, fmt.Errorf("unmarshalling json: %w", err)
}
return Flatten(data, opts...)
}

View File

@ -0,0 +1,37 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"encoding/json"
"fmt"
"io"
"os"
)
func JsonlFile(file string, opts ...FlattenOpts) ([]Row, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
return Jsonl(f, opts...)
}
func Jsonl(r io.Reader, opts ...FlattenOpts) ([]Row, error) {
decoder := json.NewDecoder(r)
var objects []interface{}
for {
var object interface{}
err := decoder.Decode(&object)
switch {
case err == nil:
objects = append(objects, object)
case err == io.EOF:
return Flatten(objects, opts...)
default:
return nil, fmt.Errorf("unmarshalling jsonl: %w", err)
}
}
}

View File

@ -0,0 +1,27 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"fmt"
"os"
"howett.net/plist"
)
func PlistFile(file string, opts ...FlattenOpts) ([]Row, error) {
rawdata, err := os.ReadFile(file)
if err != nil {
return nil, err
}
return Plist(rawdata, opts...)
}
func Plist(rawdata []byte, opts ...FlattenOpts) ([]Row, error) {
var data interface{}
if _, err := plist.Unmarshal(rawdata, &data); err != nil {
return nil, fmt.Errorf("unmarshalling plist: %w", err)
}
return Flatten(data, opts...)
}

View File

@ -0,0 +1,84 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"path/filepath"
"testing"
)
// TestPlist is testing a very simple plist case. Most of the more complex testing is in the spec files.
func TestPlist(t *testing.T) {
t.Parallel()
var tests = []flattenTestCase{
{
in: `<?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"><array><string>a</string><string>b</string></array></plist>`,
out: []Row{
{Path: []string{"0"}, Value: "a"},
{Path: []string{"1"}, Value: "b"},
},
},
{
in: `<?xml version="1.0" encoding="UTF-8"?>`,
err: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
actual, err := Plist([]byte(tt.in))
testFlattenCase(t, tt, actual, err)
})
}
}
func TestNestedPlists(t *testing.T) {
t.Parallel()
var tests = []flattenTestCase{
{
options: []FlattenOpts{WithNestedPlist()},
comment: "expand nested",
out: []Row{
{Path: []string{"inbinary", "astring"}, Value: "hello"},
{Path: []string{"inbinary", "arr", "0"}, Value: "one"},
{Path: []string{"inbinary", "arr", "1"}, Value: "two"},
{Path: []string{"inxml", "arr", "0"}, Value: "one"},
{Path: []string{"inxml", "arr", "1"}, Value: "two"},
{Path: []string{"inxml", "astring"}, Value: "hello"},
},
},
{
comment: "nested and queried",
options: []FlattenOpts{WithNestedPlist(), WithQuery([]string{"*", "arr", "0"})},
out: []Row{
{Path: []string{"inbinary", "arr", "0"}, Value: "one"},
{Path: []string{"inxml", "arr", "0"}, Value: "one"},
},
},
{
comment: "not expanded",
out: []Row{
{Path: []string{"inbinary"}, Value: "YnBsaXN0MDDSAQIDBlNhcnJXYXN0cmluZ6IEBVNvbmVTdHdvVWhlbGxvCA0RGRwgJAAAAAAAAAEBAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAq"},
{Path: []string{"inxml"}, Value: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>arr</key>\n\t<array>\n\t\t<string>one</string>\n\t\t<string>two</string>\n\t</array>\n\t<key>astring</key>\n\t<string>hello</string>\n</dict>\n</plist>"},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.comment, func(t *testing.T) {
t.Parallel()
for _, f := range []string{"nested.xml", "nested.plist"} {
actual, err := PlistFile(filepath.Join("testdata", "nested", f), tt.options...)
testFlattenCase(t, tt, actual, err)
}
})
}
}

View File

@ -0,0 +1,41 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"strings"
)
// Row is the record type we return.
type Row struct {
Path []string
Value string
}
// NewRow does a copy of the path elements, and returns a row. We do
// this copy to correct for some odd pointer passing bugs
func NewRow(path []string, value string) Row {
copiedPath := make([]string, len(path))
copy(copiedPath, path)
return Row{
Path: copiedPath,
Value: value,
}
}
func (r Row) StringPath(sep string) string {
return strings.Join(r.Path, sep)
}
func (r Row) ParentKey(sep string) (string, string) {
switch len(r.Path) {
case 0:
return "", ""
case 1:
return "", r.Path[0]
}
parent := strings.Join(r.Path[:len(r.Path)-1], sep)
key := r.Path[len(r.Path)-1]
return parent, key
}

View File

@ -0,0 +1,47 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestRowParentFunctions(t *testing.T) {
t.Parallel()
var tests = []struct {
in Row
parent string
key string
}{
{
in: Row{},
},
{
in: Row{Path: []string{}},
},
{
in: Row{Path: []string{"a"}},
parent: "",
key: "a",
},
{
in: Row{Path: []string{"a", "b"}},
parent: "a",
key: "b",
},
{
in: Row{Path: []string{"a", "b", "c"}},
parent: "a/b",
key: "c",
},
}
for _, tt := range tests {
parent, key := tt.in.ParentKey("/")
require.Equal(t, tt.parent, parent)
require.Equal(t, tt.key, key)
}
}

View File

@ -0,0 +1,84 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"bufio"
"bytes"
"strings"
)
type dataFunc func(data []byte, opts ...FlattenOpts) ([]Row, error)
type recordSplittingStrategy int
const (
None recordSplittingStrategy = iota
DuplicateKeys
)
func StringDelimitedFunc(kVDelimiter string, splittingStrategy recordSplittingStrategy) dataFunc {
switch splittingStrategy {
case None:
return singleRecordFunc(kVDelimiter)
case DuplicateKeys:
return duplicateKeyFunc(kVDelimiter)
default:
panic("Unknown record splitting strategy")
}
}
// duplicateKeyFunc returns a function that conforms to the interface expected
// by dataflattentable.Table's execDataFunc property. properties are grouped
// into a single record based on 'duplicate key' strategy: If a key/value pair
// is encountered, and the record being built already has a value for that key,
// then that record is considered 'complete'. The record is stored in the
// collection, and a new record is started. This strategy is only suitable if
// properties for a single record are grouped together, and there is at least
// one field that appears for every record before any sparse data.
func duplicateKeyFunc(kVDelimiter string) dataFunc {
return func(rawdata []byte, opts ...FlattenOpts) ([]Row, error) {
results := []interface{}{}
scanner := bufio.NewScanner(bytes.NewReader(rawdata))
row := map[string]interface{}{}
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, kVDelimiter, 2)
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if _, ok := row[key]; ok { // this key already exists, so we want to start a new record.
results = append(results, row) // store the 'finished' record in the collection
row = map[string]interface{}{} // reset the record
}
row[key] = value
}
results = append(results, row) // store the final record
return Flatten(results, opts...)
}
}
// singleRecordFunc returns an execData function that assumes 'rawdata'
// only holds key-value pairs for a single record. Additionally, each k/v pair
// must be on its own line. Useful for output that can be easily separated into
// separate records before 'flattening'
func singleRecordFunc(kVDelimiter string) dataFunc {
return func(rawdata []byte, opts ...FlattenOpts) ([]Row, error) {
results := []interface{}{}
scanner := bufio.NewScanner(bytes.NewReader(rawdata))
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, kVDelimiter, 2)
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
results = append(results, map[string]interface{}{key: value})
}
return Flatten(results, opts...)
}
}

View File

@ -0,0 +1,34 @@
{
"metadata": {
"testing": true,
"version": "1.0.1"
},
"system": "users demo",
"users": [
{
"favorites": [
"ants"
],
"uuid": "abc123",
"name": "Alex Aardvark",
"id": 1
},
{
"favorites": [
"mice",
"birds"
],
"uuid": "def456",
"name": "Bailey Bobcat",
"id": 2
},
{
"favorites": [
"seeds"
],
"uuid": "ghi789",
"name": "Cam Chipmunk",
"id": 3
}
]
}

View File

@ -0,0 +1,43 @@
{
"metadata": {
"testing": true,
"version": "1.0.1"
}
}
{
"system": "users demo"
}
{
"users": [
{
"favorites": [
"ants"
],
"uuid": "abc123",
"name": "Alex Aardvark",
"id": 1
},
{
"favorites": [
"mice",
"birds"
],
"uuid": "def456",
"name": "Bailey Bobcat",
"id": 2
},
{
"favorites": [
"seeds"
],
"uuid": "ghi789",
"name": "Cam Chipmunk",
"id": 3
}
]
}
[
"array-item-A",
"array-item-B",
"array-item-C"
]

Binary file not shown.

View File

@ -0,0 +1,55 @@
<?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>metadata</key>
<dict>
<key>testing</key>
<true/>
<key>version</key>
<string>1.0.1</string>
</dict>
<key>system</key>
<string>users demo</string>
<key>users</key>
<array>
<dict>
<key>favorites</key>
<array>
<string>ants</string>
</array>
<key>id</key>
<integer>1</integer>
<key>name</key>
<string>Alex Aardvark</string>
<key>uuid</key>
<string>abc123</string>
</dict>
<dict>
<key>favorites</key>
<array>
<string>mice</string>
<string>birds</string>
</array>
<key>id</key>
<integer>2</integer>
<key>name</key>
<string>Bailey Bobcat</string>
<key>uuid</key>
<string>def456</string>
</dict>
<dict>
<key>favorites</key>
<array>
<string>seeds</string>
</array>
<key>id</key>
<integer>3</integer>
<key>name</key>
<string>Cam Chipmunk</string>
<key>uuid</key>
<string>ghi789</string>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,28 @@
{
"addons": [
{
"string1": "hello",
"null1": null,
"null2": null,
"bool1": true,
"nest1": {
"anArray": []
},
"nest2": [
{
"null3": null,
"null4": null,
"string2": "foo"
}
],
"nest3": {
"string3": null,
"string4": null,
"string5": null,
"string6": "null",
"string7": "A Very Long Sentence",
"string8": "short"
}
}
]
}

View File

@ -0,0 +1,15 @@
{
"addons": [
{
"name": "Nested Strings",
"nest1": {
"string1": null,
"string2": null,
"string3": "string3",
"string4": "string4",
"string5": "string5",
"string6": "string6"
}
}
]
}

View File

@ -0,0 +1,29 @@
TARGETS:= nested.xml nested.plist
all: $(TARGETS)
clean:
-rm $(TARGETS)
inner.xml: inner.json
plutil -convert xml1 -o $@ $^
inner.plist.b64: inner.json
plutil -convert binary1 -o - $^ | base64 -o $@
inner.plist: inner.json
plutil -convert binary1 -o $@ $^
nested.xml: nested.template.xml inner.xml inner.plist.b64
cat $< | ruby -pe ' \
require "cgi"; \
x = IO.readlines("inner.xml").join.chomp; \
b = IO.readlines("inner.plist.b64").join.chomp; \
STDIN.each_line { |l| l.sub!("INBINARY", b); l.sub!("INXML",CGI::escapeHTML(x)); puts l };' > $@
nested.plist: nested.xml
plutil -convert binary1 -o $@ $<

View File

@ -0,0 +1 @@
Some simple shell/ruby/make hackery to get a nested plist

View File

@ -0,0 +1,7 @@
{
"arr": [
"one",
"two"
],
"astring": "hello"
}

Binary file not shown.

View File

@ -0,0 +1,11 @@
plutil -convert xml1 -o - nested.template.json
<?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>inbinary</key>
<data>INBINARY</data>
<key>inxml</key>
<string>INXML</string>
</dict>
</plist>

View File

@ -0,0 +1,23 @@
<?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>inbinary</key>
<data>YnBsaXN0MDDSAQIDBlNhcnJXYXN0cmluZ6IEBVNvbmVTdHdvVWhlbGxvCA0RGRwgJAAAAAAAAAEBAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAq</data>
<key>inxml</key>
<string>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;dict&gt;
&lt;key&gt;arr&lt;/key&gt;
&lt;array&gt;
&lt;string&gt;one&lt;/string&gt;
&lt;string&gt;two&lt;/string&gt;
&lt;/array&gt;
&lt;key&gt;astring&lt;/key&gt;
&lt;string&gt;hello&lt;/string&gt;
&lt;/dict&gt;
&lt;/plist&gt;</string>
</dict>
</plist>
plutil -convert xml1 -o - nested.template.json

View File

@ -0,0 +1,92 @@
[Unicode]
Unicode=yes
[System Access]
MinimumPasswordAge = 0
MaximumPasswordAge = 42
MinimumPasswordLength = 0
PasswordComplexity = 1
PasswordHistorySize = 0
LockoutBadCount = 1
ResetLockoutCount = 30
LockoutDuration = 30
RequireLogonToChangePassword = 0
ForceLogoffWhenHourExpire = 0
NewAdministratorName = "Administrator"
NewGuestName = "Guest"
ClearTextPassword = 0
LSAAnonymousNameLookup = 0
EnableAdminAccount = 0
EnableGuestAccount = 0
[Event Audit]
AuditSystemEvents = 0
AuditLogonEvents = 0
AuditObjectAccess = 0
AuditPrivilegeUse = 0
AuditPolicyChange = 0
AuditAccountManage = 0
AuditProcessTracking = 0
AuditDSAccess = 0
AuditAccountLogon = 0
[Registry Values]
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Setup\RecoveryConsole\SecurityLevel=4,0
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Setup\RecoveryConsole\SetCommand=4,0
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\CachedLogonsCount=1,"10"
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ForceUnlockLogon=4,0
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\PasswordExpiryWarning=4,5
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ScRemoveOption=1,"0"
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorAdmin=4,5
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorUser=4,3
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLastUserName=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableInstallerDetection=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableSecureUIAPaths=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableUIADesktopToggle=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableVirtualization=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters\SupportedEncryptionTypes=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\LegalNoticeCaption=1,""
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\LegalNoticeText=7,
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\PromptOnSecureDesktop=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ScForceOption=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ShutdownWithoutLogon=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\UndockWithoutLogon=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ValidateAdminCodeSignatures=4,0
MACHINE\Software\Policies\Microsoft\Windows\Safer\CodeIdentifiers\AuthenticodeEnabled=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\AuditBaseObjects=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\CrashOnAuditFail=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\DisableDomainCreds=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\EveryoneIncludesAnonymous=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy\Enabled=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\ForceGuest=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\FullPrivilegeAuditing=3,0
MACHINE\System\CurrentControlSet\Control\Lsa\LimitBlankPasswordUse=4,1
MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinClientSec=4,536870912
MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinServerSec=4,536870912
MACHINE\System\CurrentControlSet\Control\Lsa\NoLMHash=4,1
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymous=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymousSAM=4,1
MACHINE\System\CurrentControlSet\Control\Print\Providers\LanMan Print Services\Servers\AddPrinterDrivers=4,0
MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactPaths\Machine=7,System\CurrentControlSet\Control\ProductOptions,System\CurrentControlSet\Control\Server Applications,Software\Microsoft\Windows NT\CurrentVersion
MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedPaths\Machine=7,System\CurrentControlSet\Control\Print\Printers,System\CurrentControlSet\Services\Eventlog,Software\Microsoft\OLAP Server,Software\Microsoft\Windows NT\CurrentVersion\Print,Software\Microsoft\Windows NT\CurrentVersion\Windows,System\CurrentControlSet\Control\ContentIndex,System\CurrentControlSet\Control\Terminal Server,System\CurrentControlSet\Control\Terminal Server\UserConfig,System\CurrentControlSet\Control\Terminal Server\DefaultUserConfiguration,Software\Microsoft\Windows NT\CurrentVersion\Perflib,System\CurrentControlSet\Services\SysmonLog
MACHINE\System\CurrentControlSet\Control\Session Manager\Kernel\ObCaseInsensitive=4,1
MACHINE\System\CurrentControlSet\Control\Session Manager\Memory Management\ClearPageFileAtShutdown=4,0
MACHINE\System\CurrentControlSet\Control\Session Manager\ProtectionMode=4,1
MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\optional=7,
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\AutoDisconnect=4,15
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\EnableForcedLogOff=4,1
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\EnableSecuritySignature=4,0
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\NullSessionPipes=7,
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RequireSecuritySignature=4,0
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RestrictNullSessAccess=4,1
MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\EnablePlainTextPassword=4,0
MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\EnableSecuritySignature=4,1
MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\RequireSecuritySignature=4,0
MACHINE\System\CurrentControlSet\Services\LDAP\LDAPClientIntegrity=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\DisablePasswordChange=4,0
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\MaximumPasswordAge=4,30
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RequireSignOrSeal=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RequireStrongKey=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\SealSecureChannel=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\SignSecureChannel=4,1
[Version]
signature="$CHICAGO$"
Revision=1

View File

@ -0,0 +1,33 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflatten
import (
"fmt"
"os"
"github.com/clbanning/mxj"
)
func XmlFile(file string, opts ...FlattenOpts) ([]Row, error) {
rdr, err := os.Open(file)
if err != nil {
return nil, err
}
mv, err := mxj.NewMapXmlReader(rdr)
if err != nil {
return nil, err
}
return Flatten(mv.Old(), opts...)
}
func Xml(rawdata []byte, opts ...FlattenOpts) ([]Row, error) {
mv, err := mxj.NewMapXml(rawdata)
if err != nil {
return nil, fmt.Errorf("mxj parse: %w", err)
}
return Flatten(mv.Old(), opts...)
}

View File

@ -0,0 +1,94 @@
//go:build darwin
// +build darwin
// based on github.com/kolide/launcher/pkg/osquery/tables
package appicons
/*
#cgo darwin CFLAGS: -DDARWIN -x objective-c
#cgo darwin LDFLAGS: -framework Cocoa
#import <Appkit/AppKit.h>
void Icon(CFDataRef *iconDataRef, char* path) {
NSString *appPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
NSImage *img = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
//request 128x128 since we are going to resize the icon
NSRect targetFrame = NSMakeRect(0, 0, 128, 128);
CGImageRef cgref = [img CGImageForProposedRect:&targetFrame context:nil hints:nil];
NSBitmapImageRep *brep = [[NSBitmapImageRep alloc] initWithCGImage:cgref];
NSData *imageData = [brep TIFFRepresentation];
*iconDataRef = (CFDataRef)imageData;
}
*/
import (
"C"
)
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"hash/crc64"
"image"
"image/png"
"unsafe"
"github.com/nfnt/resize"
"github.com/osquery/osquery-go/plugin/table"
"golang.org/x/image/tiff"
)
var crcTable = crc64.MakeTable(crc64.ECMA)
func AppIcons() *table.Plugin {
columns := []table.ColumnDefinition{
table.TextColumn("path"),
table.TextColumn("icon"),
table.TextColumn("hash"),
}
return table.NewPlugin("app_icons", columns, generateAppIcons)
}
func generateAppIcons(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
q, ok := queryContext.Constraints["path"]
if !ok || len(q.Constraints) == 0 {
return nil, errors.New("The app_icons table requires that you specify a constraint WHERE path =")
}
path := q.Constraints[0].Expression
img, hash, err := getAppIcon(path, queryContext)
if err != nil {
return nil, err
}
var results []map[string]string
buf := new(bytes.Buffer)
img = resize.Resize(128, 128, img, resize.Bilinear)
if err := png.Encode(buf, img); err != nil {
return nil, err
}
results = append(results, map[string]string{
"path": path,
"icon": base64.StdEncoding.EncodeToString(buf.Bytes()),
"hash": fmt.Sprintf("%x", hash),
})
return results, nil
}
func getAppIcon(appPath string, queryContext table.QueryContext) (image.Image, uint64, error) {
var data C.CFDataRef
C.Icon(&data, C.CString(appPath))
defer C.CFRelease(C.CFTypeRef(data))
tiffBytes := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
img, err := tiff.Decode(bytes.NewBuffer(tiffBytes))
if err != nil {
return nil, 0, fmt.Errorf("decoding tiff bytes: %w", err)
}
checksum := crc64.Checksum(tiffBytes, crcTable)
return img, checksum, nil
}

View File

@ -0,0 +1,90 @@
package falcon_kernel_check
// based on github.com/kolide/launcher/pkg/osquery/tables
import (
"context"
"fmt"
"regexp"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
const kernelCheckUtilPath = "/opt/CrowdStrike/falcon-kernel-check"
type Table struct {
logger log.Logger
}
func TablePlugin(logger log.Logger) *table.Plugin {
columns := []table.ColumnDefinition{
table.TextColumn("kernel"),
table.IntegerColumn("supported"),
table.IntegerColumn("sensor_version"),
}
tableName := "falcon_kernel_check"
t := &Table{
logger: log.With(logger, "table", tableName),
}
return table.NewPlugin(tableName, columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
output, err := tablehelpers.Exec(ctx, t.logger, 5, []string{kernelCheckUtilPath}, []string{}, false)
if err != nil {
level.Info(t.logger).Log("msg", "exec failed", "err", err)
return nil, err
}
status, err := parseStatus(string(output))
if err != nil {
level.Info(t.logger).Log("msg", "Error parsing exec status", "err", err)
return nil, err
}
results := []map[string]string{status}
return results, nil
}
// Example falcon-kernel-check output:
// $ sudo /opt/CrowdStrike/falcon-kernel-check
// Host OS 5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022 is supported by Sensor version 14006.
// # Upgrade happens
// $ sudo /opt/CrowdStrike/falcon-kernel-check
// Host OS Linux 5.15.0-46-generic #49~20.04.1-Ubuntu SMP Thu Aug 4 19:15:44 UTC 2022 is not supported by Sensor version 14006.
//
// This regexp gets matches for the kernel string, supported status, and sensor version number
var kernelCheckRegexp = regexp.MustCompile(`^((?:Host OS (.*) (is supported|is not supported)))(?: by Sensor version (\d*))`)
func parseStatus(status string) (map[string]string, error) {
matches := kernelCheckRegexp.FindAllStringSubmatch(status, -1)
if len(matches) != 1 {
return nil, fmt.Errorf("Failed to match output: %s", status)
}
if len(matches[0]) != 5 {
return nil, fmt.Errorf("Got %d matches. Expected 5. Failed to match output: %s", len(matches[0]), status)
}
// matches[0][2] = kernel version string
// matches[0][3] = (is supported|is not supported)
// matches[0][4] = sensor version number
supported := "0"
if matches[0][3] == "is supported" {
supported = "1"
}
data := make(map[string]string, 3)
data["kernel"] = matches[0][2]
data["supported"] = supported
data["sensor_version"] = matches[0][4]
return data, nil
}

View File

@ -0,0 +1,78 @@
package falcon_kernel_check
// based on github.com/kolide/launcher/pkg/osquery/tables
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ParseStatusErrors(t *testing.T) {
t.Parallel()
tests := []struct {
name string
status string
expectedResult map[string]string
}{
{
name: "no status",
},
{
name: "bad output",
status: "\n\n\n\n",
},
{
name: "no supported string",
status: "Host OS 5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022 might be supported. idk lol.",
},
{
name: "no sensor version",
status: "Host OS 5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022 is supported",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := parseStatus(tt.status)
require.Error(t, err, "parseStatus")
})
}
}
func Test_ParseStatus(t *testing.T) {
t.Parallel()
tests := []struct {
name string
status string
expectedResult map[string]string
}{
{
name: "is supported",
status: "Host OS 5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022 is supported by Sensor version 14006.",
expectedResult: map[string]string{"kernel": "5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022", "supported": "1", "sensor_version": "14006"},
},
{
name: "is not supported",
status: "Host OS Linux 5.15.0-46-generic #49~20.04.1-Ubuntu SMP Thu Aug 4 19:15:44 UTC 2022 is not supported by Sensor version 14006.",
expectedResult: map[string]string{"kernel": "Linux 5.15.0-46-generic #49~20.04.1-Ubuntu SMP Thu Aug 4 19:15:44 UTC 2022", "supported": "0", "sensor_version": "14006"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
data, err := parseStatus(tt.status)
require.NoError(t, err, "parseStatus")
assert.Equal(t, tt.expectedResult, data)
})
}
}

View File

@ -0,0 +1,92 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package falconctl
import (
"bufio"
"fmt"
"io"
"strings"
)
// parseOptions parses the stdout returned from falconctl's displayed options. As far as we know, output is a single
// line, comma separated. We parse multiple lines, but assume data does not space that. Eg: linebreaks and commas
// treated as seperators.
func parseOptions(reader io.Reader) (any, error) {
results := make(map[string]interface{})
errors := make([]error, 0)
// rfm-reason, oddly, produces two KV pairs on a single line. We need to track the last key we saw, and
// append to that value.
var lastKey string
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
// sometimes lines end in , or ., remove them.
line = strings.TrimRight(line, ",.")
if line == "" {
continue
}
pairs := strings.Split(line, ", ")
for _, pair := range pairs {
pair = strings.TrimSpace(pair)
// The format is quite inconsistent. The following sample shows 4 possible
// outputs. We'll try to parse them all:
//
// cid="ac917ab****************************"
// aid is not set
// aph is not set
// app is not set
// rfm-state is not set
// rfm-reason is not set
// rfm-reason=None, code=0x0,
// feature is not set
// metadata-query=enable (unset default)
// version = 6.38.13501.0
// We see 4 different formats. We'll try to parse them all.
if strings.HasSuffix(pair, " is not set") {
// What should this be set to? nil? "is not set"? TBD!
results[pair[:len(pair)-len(" is not set")]] = "is not set"
continue
}
kv := strings.SplitN(pair, "=", 2)
if len(kv) == 2 {
// remove quotes and extra spaces
kv[0] = strings.Trim(kv[0], `" `)
kv[1] = strings.Trim(kv[1], `" `)
// Remove parenthetical note about an unset default
if strings.HasSuffix(kv[1], " (unset default)") {
kv[1] = kv[1][:len(kv[1])-len(" (unset default)")]
}
if lastKey == "rfm-reason" && kv[0] == "code" {
kv[0] = "rfm-reason-code"
}
if kv[0] == "tags" {
results[kv[0]] = strings.Split(kv[1], ",")
continue
}
results[kv[0]] = kv[1]
lastKey = kv[0]
continue
}
// Unknown format. Note the error
errors = append(errors, fmt.Errorf("unknown format: `%s` on line `%s`", pair, line))
}
}
if len(errors) > 0 {
return results, fmt.Errorf("errors parsing: %v", errors)
}
return results, nil
}

View File

@ -0,0 +1,166 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package falconctl
import (
"bytes"
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseOptions(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []byte
expected any
expectedErr bool
}{
{
name: "empty",
expected: map[string]any{},
},
{
name: "--cid",
input: []byte(`cid="REDACTED"`),
expected: map[string]any{"cid": "REDACTED"},
},
{
name: "--aid",
input: []byte(`aid="REDACTED"`),
expected: map[string]any{"aid": "REDACTED"},
},
{
name: "--apd",
input: []byte(`apd is not set,`),
expected: map[string]any{"apd": "is not set"},
},
{
name: "--aph",
input: []byte(`aph is not set,`),
expected: map[string]any{"aph": "is not set"},
},
{
name: "--app",
input: []byte(`app is not set,`),
expected: map[string]any{"app": "is not set"},
},
{
name: "--rfm-state",
input: []byte(`rfm-state=false,`),
expected: map[string]any{"rfm-state": "false"},
},
{
name: "--rfm-reason",
input: []byte(`rfm-reason=None, code=0x0,`),
expected: map[string]any{"rfm-reason": "None", "rfm-reason-code": "0x0"},
},
{
name: "--trace",
input: []byte(`trace is not set,`),
expected: map[string]any{"trace": "is not set"},
},
{
name: "--feature",
input: []byte(`feature= (hex bitmask: 0),`),
expected: map[string]any{"feature": "(hex bitmask: 0)"},
},
{
name: "--metadata-query",
input: []byte(`metadata-query=enable (unset default),`),
expected: map[string]any{"metadata-query": "enable"},
},
{
name: "--version",
input: []byte(`version = 6.45.14203.0,`),
expected: map[string]any{"version": "6.45.14203.0"},
},
{
name: "--billing",
input: []byte(`billing is not set,`),
expected: map[string]any{"billing": "is not set"},
},
// Tags are quite tricky to parse\
{
name: "--tags",
input: []byte(`tags=kolide-test-1,kolide-test-2,`),
expected: map[string]any{"tags": []string{"kolide-test-1", "kolide-test-2"}},
},
{
name: "--rfm-state --rfm-reason --aph --tags",
input: []byte("aph is not set, rfm-state=false, rfm-reason=None, code=0x0, tags=kolide-test-1,kolide-test-2."),
expected: map[string]any{
"aph": "is not set",
"rfm-reason": "None",
"rfm-reason-code": "0x0",
"rfm-state": "false",
"tags": []string{"kolide-test-1", "kolide-test-2"},
},
},
{
name: "-rfm-state --rfm-reason --aph --tags --version",
input: []byte("aph is not set, rfm-state=false, rfm-reason=None, code=0x0, version = 6.45.14203.0\ntags=kolide-test-1,kolide-test-2,"),
expected: map[string]any{
"aph": "is not set",
"rfm-reason": "None",
"rfm-reason-code": "0x0",
"rfm-state": "false",
"version": "6.45.14203.0",
"tags": []string{"kolide-test-1", "kolide-test-2"},
},
},
// something with a bunch of things
{
name: "normal",
input: readTestFile(t, path.Join("test-data", "options.txt")),
expected: map[string]any{
"aid": "is not set",
"aph": "is not set",
"app": "is not set",
"cid": "ac917ab****************************",
"feature": "is not set",
"metadata-query": "enable",
"rfm-reason": "is not set",
"rfm-state": "is not set",
"version": "6.38.13501.0",
},
},
{
name: "--rfm-state --rfm-reason --aph",
input: []byte("aph is not set, rfm-state=false, rfm-reason=None, code=0x0.\n"),
expected: map[string]any{"aph": "is not set", "rfm-reason": "None", "rfm-reason-code": "0x0", "rfm-state": "false"},
},
{
name: "cid not set",
input: readTestFile(t, path.Join("test-data", "cid-error.txt")),
expectedErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
actual, err := parseOptions(bytes.NewReader(tt.input))
if tt.expectedErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tt.expected, actual)
})
}
}
func readTestFile(t *testing.T, filepath string) []byte {
b, err := os.ReadFile(filepath)
require.NoError(t, err)
return b
}

View File

@ -0,0 +1,142 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package falconctl
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
var (
falconctlPaths = []string{"/opt/CrowdStrike/falconctl"}
// allowedOptions is the list of options this table is allowed to query. Notable exceptions
// are `systags` (which is parsed seperatedly) and `provisioning-token` (which is a secret).
allowedOptions = []string{
"--aid",
"--apd",
"--aph",
"--app",
"--cid",
"--feature",
"--metadata-query",
"--rfm-reason",
"--rfm-state",
"--tags",
"--version",
}
defaultOption = strings.Join(allowedOptions, " ")
)
type execFunc func(context.Context, log.Logger, int, []string, []string, bool) ([]byte, error)
type falconctlOptionsTable struct {
logger log.Logger
tableName string
execFunc execFunc
}
func NewFalconctlOptionTable(logger log.Logger) *table.Plugin {
columns := dataflattentable.Columns(
table.TextColumn("options"),
)
t := &falconctlOptionsTable{
logger: log.With(logger, "table", "falconctl_options"),
tableName: "falconctl_options",
execFunc: tablehelpers.Exec,
}
return table.NewPlugin(t.tableName, columns, t.generate)
}
func (t *falconctlOptionsTable) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
var results []map[string]string
// Note that we don't use tablehelpers.AllowedValues here, because that would disallow us from
// passing `where options = "--aid --aph"`, and allowing that, allows us a single exec.
OUTER:
for _, requested := range tablehelpers.GetConstraints(
queryContext,
"options",
tablehelpers.WithDefaults(defaultOption),
) {
options := strings.Split(requested, " ")
// Check that all requested options are allowed
for _, option := range options {
option = strings.Trim(option, " ")
if !optionAllowed(option) {
level.Info(t.logger).Log("msg", "requested option not allowed", "option", option)
continue OUTER
}
}
rowData := map[string]string{"options": requested}
// As I understand it the falconctl command line uses `-g` to indicate it's fetching the options settings, and
// then the list of options to fetch. Set the command line thusly.
args := append([]string{"-g"}, options...)
output, err := t.execFunc(ctx, t.logger, 30, falconctlPaths, args, false)
if err != nil {
level.Info(t.logger).Log("msg", "exec failed", "err", err)
synthesizedData := map[string]string{
"_error": fmt.Sprintf("falconctl parse failure: %s", err),
}
flattened, err := dataflatten.Flatten(synthesizedData)
if err != nil {
level.Info(t.logger).Log("msg", "failure flattening output", "err", err)
continue
}
results = append(results, dataflattentable.ToMap(flattened, "", rowData)...)
continue
}
parsed, err := parseOptions(bytes.NewReader(output))
if err != nil {
level.Info(t.logger).Log("msg", "parse failed", "err", err)
parsed = map[string]string{
"_error": fmt.Sprintf("falconctl parse failure: %s", err),
}
}
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithLogger(t.logger),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
flattened, err := dataflatten.Flatten(parsed, flattenOpts...)
if err != nil {
level.Info(t.logger).Log("msg", "failure flattening output", "err", err)
continue
}
results = append(results, dataflattentable.ToMap(flattened, dataQuery, rowData)...)
}
}
return results, nil
}
func optionAllowed(opt string) bool {
for _, b := range allowedOptions {
if b == opt {
return true
}
}
return false
}

View File

@ -0,0 +1,88 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package falconctl
import (
"bytes"
"context"
"strings"
"testing"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/stretchr/testify/require"
)
// TestOptionRestrictions tests that the table only allows the options we expect.
func TestOptionRestrictions(t *testing.T) {
t.Parallel()
tests := []struct {
name string
options []string
expectedExecs int
expectedDisallows int
}{
{
name: "default",
expectedExecs: 1,
expectedDisallows: 0,
},
{
name: "allowed options as array",
options: []string{"--aid", "--aph"},
expectedExecs: 2,
expectedDisallows: 0,
},
{
name: "allowed options as string",
options: []string{"--aid --aph"},
expectedExecs: 1,
expectedDisallows: 0,
},
{
name: "disallowed option as array",
options: []string{"--not-allowed", "--definitely-not-allowed", "--aid", "--aph"},
expectedExecs: 2,
expectedDisallows: 2,
},
{
name: "disallowed option as string",
options: []string{"--aid --aph --not-allowed"},
expectedExecs: 0,
expectedDisallows: 1,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var logBytes bytes.Buffer
testTable := &falconctlOptionsTable{
logger: log.NewLogfmtLogger(&logBytes),
execFunc: noopExec,
}
mockQC := tablehelpers.MockQueryContext(map[string][]string{
"options": tt.options,
})
_, err := testTable.generate(context.TODO(), mockQC)
require.NoError(t, err)
// test the number of times exec was called
require.Equal(t, tt.expectedExecs, strings.Count(logBytes.String(), "exec-in-test"))
// test the number of times we disallowed an option
require.Equal(t, tt.expectedDisallows, strings.Count(logBytes.String(), "requested option not allowed"))
})
}
}
func noopExec(_ context.Context, log log.Logger, _ int, _ []string, args []string, _ bool) ([]byte, error) {
log.Log("exec", "exec-in-test", "args", strings.Join(args, " "))
return []byte{}, nil
}

View File

@ -0,0 +1,2 @@
CID is not set. Use falconctl to set the CID
ERROR: failed to process the option --cid

View File

@ -0,0 +1 @@
cid="ac917ab****************************", aid is not set, aph is not set, app is not set, rfm-state is not set, rfm-reason is not set, feature is not set, metadata-query=enable (unset default), version = 6.38.13501.0

View File

@ -0,0 +1,108 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptoinfotable
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/cryptoinfo"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
type Table struct {
logger log.Logger
}
func TablePlugin(logger log.Logger) *table.Plugin {
columns := dataflattentable.Columns(
table.TextColumn("passphrase"),
table.TextColumn("path"),
)
t := &Table{
logger: logger,
}
return table.NewPlugin("cryptoinfo", columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
var results []map[string]string
requestedPaths := tablehelpers.GetConstraints(queryContext, "path")
if len(requestedPaths) == 0 {
return results, errors.New("The cryptoinfo table requires that you specify an equals constraint for path")
}
for _, requestedPath := range requestedPaths {
// We take globs in via the sql %, but glob needs *. So convert.
filePaths, err := filepath.Glob(strings.ReplaceAll(requestedPath, `%`, `*`))
if err != nil {
level.Info(t.logger).Log("msg", "bad file glob", "err", err)
continue
}
for _, filePath := range filePaths {
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
for _, passphrase := range tablehelpers.GetConstraints(queryContext, "passphrase", tablehelpers.WithDefaults("")) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithLogger(t.logger),
dataflatten.WithNestedPlist(),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
flatData, err := flattenCryptoInfo(filePath, passphrase, flattenOpts...)
if err != nil {
level.Info(t.logger).Log(
"msg", "failed to get data for path",
"path", filePath,
"err", err,
)
continue
}
rowData := map[string]string{
"path": filePath,
"passphrase": passphrase,
}
results = append(results, dataflattentable.ToMap(flatData, dataQuery, rowData)...)
}
}
}
}
return results, nil
}
// flattenCryptoInfo is a small wrapper over pkg/cryptoinfo that passes it off to dataflatten for table generation
func flattenCryptoInfo(filename, passphrase string, opts ...dataflatten.FlattenOpts) ([]dataflatten.Row, error) {
filebytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("reading %s: %w", filename, err)
}
result, err := cryptoinfo.Identify(filebytes, passphrase)
if err != nil {
return nil, fmt.Errorf("parsing with cryptoinfo: %w", err)
}
// convert to json, so it's parsable
jsonBytes, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("json: %w", err)
}
return dataflatten.Json(jsonBytes, opts...)
}

View File

@ -0,0 +1,91 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptsetup
import (
"bufio"
"bytes"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
// parseStatus parses the output from `cryptsetup status`. This is a
// pretty simple key, value format, but does have a free form first
// line. It's not clear if this is going to be stable, or change
// across versions.
func parseStatus(rawdata []byte) (map[string]interface{}, error) {
var data map[string]interface{}
if len(rawdata) == 0 {
return nil, errors.New("No data")
}
scanner := bufio.NewScanner(bytes.NewReader(rawdata))
firstLine := true
for scanner.Scan() {
line := scanner.Text()
if firstLine {
var err error
data, err = parseFirstLine(line)
if err != nil {
return nil, err
}
firstLine = false
continue
}
kv := strings.SplitN(line, ": ", 2)
// blank lines, or other unexpected input can just be skipped.
if len(kv) < 2 {
continue
}
data[strings.ReplaceAll(strings.TrimSpace(kv[0]), " ", "_")] = strings.TrimSpace(kv[1])
}
return data, nil
}
// regexp for the first line of the status output.
var firstLineRegexp = regexp.MustCompile(`^(?:Device (.*) (not found))|(?:(.*?) is ([a-z]+)(?:\.| and is (in use)))`)
// parseFirstLine parses the first line of the status output. This
// appears to be a free form string indicating several pieces of
// information. It is parsed with a single regexp. (See tests for
// examples)
func parseFirstLine(line string) (map[string]interface{}, error) {
if line == "" {
return nil, errors.New("Invalid first line")
}
m := firstLineRegexp.FindAllStringSubmatch(line, -1)
if len(m) != 1 {
return nil, fmt.Errorf("Failed to match first line: %s", line)
}
if len(m[0]) != 6 {
return nil, fmt.Errorf("Got %d matches. Expected 6. Failed to match first line: %s", len(m[0]), line)
}
data := make(map[string]interface{}, 3)
// check for $1 and $2 for the error condition
if m[0][1] != "" && m[0][2] != "" {
data["short_name"] = m[0][1]
data["status"] = strings.ReplaceAll(m[0][2], " ", "_")
data["mounted"] = strconv.FormatBool(false)
return data, nil
}
if m[0][3] != "" && m[0][4] != "" {
data["display_name"] = m[0][3]
data["status"] = strings.ReplaceAll(m[0][4], " ", "_")
data["mounted"] = strconv.FormatBool(m[0][5] != "")
return data, nil
}
return nil, fmt.Errorf("Unknown first line: %s", line)
}

View File

@ -0,0 +1,135 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptsetup
import (
"os"
"path/filepath"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseStatusErrors(t *testing.T) {
t.Parallel()
tests := []struct {
input string
}{
{
input: "",
},
{
input: "\n\n\n\n",
},
{
input: "type: LUKS2",
},
{
input: "Hello world",
},
}
for _, tt := range tests {
tt := tt
t.Run("", func(t *testing.T) {
t.Parallel()
data, err := parseStatus([]byte(tt.input))
assert.Error(t, err, "parseStatus")
assert.Nil(t, data, "data is nil")
})
}
}
func TestParseStatus(t *testing.T) {
t.Parallel()
tests := []struct {
infile string
len int
status string
mounted bool
ctype string
keysize string
key_location string
}{
{
infile: "status-active-luks1.txt",
status: "active",
mounted: true,
ctype: "LUKS1",
keysize: "512 bits",
key_location: "dm-crypt",
},
{
infile: "status-active-luks2.txt",
status: "active",
mounted: true,
ctype: "LUKS2",
keysize: "512 bits",
key_location: "keyring",
},
{
infile: "status-active-mounted.txt",
status: "active",
mounted: true,
ctype: "PLAIN",
keysize: "256 bits",
key_location: "dm-crypt",
},
{
infile: "status-active-umounted.txt",
status: "active",
ctype: "PLAIN",
keysize: "256 bits",
key_location: "dm-crypt",
},
{
infile: "status-active.txt",
status: "active",
mounted: true,
ctype: "PLAIN",
keysize: "256 bits",
key_location: "dm-crypt",
},
{
infile: "status-error.txt",
status: "not_found",
},
{
infile: "status-inactive.txt",
status: "inactive",
},
{
infile: "status-unactive.txt",
status: "inactive",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.infile, func(t *testing.T) {
t.Parallel()
input, err := os.ReadFile(filepath.Join("testdata", tt.infile))
require.NoError(t, err, "read input file")
data, err := parseStatus(input)
require.NoError(t, err, "parseStatus")
assert.Equal(t, tt.status, data["status"], "status")
assert.Equal(t, strconv.FormatBool(tt.mounted), data["mounted"], "mounted")
// These values aren't populated in the map,
// so only check them if the test case lists
// them
if tt.ctype != "" {
assert.Equal(t, tt.ctype, data["type"], "type")
assert.Equal(t, tt.keysize, data["keysize"], "keysize")
assert.Equal(t, tt.key_location, data["key_location"], "key_location")
}
})
}
}

View File

@ -0,0 +1,90 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package cryptsetup
import (
"context"
"fmt"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
var cryptsetupPaths = []string{
"/usr/sbin/cryptsetup",
"/sbin/cryptsetup",
}
const allowedNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/_"
type Table struct {
logger log.Logger
name string
}
func TablePlugin(logger log.Logger) *table.Plugin {
columns := dataflattentable.Columns(
table.TextColumn("name"),
)
t := &Table{
logger: logger,
name: "cryptsetup_status",
}
return table.NewPlugin(t.name, columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
var results []map[string]string
requestedNames := tablehelpers.GetConstraints(queryContext, "name",
tablehelpers.WithAllowedCharacters(allowedNameCharacters),
tablehelpers.WithLogger(t.logger),
)
if len(requestedNames) == 0 {
return results, fmt.Errorf("The %s table requires that you specify a constraint for name", t.name)
}
for _, name := range requestedNames {
output, err := tablehelpers.Exec(ctx, t.logger, 15, cryptsetupPaths, []string{"--readonly", "status", name}, false)
if err != nil {
level.Debug(t.logger).Log("msg", "Error execing for status", "name", name, "err", err)
continue
}
status, err := parseStatus(output)
if err != nil {
level.Info(t.logger).Log("msg", "Error parsing status", "name", name, "err", err)
continue
}
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
flatData, err := t.flattenOutput(dataQuery, status)
if err != nil {
level.Info(t.logger).Log("msg", "flatten failed", "err", err)
continue
}
rowData := map[string]string{"name": name}
results = append(results, dataflattentable.ToMap(flatData, dataQuery, rowData)...)
}
}
return results, nil
}
func (t *Table) flattenOutput(dataQuery string, status map[string]interface{}) ([]dataflatten.Row, error) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithLogger(t.logger),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
return dataflatten.Flatten(status, flattenOpts...)
}

View File

@ -0,0 +1,11 @@
/dev/mapper/dm-crypto-luks1 is active and is in use.
type: LUKS1
cipher: aes-xts-plain64
keysize: 512 bits
key location: dm-crypt
device: /dev/sdc1
sector size: 512
offset: 4096 sectors
size: 8382464 sectors
mode: read/write

View File

@ -0,0 +1,10 @@
/dev/mapper/dm-crypto-luks2 is active and is in use.
type: LUKS2
cipher: aes-xts-plain64
keysize: 512 bits
key location: keyring
device: /dev/sdc2
sector size: 512
offset: 32768 sectors
size: 8355840 sectors
mode: read/write

View File

@ -0,0 +1,10 @@
/dev/mapper/dm-crypto-plain is active and is in use.
type: PLAIN
cipher: aes-cbc-essiv:sha256
keysize: 256 bits
key location: dm-crypt
device: /dev/sdc3
sector size: 512
offset: 0 sectors
size: 8388608 sectors
mode: read/write

View File

@ -0,0 +1,10 @@
/dev/mapper/dm-crypto-plain is active.
type: PLAIN
cipher: aes-cbc-essiv:sha256
keysize: 256 bits
key location: dm-crypt
device: /dev/sdc3
sector size: 512
offset: 0 sectors
size: 8388608 sectors
mode: read/write

View File

@ -0,0 +1,10 @@
/dev/mapper/dm-crypto-plain is active and is in use.
type: PLAIN
cipher: aes-cbc-essiv:sha256
keysize: 256 bits
key location: dm-crypt
device: /dev/sdc3
sector size: 512
offset: 0 sectors
size: 8388608 sectors
mode: read/write

View File

@ -0,0 +1 @@
Device sdc3 not found

View File

@ -0,0 +1 @@
/dev/mapper/dm-crypto-plain is inactive.

View File

@ -0,0 +1 @@
/dev/mapper/dm-crypto-unknown is inactive.

View File

@ -0,0 +1,139 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflattentable
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
type ExecTableOpt func(*Table)
// WithKVSeparator sets the delimiter between key and value. It replaces the
// default ":" in dataflattentable.Table
func WithKVSeparator(separator string) ExecTableOpt {
return func(t *Table) {
t.keyValueSeparator = separator
}
}
func WithBinDirs(binDirs ...string) ExecTableOpt {
return func(t *Table) {
t.binDirs = binDirs
}
}
func TablePluginExec(logger log.Logger, tableName string, dataSourceType DataSourceType, execArgs []string, opts ...ExecTableOpt) *table.Plugin {
columns := Columns()
t := &Table{
logger: level.NewFilter(logger, level.AllowInfo()),
tableName: tableName,
execArgs: execArgs,
keyValueSeparator: ":",
}
for _, opt := range opts {
opt(t)
}
switch dataSourceType {
case PlistType:
t.flattenBytesFunc = dataflatten.Plist
case JsonType:
t.flattenBytesFunc = dataflatten.Json
case KeyValueType:
// TODO: allow callers of TablePluginExec to specify the record
// splitting strategy
t.flattenBytesFunc = dataflatten.StringDelimitedFunc(t.keyValueSeparator, dataflatten.DuplicateKeys)
default:
panic("Unknown data source type")
}
return table.NewPlugin(t.tableName, columns, t.generateExec)
}
func (t *Table) generateExec(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
var results []map[string]string
execBytes, err := t.exec(ctx)
if err != nil {
// exec will error if there's no binary, so we never want to record that
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
// If the exec failed for some reason, it's probably better to return no results, and log the,
// error. Returning an error here will cause a table failure, and thus break joins
level.Info(t.logger).Log("msg", "failed to exec", "err", err)
return nil, nil
}
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithLogger(t.logger),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
flattened, err := t.flattenBytesFunc(execBytes, flattenOpts...)
if err != nil {
level.Info(t.logger).Log("msg", "failure flattening output", "err", err)
continue
}
results = append(results, ToMap(flattened, dataQuery, nil)...)
}
return results, nil
}
func (t *Table) exec(ctx context.Context) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 50*time.Second)
defer cancel()
possibleBinaries := []string{}
if t.binDirs == nil || len(t.binDirs) == 0 {
possibleBinaries = []string{t.execArgs[0]}
} else {
for _, possiblePath := range t.binDirs {
possibleBinaries = append(possibleBinaries, filepath.Join(possiblePath, t.execArgs[0]))
}
}
for _, execPath := range possibleBinaries {
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.CommandContext(ctx, execPath, t.execArgs[1:]...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
level.Debug(t.logger).Log("msg", "calling %s", "args", cmd.String())
if err := cmd.Run(); os.IsNotExist(err) {
// try the next binary
continue
} else if err != nil {
return nil, fmt.Errorf("calling %s. Got: %s: %w", t.execArgs[0], string(stderr.Bytes()), err)
}
// success!
return stdout.Bytes(), nil
}
// None of the possible execs were found
return nil, fmt.Errorf("Unable to exec '%s'. No binary found is specified paths", t.execArgs[0])
}

View File

@ -0,0 +1,47 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflattentable
import (
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/osquery/osquery-go/plugin/table"
)
// ToMap is a helper function to convert Flatten output directly for
// consumption by osquery tables.
func ToMap(rows []dataflatten.Row, query string, rowData map[string]string) []map[string]string {
results := make([]map[string]string, len(rows))
for i, row := range rows {
res := make(map[string]string, len(rowData)+5)
for k, v := range rowData {
res[k] = v
}
p, k := row.ParentKey("/")
res["fullkey"] = row.StringPath("/")
res["parent"] = p
res["key"] = k
res["value"] = row.Value
res["query"] = query
results[i] = res
}
return results
}
// Columns returns the standard data flatten columns, plus whatever
// ones have been provided as additional. This is syntantic sugar for
// dataflatten based tables.
func Columns(additional ...table.ColumnDefinition) []table.ColumnDefinition {
columns := []table.ColumnDefinition{
table.TextColumn("fullkey"),
table.TextColumn("parent"),
table.TextColumn("key"),
table.TextColumn("value"),
table.TextColumn("query"),
}
return append(columns, additional...)
}

View File

@ -0,0 +1,81 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflattentable
import (
"context"
"path/filepath"
"sort"
"testing"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/stretchr/testify/require"
)
// TestPlist runs some real-world tests against sample plist data.
func TestPlist(t *testing.T) {
t.Parallel()
plistTable := Table{flattenFileFunc: dataflatten.PlistFile}
tests := []struct {
paths []string
queries []string
expected []map[string]string
err bool
}{
{
err: true,
},
{
paths: []string{filepath.Join("testdata", "NetworkInterfaces.plist")},
queries: []string{"Interfaces/#BSD Name/SCNetworkInterfaceType/FireWire"},
expected: []map[string]string{
{
"fullkey": "Interfaces/fw0/SCNetworkInterfaceType",
"key": "SCNetworkInterfaceType",
"parent": "Interfaces/fw0",
"value": "FireWire",
},
},
},
{
paths: []string{filepath.Join("testdata", "com.apple.launchservices.secure.plist")},
queries: []string{
"LSHandlers/LSHandlerURLScheme=>htt*/LSHandlerRole*",
"LSHandlers/LSHandlerContentType=>*html/LSHandlerRole*",
},
expected: []map[string]string{
{"fullkey": "LSHandlers/5/LSHandlerRoleAll", "key": "LSHandlerRoleAll", "parent": "LSHandlers/5", "value": "com.choosyosx.choosy"},
{"fullkey": "LSHandlers/6/LSHandlerRoleAll", "key": "LSHandlerRoleAll", "parent": "LSHandlers/6", "value": "com.choosyosx.choosy"},
{"fullkey": "LSHandlers/7/LSHandlerRoleAll", "key": "LSHandlerRoleAll", "parent": "LSHandlers/7", "value": "com.choosyosx.choosy"},
{"fullkey": "LSHandlers/8/LSHandlerRoleAll", "key": "LSHandlerRoleAll", "parent": "LSHandlers/8", "value": "com.google.chrome"},
},
},
}
for _, tt := range tests {
mockQC := tablehelpers.MockQueryContext(map[string][]string{
"path": tt.paths,
"query": tt.queries,
})
rows, err := plistTable.generate(context.TODO(), mockQC)
if tt.err {
require.Error(t, err)
continue
}
require.NoError(t, err)
// delete the path and query keys, so we don't need to enumerate them in the test case
for _, row := range rows {
delete(row, "path")
delete(row, "query")
}
// Despite being an array. data is returned unordered. Sort it.
sort.SliceStable(tt.expected, func(i, j int) bool { return tt.expected[i]["fullkey"] < tt.expected[j]["fullkey"] })
sort.SliceStable(rows, func(i, j int) bool { return rows[i]["fullkey"] < rows[j]["fullkey"] })
require.EqualValues(t, tt.expected, rows)
}
}

View File

@ -0,0 +1,137 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflattentable
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
)
type DataSourceType int
const (
PlistType DataSourceType = iota + 1
JsonType
JsonlType
ExecType
XmlType
IniType
KeyValueType
)
type Table struct {
logger log.Logger
tableName string
flattenFileFunc func(string, ...dataflatten.FlattenOpts) ([]dataflatten.Row, error)
flattenBytesFunc func([]byte, ...dataflatten.FlattenOpts) ([]dataflatten.Row, error)
execArgs []string
binDirs []string
keyValueSeparator string
}
// AllTablePlugins is a helper to return all the expected flattening tables.
func AllTablePlugins(logger log.Logger) []osquery.OsqueryPlugin {
return []osquery.OsqueryPlugin{
TablePlugin(logger, JsonType),
TablePlugin(logger, XmlType),
TablePlugin(logger, IniType),
TablePlugin(logger, PlistType),
TablePlugin(logger, JsonlType),
}
}
func TablePlugin(logger log.Logger, dataSourceType DataSourceType) osquery.OsqueryPlugin {
columns := Columns(table.TextColumn("path"))
t := &Table{
logger: logger,
}
switch dataSourceType {
case PlistType:
t.flattenFileFunc = dataflatten.PlistFile
t.tableName = "parse_plist"
case JsonType:
t.flattenFileFunc = dataflatten.JsonFile
t.tableName = "parse_json"
case JsonlType:
t.flattenFileFunc = dataflatten.JsonlFile
t.tableName = "parse_jsonl"
case XmlType:
t.flattenFileFunc = dataflatten.XmlFile
t.tableName = "parse_xml"
case IniType:
t.flattenFileFunc = dataflatten.IniFile
t.tableName = "parse_ini"
default:
panic("Unknown data source type")
}
return table.NewPlugin(t.tableName, columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
var results []map[string]string
requestedPaths := tablehelpers.GetConstraints(queryContext, "path")
if len(requestedPaths) == 0 {
return results, fmt.Errorf("The %s table requires that you specify a single constraint for path", t.tableName)
}
for _, requestedPath := range requestedPaths {
// We take globs in via the sql %, but glob needs *. So convert.
filePaths, err := filepath.Glob(strings.ReplaceAll(requestedPath, `%`, `*`))
if err != nil {
return results, fmt.Errorf("bad glob: %w", err)
}
for _, filePath := range filePaths {
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
subresults, err := t.generatePath(filePath, dataQuery)
if err != nil {
level.Info(t.logger).Log(
"msg", "failed to get data for path",
"path", filePath,
"err", err,
)
continue
}
results = append(results, subresults...)
}
}
}
return results, nil
}
func (t *Table) generatePath(filePath string, dataQuery string) ([]map[string]string, error) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithLogger(t.logger),
dataflatten.WithNestedPlist(),
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
data, err := t.flattenFileFunc(filePath, flattenOpts...)
if err != nil {
level.Info(t.logger).Log("msg", "failure parsing file", "file", filePath)
return nil, fmt.Errorf("parsing data: %w", err)
}
rowData := map[string]string{
"path": filePath,
}
return ToMap(data, dataQuery, rowData), nil
}

View File

@ -0,0 +1,160 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package dataflattentable
import (
"context"
"fmt"
"path"
"path/filepath"
"sort"
"testing"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/stretchr/testify/require"
)
// TestDataFlattenTable_Animals tests the basic generation
// functionality for both plist and json parsing using the mock
// animals data.
func TestDataFlattenTablePlist_Animals(t *testing.T) {
t.Parallel()
logger := log.NewNopLogger()
// Test plist parsing both the json and xml forms
testTables := map[string]Table{
"plist": {logger: logger, flattenFileFunc: dataflatten.PlistFile},
"xml": {logger: logger, flattenFileFunc: dataflatten.PlistFile},
"json": {logger: logger, flattenFileFunc: dataflatten.JsonFile},
}
tests := []struct {
queries []string
expected []map[string]string
}{
{
queries: []string{
"metadata",
},
expected: []map[string]string{
{"fullkey": "metadata/testing", "key": "testing", "parent": "metadata", "value": "true"},
{"fullkey": "metadata/version", "key": "version", "parent": "metadata", "value": "1.0.1"},
},
},
{
queries: []string{
"users/name=>*Aardvark/id",
"users/name=>*Chipmunk/id",
},
expected: []map[string]string{
{"fullkey": "users/0/id", "key": "id", "parent": "users/0", "value": "1"},
{"fullkey": "users/2/id", "key": "id", "parent": "users/2", "value": "3"},
},
},
}
for _, tt := range tests {
for dataType, tableFunc := range testTables {
testFile := filepath.Join("testdata", "animals."+dataType)
mockQC := tablehelpers.MockQueryContext(map[string][]string{
"path": {testFile},
"query": tt.queries,
})
rows, err := tableFunc.generate(context.TODO(), mockQC)
require.NoError(t, err)
// delete the path and query keys, so we don't need to enumerate them in the test case
for _, row := range rows {
delete(row, "path")
delete(row, "query")
}
// Despite being an array. data is returned unordered. Sort it.
sort.SliceStable(tt.expected, func(i, j int) bool { return tt.expected[i]["fullkey"] < tt.expected[j]["fullkey"] })
sort.SliceStable(rows, func(i, j int) bool { return rows[i]["fullkey"] < rows[j]["fullkey"] })
require.EqualValues(t, tt.expected, rows, "table type %s test", dataType)
}
}
}
func TestDataFlattenTables(t *testing.T) {
t.Parallel()
logger := log.NewNopLogger()
tests := []struct {
testTables map[string]Table
testFile string
queries []string
expectedRows int
expectNoData bool
}{
// xml
{
testTables: map[string]Table{"xml": {logger: logger, flattenFileFunc: dataflatten.XmlFile}},
testFile: path.Join("testdata", "simple.xml"),
expectedRows: 6,
},
{
testTables: map[string]Table{"xml": {logger: logger, flattenFileFunc: dataflatten.XmlFile}},
testFile: path.Join("testdata", "simple.xml"),
queries: []string{"simple/Items"},
expectedRows: 3,
},
{
testTables: map[string]Table{"xml": {logger: logger, flattenFileFunc: dataflatten.XmlFile}},
testFile: path.Join("testdata", "simple.xml"),
queries: []string{"this/does/not/exist"},
expectNoData: true,
},
// ini
{
testTables: map[string]Table{"ini": {logger: logger, flattenFileFunc: dataflatten.IniFile}},
testFile: path.Join("testdata", "secdata.ini"),
expectedRows: 87,
},
{
testTables: map[string]Table{"ini": {logger: logger, flattenFileFunc: dataflatten.IniFile}},
testFile: path.Join("testdata", "secdata.ini"),
queries: []string{"Registry Values"},
expectedRows: 59,
},
{
testTables: map[string]Table{"ini": {logger: logger, flattenFileFunc: dataflatten.IniFile}},
testFile: path.Join("testdata", "secdata.ini"),
queries: []string{"this/does/not/exist"},
expectNoData: true,
},
}
for testN, tt := range tests {
tt := tt
for tableName, testTable := range tt.testTables {
tableName, testTable := tableName, testTable
t.Run(fmt.Sprintf("%d/%s", testN, tableName), func(t *testing.T) {
t.Parallel()
mockQC := tablehelpers.MockQueryContext(map[string][]string{
"path": {tt.testFile},
"query": tt.queries,
})
rows, err := testTable.generate(context.TODO(), mockQC)
require.NoError(t, err)
if tt.expectNoData {
require.Len(t, rows, 0)
} else {
require.Len(t, rows, tt.expectedRows)
}
})
}
}
}

View File

@ -0,0 +1,277 @@
<?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>Interfaces</key>
<array>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en0</string>
<key>IOBuiltin</key>
<true/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>0</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Wi-Fi</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>IEEE80211</string>
</dict>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en1</string>
<key>IOBuiltin</key>
<true/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>1</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Thunderbolt 1</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en2</string>
<key>IOBuiltin</key>
<true/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>2</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Thunderbolt 2</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en3</string>
<key>IOBuiltin</key>
<true/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>3</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Thunderbolt 3</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en4</string>
<key>IOBuiltin</key>
<true/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>4</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Thunderbolt 4</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en5</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>5</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>USB Product Name</key>
<string>iBridge</string>
<key>UserDefinedName</key>
<string>iBridge</string>
<key>idProduct</key>
<integer>34304</integer>
<key>idVendor</key>
<integer>1452</integer>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>BSD Name</key>
<string>en6</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>6</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Bluetooth PAN</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>BSD Name</key>
<string>en7</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>7</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>USB Product Name</key>
<string>Belkin USB_C LAN</string>
<key>UserDefinedName</key>
<string>Belkin USB-C LAN</string>
<key>idProduct</key>
<integer>33107</integer>
<key>idVendor</key>
<integer>3034</integer>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>Active</key>
<true/>
<key>BSD Name</key>
<string>en8</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>8</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>USB Product Name</key>
<string>USB 10_100_1000 LAN</string>
<key>UserDefinedName</key>
<string>USB 10/100/1000 LAN</string>
<key>idProduct</key>
<integer>33107</integer>
<key>idVendor</key>
<integer>3034</integer>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>BSD Name</key>
<string>en9</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>9</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>USB Product Name</key>
<string>iPhone</string>
<key>UserDefinedName</key>
<string>iPhone</string>
<key>idProduct</key>
<integer>4776</integer>
<key>idVendor</key>
<integer>1452</integer>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>BSD Name</key>
<string>en10</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>en</string>
<key>IOInterfaceType</key>
<integer>6</integer>
<key>IOInterfaceUnit</key>
<integer>10</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Display Ethernet</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>Ethernet</string>
</dict>
<dict>
<key>BSD Name</key>
<string>fw0</string>
<key>IOBuiltin</key>
<false/>
<key>IOInterfaceNamePrefix</key>
<string>fw</string>
<key>IOInterfaceType</key>
<integer>144</integer>
<key>IOInterfaceUnit</key>
<integer>0</integer>
<key>SCNetworkInterfaceInfo</key>
<dict>
<key>UserDefinedName</key>
<string>Display FireWire</string>
</dict>
<key>SCNetworkInterfaceType</key>
<string>FireWire</string>
</dict>
</array>
<key>Model</key>
<string>MacBookPro14,3</string>
</dict>
</plist>

View File

@ -0,0 +1,34 @@
{
"metadata": {
"testing": true,
"version": "1.0.1"
},
"system": "users demo",
"users": [
{
"favorites": [
"ants"
],
"uuid": "abc123",
"name": "Alex Aardvark",
"id": 1
},
{
"favorites": [
"mice",
"birds"
],
"uuid": "def456",
"name": "Bailey Bobcat",
"id": 2
},
{
"favorites": [
"seeds"
],
"uuid": "ghi789",
"name": "Cam Chipmunk",
"id": 3
}
]
}

Binary file not shown.

View File

@ -0,0 +1,55 @@
<?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>metadata</key>
<dict>
<key>testing</key>
<true/>
<key>version</key>
<string>1.0.1</string>
</dict>
<key>system</key>
<string>users demo</string>
<key>users</key>
<array>
<dict>
<key>favorites</key>
<array>
<string>ants</string>
</array>
<key>id</key>
<integer>1</integer>
<key>name</key>
<string>Alex Aardvark</string>
<key>uuid</key>
<string>abc123</string>
</dict>
<dict>
<key>favorites</key>
<array>
<string>mice</string>
<string>birds</string>
</array>
<key>id</key>
<integer>2</integer>
<key>name</key>
<string>Bailey Bobcat</string>
<key>uuid</key>
<string>def456</string>
</dict>
<dict>
<key>favorites</key>
<array>
<string>seeds</string>
</array>
<key>id</key>
<integer>3</integer>
<key>name</key>
<string>Cam Chipmunk</string>
<key>uuid</key>
<string>ghi789</string>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,295 @@
<?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>LSHandlers</key>
<array>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>xcbot</string>
</dict>
<dict>
<key>LSHandlerContentType</key>
<string>public.svg-image</string>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>org.mozilla.firefox</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.logmein.mac.gotoopener</string>
<key>LSHandlerURLScheme</key>
<string>citrixonline488</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>keybase.electron</string>
<key>LSHandlerURLScheme</key>
<string>web+stellar</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.facetime</string>
<key>LSHandlerURLScheme</key>
<string>facetime</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.choosyosx.choosy</string>
<key>LSHandlerURLScheme</key>
<string>http</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.choosyosx.choosy</string>
<key>LSHandlerURLScheme</key>
<string>https</string>
</dict>
<dict>
<key>LSHandlerContentType</key>
<string>public.html</string>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.choosyosx.choosy</string>
</dict>
<dict>
<key>LSHandlerContentType</key>
<string>public.xhtml</string>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.google.chrome</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>org.whispersystems.signal-desktop</string>
<key>LSHandlerURLScheme</key>
<string>sgnl</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.choosyosx.choosy</string>
<key>LSHandlerURLScheme</key>
<string>x-choosy</string>
</dict>
<dict>
<key>LSHandlerContentType</key>
<string>com.apple.installer-package</string>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.installer</string>
</dict>
<dict>
<key>LSHandlerContentType</key>
<string>com.apple.installer-meta-package</string>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.installer</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.bluejeans.nw.app</string>
<key>LSHandlerURLScheme</key>
<string>bjn</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>xcpref</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>xcdevice</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>xcdoc</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>apple-reference-documentation</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.logmein.mac.gotoopener</string>
<key>LSHandlerURLScheme</key>
<string>gotoopener</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>xcode</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.logmein.mac.gotoopener</string>
<key>LSHandlerURLScheme</key>
<string>citrixonline</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.google.chrome</string>
<key>LSHandlerURLScheme</key>
<string>webcal</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>keybase.electron</string>
<key>LSHandlerURLScheme</key>
<string>keybase</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.apple.dt.xcode</string>
<key>LSHandlerURLScheme</key>
<string>x-source-tag</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>com.logmein.mac.gotoopener</string>
<key>LSHandlerURLScheme</key>
<string>gotoopener488</string>
</dict>
<dict>
<key>LSHandlerPreferredVersions</key>
<dict>
<key>LSHandlerRoleAll</key>
<string>-</string>
</dict>
<key>LSHandlerRoleAll</key>
<string>us.zoom.xos</string>
<key>LSHandlerURLScheme</key>
<string>zoomphonecall</string>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,92 @@
[Unicode]
Unicode=yes
[System Access]
MinimumPasswordAge = 0
MaximumPasswordAge = 42
MinimumPasswordLength = 0
PasswordComplexity = 1
PasswordHistorySize = 0
LockoutBadCount = 1
ResetLockoutCount = 30
LockoutDuration = 30
RequireLogonToChangePassword = 0
ForceLogoffWhenHourExpire = 0
NewAdministratorName = "Administrator"
NewGuestName = "Guest"
ClearTextPassword = 0
LSAAnonymousNameLookup = 0
EnableAdminAccount = 0
EnableGuestAccount = 0
[Event Audit]
AuditSystemEvents = 0
AuditLogonEvents = 0
AuditObjectAccess = 0
AuditPrivilegeUse = 0
AuditPolicyChange = 0
AuditAccountManage = 0
AuditProcessTracking = 0
AuditDSAccess = 0
AuditAccountLogon = 0
[Registry Values]
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Setup\RecoveryConsole\SecurityLevel=4,0
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Setup\RecoveryConsole\SetCommand=4,0
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\CachedLogonsCount=1,"10"
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ForceUnlockLogon=4,0
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\PasswordExpiryWarning=4,5
MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ScRemoveOption=1,"0"
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorAdmin=4,5
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorUser=4,3
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLastUserName=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableInstallerDetection=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableSecureUIAPaths=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableUIADesktopToggle=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableVirtualization=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters\SupportedEncryptionTypes=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\LegalNoticeCaption=1,""
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\LegalNoticeText=7,
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\PromptOnSecureDesktop=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ScForceOption=4,0
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ShutdownWithoutLogon=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\UndockWithoutLogon=4,1
MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ValidateAdminCodeSignatures=4,0
MACHINE\Software\Policies\Microsoft\Windows\Safer\CodeIdentifiers\AuthenticodeEnabled=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\AuditBaseObjects=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\CrashOnAuditFail=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\DisableDomainCreds=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\EveryoneIncludesAnonymous=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy\Enabled=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\ForceGuest=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\FullPrivilegeAuditing=3,0
MACHINE\System\CurrentControlSet\Control\Lsa\LimitBlankPasswordUse=4,1
MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinClientSec=4,536870912
MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinServerSec=4,536870912
MACHINE\System\CurrentControlSet\Control\Lsa\NoLMHash=4,1
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymous=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymousSAM=4,1
MACHINE\System\CurrentControlSet\Control\Print\Providers\LanMan Print Services\Servers\AddPrinterDrivers=4,0
MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactPaths\Machine=7,System\CurrentControlSet\Control\ProductOptions,System\CurrentControlSet\Control\Server Applications,Software\Microsoft\Windows NT\CurrentVersion
MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedPaths\Machine=7,System\CurrentControlSet\Control\Print\Printers,System\CurrentControlSet\Services\Eventlog,Software\Microsoft\OLAP Server,Software\Microsoft\Windows NT\CurrentVersion\Print,Software\Microsoft\Windows NT\CurrentVersion\Windows,System\CurrentControlSet\Control\ContentIndex,System\CurrentControlSet\Control\Terminal Server,System\CurrentControlSet\Control\Terminal Server\UserConfig,System\CurrentControlSet\Control\Terminal Server\DefaultUserConfiguration,Software\Microsoft\Windows NT\CurrentVersion\Perflib,System\CurrentControlSet\Services\SysmonLog
MACHINE\System\CurrentControlSet\Control\Session Manager\Kernel\ObCaseInsensitive=4,1
MACHINE\System\CurrentControlSet\Control\Session Manager\Memory Management\ClearPageFileAtShutdown=4,0
MACHINE\System\CurrentControlSet\Control\Session Manager\ProtectionMode=4,1
MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\optional=7,
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\AutoDisconnect=4,15
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\EnableForcedLogOff=4,1
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\EnableSecuritySignature=4,0
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\NullSessionPipes=7,
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RequireSecuritySignature=4,0
MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RestrictNullSessAccess=4,1
MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\EnablePlainTextPassword=4,0
MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\EnableSecuritySignature=4,1
MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\RequireSecuritySignature=4,0
MACHINE\System\CurrentControlSet\Services\LDAP\LDAPClientIntegrity=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\DisablePasswordChange=4,0
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\MaximumPasswordAge=4,30
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RequireSignOrSeal=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RequireStrongKey=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\SealSecureChannel=4,1
MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\SignSecureChannel=4,1
[Version]
signature="$CHICAGO$"
Revision=1

View File

@ -0,0 +1,12 @@
<simple>
<Items>
<item1>One</item1>
<item2>Two</item2>
<item3>Three</item3>
</Items>
<Animals>
<Cat/>
<Dog/>
<Mouse/>
</Animals>
</simple>

View File

@ -8,12 +8,17 @@ import (
"sync"
"time"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/cryptoinfotable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/firefox_preferences"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/sntp_request"
"github.com/macadmins/osquery-extension/tables/chromeuserprofiles"
"github.com/macadmins/osquery-extension/tables/fileline"
"github.com/macadmins/osquery-extension/tables/puppet"
"github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
"github.com/rs/zerolog/log"
)
@ -32,7 +37,7 @@ type Runner struct {
type Extension interface {
// Name returns the name of the table.
Name() string
// Column returns the definition of the table columns.
// Columns returns the definition of the table columns.
Columns() []table.ColumnDefinition
// GenerateFunc generates results for a query.
GenerateFunc(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error)
@ -41,6 +46,9 @@ type Extension interface {
// Opt allows configuring a Runner.
type Opt func(*Runner)
// Logger for osquery tables
var osqueryLogger *Logger
// WithExtension registers the given Extension on the Runner.
func WithExtension(t Extension) Opt {
return func(r *Runner) {
@ -61,6 +69,8 @@ func NewRunner(socket string, opts ...Opt) *Runner {
func (r *Runner) Execute() error {
log.Debug().Msg("start osquery extension")
osqueryLogger = NewOsqueryLogger()
if err := waitExtensionSocket(r.socket, 1*time.Minute); err != nil {
return err
}
@ -123,6 +133,9 @@ func OrbitDefaultTables() []osquery.OsqueryPlugin {
// Orbit extensions.
table.NewPlugin("sntp_request", sntp_request.Columns(), sntp_request.GenerateFunc),
firefox_preferences.TablePlugin(osqueryLogger),
cryptoinfotable.TablePlugin(osqueryLogger),
}
return plugins
}

View File

@ -5,12 +5,16 @@ package table
import (
"github.com/fleetdm/fleet/v4/orbit/pkg/table/authdb"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/csrutil_info"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/diskutil/apfs"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/diskutil/corestorage"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dscl"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/filevault_prk"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/filevault_status"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/find_cmd"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/firmware_eficheck_integrity_check"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/firmwarepasswd"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/ioreg"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/nvram_info"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/pmset"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/privaterelay"
@ -18,18 +22,20 @@ import (
"github.com/fleetdm/fleet/v4/orbit/pkg/table/software_update"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/sudo_info"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/user_login_settings"
"github.com/macadmins/osquery-extension/tables/filevaultusers"
"github.com/macadmins/osquery-extension/tables/macos_profiles"
"github.com/macadmins/osquery-extension/tables/macosrsr"
"github.com/macadmins/osquery-extension/tables/mdm"
"github.com/macadmins/osquery-extension/tables/munki"
"github.com/macadmins/osquery-extension/tables/unifiedlog"
"github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
)
func PlatformTables() []osquery.OsqueryPlugin {
return []osquery.OsqueryPlugin{
plugins := []osquery.OsqueryPlugin{
// Fleet tables
table.NewPlugin("icloud_private_relay", privaterelay.Columns(), privaterelay.Generate),
table.NewPlugin("user_login_settings", user_login_settings.Columns(), user_login_settings.Generate),
@ -59,5 +65,19 @@ func PlatformTables() []osquery.OsqueryPlugin {
// osquery version 5.5.0 and up ships a unified_log table in core
// we are renaming the one from the macadmins extension to avoid collision
table.NewPlugin("macadmins_unified_log", unifiedlog.UnifiedLogColumns(), unifiedlog.UnifiedLogGenerate),
filevault_status.TablePlugin(osqueryLogger), // table name is "filevault_status"
ioreg.TablePlugin(osqueryLogger), // table name is "ioreg"
// firmwarepasswd table. Only returns valid data on a Mac with an Intel processor. Background: https://support.apple.com/en-us/HT204455
firmwarepasswd.TablePlugin(osqueryLogger), // table name is "firmwarepasswd"
// Table for parsing Apple Property List files, which are typically stored in ~/Library/Preferences/
dataflattentable.TablePlugin(osqueryLogger, dataflattentable.PlistType), // table name is "parse_plist"
}
// append platform specific tables
plugins = appendTables(plugins)
return plugins
}

View File

@ -0,0 +1,10 @@
//go:build darwin && amd64
package table
import "github.com/osquery/osquery-go"
// stub for amd64 platforms
func appendTables(plugins []osquery.OsqueryPlugin) []osquery.OsqueryPlugin {
return plugins
}

View File

@ -0,0 +1,18 @@
//go:build darwin && arm64
package table
import (
// ARM64 Kolide tables
appicons "github.com/fleetdm/fleet/v4/orbit/pkg/table/app-icons"
"github.com/osquery/osquery-go"
)
func appendTables(plugins []osquery.OsqueryPlugin) []osquery.OsqueryPlugin {
plugins = append(plugins,
// arm64 tables
appicons.AppIcons(),
)
return plugins
}

View File

@ -0,0 +1,19 @@
//go:build linux
package table
import (
"github.com/fleetdm/fleet/v4/orbit/pkg/table/crowdstrike/falcon_kernel_check"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/crowdstrike/falconctl"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/cryptsetup"
"github.com/osquery/osquery-go"
)
func PlatformTables() []osquery.OsqueryPlugin {
return []osquery.OsqueryPlugin{
cryptsetup.TablePlugin(osqueryLogger), // table name is "cryptsetup_status"
falconctl.NewFalconctlOptionTable(osqueryLogger), // table name is "falconctl_option"
falcon_kernel_check.TablePlugin(osqueryLogger), // table name is "falcon_kernel_check"
}
}

View File

@ -0,0 +1,26 @@
package table
import (
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
// Logger is a wrapper around zerolog, which we use for tables
// using the go-kit logger
type Logger struct {
zerolog.Logger
}
// Log logs a message, implementing log.Logger interface
func (l *Logger) Log(keyValuePairs ...interface{}) error {
log.Logger.Info().Msg(fmt.Sprint(keyValuePairs...))
return nil
}
// NewOsqueryLogger returns the Logger struct.
func NewOsqueryLogger() *Logger {
// Return a Logger struct with our global logger, and use the existing global config for the log level.
return &Logger{log.Logger}
}

View File

@ -1,4 +1,7 @@
//go:build !darwin && !windows
//go:build !darwin && !windows && !linux
// Currently (2021/10/26) this file is not needed. However, keeping this around for potential
// expansion to other OSs.
package table

View File

@ -3,8 +3,10 @@
package table
import (
"github.com/fleetdm/fleet/v4/orbit/pkg/table/cis_audit"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/mdm"
cisaudit "github.com/fleetdm/fleet/v4/orbit/pkg/table/cis_audit"
mdmbridge "github.com/fleetdm/fleet/v4/orbit/pkg/table/mdm"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/windowsupdatetable"
"github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
)
@ -14,5 +16,7 @@ func PlatformTables() []osquery.OsqueryPlugin {
// Fleet tables
table.NewPlugin("mdm_bridge", mdmbridge.Columns(), mdmbridge.Generate),
table.NewPlugin("cis_audit", cisaudit.Columns(), cisaudit.Generate),
windowsupdatetable.TablePlugin(windowsupdatetable.UpdatesTable, osqueryLogger), // table name is "windows_updates"
}
}

View File

@ -0,0 +1,60 @@
//go:build darwin
// +build darwin
// based on https://github.com/fleetdm/launcher/blob/main/pkg/osquery/tables/filevault
// based on github.com/kolide/launcher/pkg/osquery/tables
package filevault_status
import (
"context"
"errors"
"fmt"
"os"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
const fdesetupPath = "/usr/bin/fdesetup"
type Table struct {
logger log.Logger
}
func TablePlugin(logger log.Logger) *table.Plugin {
columns := []table.ColumnDefinition{
table.TextColumn("status"),
}
t := &Table{
logger: logger,
}
return table.NewPlugin("filevault_status", columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
output, err := tablehelpers.Exec(ctx, t.logger, 10, []string{fdesetupPath}, []string{"status"}, false)
if err != nil {
level.Info(t.logger).Log("msg", "fdesetup failed", "err", err)
// Don't error out if the binary isn't found
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, fmt.Errorf("calling fdesetup: %w", err)
}
status := strings.TrimSuffix(string(output), "\n")
results := []map[string]string{
{
"status": status,
},
}
return results, nil
}

View File

@ -0,0 +1,122 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package firefox_preferences
import (
"bufio"
"context"
"fmt"
"os"
"regexp"
"strings"
"github.com/fleetdm/fleet/v4/orbit/pkg/dataflatten"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
type Table struct {
name string
logger log.Logger
}
const tableName = "firefox_preferences"
// For the first iteration of this table, we decided to do our own parsing with regex,
// leaving the JSON strings as-is.
//
// input -> user_pref("app.normandy.foo", "{\"abc\":123}");
// output -> [user_pref("app.normandy.foo", "{"abc":123}"); app.normandy.foo {"abc":123}]
//
// Note that we do not capture the surrounding quotes for either groups.
//
// In the future, we may want to use go-mozpref:
// https://github.com/hansmi/go-mozpref
var re = regexp.MustCompile(`^user_pref\("([^,]+)",\s*"?(.*?)"?\);$`)
func TablePlugin(logger log.Logger) *table.Plugin {
columns := dataflattentable.Columns(
table.TextColumn("path"),
)
t := &Table{
name: tableName,
logger: logger,
}
return table.NewPlugin(t.name, columns, t.generate)
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
var results []map[string]string
filePaths := tablehelpers.GetConstraints(queryContext, "path")
if len(filePaths) == 0 {
level.Info(t.logger).Log(
"msg", fmt.Sprintf("no path provided to %s", tableName),
"table", tableName,
)
return results, nil
}
for _, filePath := range filePaths {
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) {
flattenOpts := []dataflatten.FlattenOpts{
dataflatten.WithQuery(strings.Split(dataQuery, "/")),
}
file, err := os.Open(filePath)
if err != nil {
level.Info(t.logger).Log(
"msg", "failed to open file",
"table", tableName,
"path", filePath,
"err", err,
)
continue
}
rawKeyVals := make(map[string]interface{})
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// Given the line format: user_pref("app.normandy.first_run", false);
// the return value should be a three element array, where the second
// and third elements are the key and value, respectively.
match := re.FindStringSubmatch(line)
// If the match doesn't have a length of 3, the line is malformed in some way.
// Skip it.
if len(match) != 3 {
continue
}
// The regex already stripped out the surrounding quotes, so now we're
// left with escaped quotes that no longer make sense.
// i.e. {\"249024122\":[1660860020218]}
// Replace those with unescaped quotes.
rawKeyVals[match[1]] = strings.ReplaceAll(match[2], "\\\"", "\"")
}
flatData, err := dataflatten.Flatten(rawKeyVals, flattenOpts...)
if err != nil {
level.Debug(t.logger).Log(
"msg", "failed to flatten data for path",
"table", tableName,
"path", filePath,
"err", err,
)
continue
}
rowData := map[string]string{"path": filePath}
results = append(results, dataflattentable.ToMap(flatData, dataQuery, rowData)...)
}
}
return results, nil
}

View File

@ -0,0 +1,80 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package firefox_preferences
import (
"context"
"encoding/json"
"os"
"path"
"testing"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/tablehelpers"
"github.com/go-kit/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_generate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
filePaths []string
expectedResultsFilePath string
query string
}{
{
name: "no path",
},
{
name: "single path",
filePaths: []string{path.Join("testdata", "prefs.js")},
expectedResultsFilePath: "testdata/output.single_path.json",
},
{
name: "single path with query",
filePaths: []string{path.Join("testdata", "prefs.js")},
expectedResultsFilePath: "testdata/output.single_path_with_query.json",
query: "app.normandy.first_run",
},
{
name: "multiple paths",
filePaths: []string{path.Join("testdata", "prefs.js"), path.Join("testdata", "prefs2.js")},
expectedResultsFilePath: "testdata/output.multiple_paths.json",
},
{
name: "file with bad data",
filePaths: []string{path.Join("testdata", "prefs3.js")},
expectedResultsFilePath: "testdata/output.file_with_bad_data.json",
},
}
table := Table{logger: log.NewNopLogger()}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
constraints := make(map[string][]string)
constraints["path"] = tt.filePaths
if tt.query != "" {
constraints["query"] = append(constraints["query"], tt.query)
}
got, _ := table.generate(context.Background(), tablehelpers.MockQueryContext(constraints))
var want []map[string]string
if tt.expectedResultsFilePath != "" {
wantBytes, err := os.ReadFile(tt.expectedResultsFilePath)
require.NoError(t, err)
err = json.Unmarshal(wantBytes, &want)
require.NoError(t, err)
}
assert.ElementsMatch(t, want, got)
})
}
}

View File

@ -0,0 +1,10 @@
[
{
"fullkey": "browser.contextual-services.contextId",
"parent": "",
"key": "browser.contextual-services.contextId",
"value": "{97961fa4-2a6a-470f-a916-45a3f51fc393}",
"query": "*",
"path": "testdata/prefs3.js"
}
]

View File

@ -0,0 +1,74 @@
[
{
"fullkey": "app.normandy.first_run",
"parent": "",
"key": "app.normandy.first_run",
"value": "false",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.normandy.migrationsApplied",
"parent": "",
"key": "app.normandy.migrationsApplied",
"value": "12",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.normandy.user_id",
"parent": "",
"key": "app.normandy.user_id",
"value": "2324e712-7c70-4d9c-8e78-2a6eb9b2d967",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.update.background.previous.reasons",
"parent": "",
"key": "app.update.background.previous.reasons",
"value": "[\"the maintenance service registry key is not present\"]",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.contextual-services.contextId",
"parent": "",
"key": "browser.contextual-services.contextId",
"value": "{97961fa4-2a6a-470f-a916-45a3f51fc393}",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.newtabpage.activity-stream.discoverystream.rec.impressions",
"parent": "",
"key": "browser.newtabpage.activity-stream.discoverystream.rec.impressions",
"value": "{\"138341\":1660860020223,\"138361\":1660860020223,\"257394347\":1660860020221}",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.newtabpage.activity-stream.discoverystream.spoc.impressions",
"parent": "",
"key": "browser.newtabpage.activity-stream.discoverystream.spoc.impressions",
"value": "{\"249024122\":[1660860020218]}",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.newtabpage.pinned",
"parent": "",
"key": "browser.newtabpage.pinned",
"value": "[{\"url\":\"https://amazon.com\",\"label\":\"@amazon\",\"searchTopSite\":true}]",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.normandy.first_run",
"parent": "",
"key": "app.normandy.first_run",
"value": "true",
"query": "*",
"path": "testdata/prefs2.js"
}
]

View File

@ -0,0 +1,66 @@
[
{
"fullkey": "app.normandy.first_run",
"parent": "",
"key": "app.normandy.first_run",
"value": "false",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.normandy.migrationsApplied",
"parent": "",
"key": "app.normandy.migrationsApplied",
"value": "12",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.normandy.user_id",
"parent": "",
"key": "app.normandy.user_id",
"value": "2324e712-7c70-4d9c-8e78-2a6eb9b2d967",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "app.update.background.previous.reasons",
"parent": "",
"key": "app.update.background.previous.reasons",
"value": "[\"the maintenance service registry key is not present\"]",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.contextual-services.contextId",
"parent": "",
"key": "browser.contextual-services.contextId",
"value": "{97961fa4-2a6a-470f-a916-45a3f51fc393}",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.newtabpage.activity-stream.discoverystream.rec.impressions",
"parent": "",
"key": "browser.newtabpage.activity-stream.discoverystream.rec.impressions",
"value": "{\"138341\":1660860020223,\"138361\":1660860020223,\"257394347\":1660860020221}",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.newtabpage.activity-stream.discoverystream.spoc.impressions",
"parent": "",
"key": "browser.newtabpage.activity-stream.discoverystream.spoc.impressions",
"value": "{\"249024122\":[1660860020218]}",
"query": "*",
"path": "testdata/prefs.js"
},
{
"fullkey": "browser.newtabpage.pinned",
"parent": "",
"key": "browser.newtabpage.pinned",
"value": "[{\"url\":\"https://amazon.com\",\"label\":\"@amazon\",\"searchTopSite\":true}]",
"query": "*",
"path": "testdata/prefs.js"
}
]

View File

@ -0,0 +1,10 @@
[
{
"fullkey": "app.normandy.first_run",
"parent": "",
"key": "app.normandy.first_run",
"value": "false",
"query": "app.normandy.first_run",
"path": "testdata/prefs.js"
}
]

View File

@ -0,0 +1,19 @@
// Mozilla User Preferences
// DO NOT EDIT THIS FILE.
//
// If you make changes to this file while the application is running,
// the changes will be overwritten when the application exits.
//
// To change a preference value, you can either:
// - modify it via the UI (e.g. via about:config in the browser); or
// - set it within a user.js file in your profile.
user_pref("app.normandy.first_run", false);
user_pref("app.normandy.migrationsApplied", 12);
user_pref("app.normandy.user_id", "2324e712-7c70-4d9c-8e78-2a6eb9b2d967");
user_pref("app.update.background.previous.reasons", "[\"the maintenance service registry key is not present\"]");
user_pref("browser.contextual-services.contextId", "{97961fa4-2a6a-470f-a916-45a3f51fc393}");
user_pref("browser.newtabpage.activity-stream.discoverystream.rec.impressions", "{\"138341\":1660860020223,\"138361\":1660860020223,\"257394347\":1660860020221}");
user_pref("browser.newtabpage.activity-stream.discoverystream.spoc.impressions", "{\"249024122\":[1660860020218]}");
user_pref("browser.newtabpage.pinned", "[{\"url\":\"https://amazon.com\",\"label\":\"@amazon\",\"searchTopSite\":true}]");

View File

@ -0,0 +1,12 @@
// Mozilla User Preferences
// DO NOT EDIT THIS FILE.
//
// If you make changes to this file while the application is running,
// the changes will be overwritten when the application exits.
//
// To change a preference value, you can either:
// - modify it via the UI (e.g. via about:config in the browser); or
// - set it within a user.js file in your profile.
user_pref("app.normandy.first_run", true);

View File

@ -0,0 +1,14 @@
// Mozilla User Preferences
// DO NOT EDIT THIS FILE.
//
// If you make changes to this file while the application is running,
// the changes will be overwritten when the application exits.
//
// To change a preference value, you can either:
// - modify it via the UI (e.g. via about:config in the browser); or
// - set it within a user.js file in your profile.
user_pref(false);
user_pref("browser.contextual-services.contextId", "{97961fa4-2a6a-470f-a916-45a3f51fc393}");
foobar("app.normandy.user_id", "2324e712-7c70-4d9c-8e78-2a6eb9b2d967");

View File

@ -0,0 +1,166 @@
// firmwarepasswd is a simple wrapper around the
// `/usr/sbin/firmwarepasswd` tool. This should be considered beta at
// best. It serves a bit as a pattern for future exec work.
// based on github.com/kolide/launcher/pkg/osquery/tables
package firmwarepasswd
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/osquery/osquery-go/plugin/table"
)
type Table struct {
logger log.Logger
parser *OutputParser
}
func TablePlugin(logger log.Logger) *table.Plugin {
columns := []table.ColumnDefinition{
table.IntegerColumn("option_roms_allowed"),
table.IntegerColumn("password_enabled"),
table.TextColumn("mode"),
}
t := New(logger)
return table.NewPlugin("firmwarepasswd", columns, t.generate)
}
func New(logger log.Logger) *Table {
parser := NewParser(logger,
[]Matcher{
{
Match: func(in string) bool { return strings.HasPrefix(in, "Password Enabled: ") },
KeyFunc: func(_ string) (string, error) { return "password_enabled", nil },
ValFunc: func(in string) (string, error) { return passwordValue(in) },
},
{
Match: func(in string) bool { return strings.HasPrefix(in, "Mode: ") },
KeyFunc: func(_ string) (string, error) { return "mode", nil },
ValFunc: func(in string) (string, error) { return modeValue(in) },
},
{
Match: func(in string) bool { return strings.HasPrefix(in, "Option roms ") },
KeyFunc: func(_ string) (string, error) { return "option_roms_allowed", nil },
ValFunc: func(in string) (string, error) { return optionRomValue(in) },
},
})
return &Table{
logger: level.NewFilter(logger, level.AllowInfo()),
parser: parser,
}
}
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
result := make(map[string]string)
for _, mode := range []string{"-check", "-mode"} {
output := new(bytes.Buffer)
if err := t.runFirmwarepasswd(ctx, mode, output); err != nil {
level.Info(t.logger).Log(
"msg", "Error running firmware password",
"command", mode,
"err", err,
)
continue
}
// Merge resulting matches
for _, row := range t.parser.Parse(output) {
for k, v := range row {
result[k] = v
}
}
}
return []map[string]string{result}, nil
}
func (t *Table) runFirmwarepasswd(ctx context.Context, subcommand string, output *bytes.Buffer) error {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/usr/sbin/firmwarepasswd", subcommand)
dir, err := os.MkdirTemp("", "osq-firmwarepasswd")
if err != nil {
return fmt.Errorf("mktemp: %w", err)
}
defer os.RemoveAll(dir)
if err := os.Chmod(dir, 0o755); err != nil {
return fmt.Errorf("chmod: %w", err)
}
cmd.Dir = dir
stderr := new(bytes.Buffer)
cmd.Stderr = stderr
cmd.Stdout = output
if err := cmd.Run(); err != nil {
level.Debug(t.logger).Log(
"msg", "Error running firmwarepasswd",
"stderr", strings.TrimSpace(stderr.String()),
"stdout", strings.TrimSpace(output.String()),
"err", err,
)
return fmt.Errorf("running firmwarepasswd: %w", err)
}
return nil
}
func modeValue(in string) (string, error) {
components := strings.SplitN(in, ":", 2)
if len(components) < 2 {
return "", fmt.Errorf("Can't tell mode from %s", in)
}
return strings.TrimSpace(strings.ToLower(components[1])), nil
}
func passwordValue(in string) (string, error) {
components := strings.SplitN(in, ":", 2)
if len(components) < 2 {
return "", fmt.Errorf("Can't tell value from %s", in)
}
t, err := discernValBool(components[1])
if t {
return "1", err
}
return "0", err
}
func optionRomValue(in string) (string, error) {
switch strings.TrimPrefix(in, "Option roms ") {
case "not allowed":
return "0", nil
case "allowed":
return "1", nil
}
return "", fmt.Errorf("Can't tell value from %s", in)
}
func discernValBool(in string) (bool, error) {
switch strings.TrimSpace(strings.ToLower(in)) {
case "true", "t", "1", "y", "yes":
return true, nil
case "false", "f", "0", "n", "no":
return false, nil
}
return false, fmt.Errorf("Can't discern boolean from string <%s>", in)
}

View File

@ -0,0 +1,71 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package firmwarepasswd
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/go-kit/log"
"github.com/stretchr/testify/require"
)
func TestParser(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected map[string]string
}{
{
input: "check-no.txt",
expected: map[string]string{"password_enabled": "0"},
},
{
input: "check-garbage.txt",
expected: map[string]string{"password_enabled": "0"},
},
{
input: "check-yes.txt",
expected: map[string]string{"password_enabled": "1"},
},
{
input: "mode-command.txt",
expected: map[string]string{
"mode": "command",
"option_roms_allowed": "0",
},
},
{
input: "mode-none.txt",
expected: map[string]string{
"mode": "none",
"option_roms_allowed": "1",
},
},
}
for _, tt := range tests {
tt := tt
parser := New(log.NewNopLogger()).parser
t.Run(tt.input, func(t *testing.T) {
t.Parallel()
inputBytes, err := os.ReadFile(filepath.Join("testdata", tt.input))
require.NoError(t, err, "read file %s", tt.input)
inputBuffer := bytes.NewBuffer(inputBytes)
result := make(map[string]string)
for _, row := range parser.Parse(inputBuffer) {
for k, v := range row {
result[k] = v
}
}
require.EqualValues(t, tt.expected, result)
})
}
}

View File

@ -0,0 +1,83 @@
// based on github.com/kolide/launcher/pkg/osquery/tables
package firmwarepasswd
import (
"bufio"
"bytes"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
)
type Matcher struct {
Match func(string) bool
KeyFunc func(string) (string, error)
ValFunc func(string) (string, error)
}
type OutputParser struct {
matchers []Matcher
logger log.Logger
}
func NewParser(logger log.Logger, matchers []Matcher) *OutputParser {
p := &OutputParser{
matchers: matchers,
logger: logger,
}
return p
}
// Parse looks at command output, line by line. It uses the defined Matchers to set any appropriate values
func (p *OutputParser) Parse(input *bytes.Buffer) []map[string]string {
var results []map[string]string
scanner := bufio.NewScanner(input)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
row := make(map[string]string)
// check each possible key match
for _, m := range p.matchers {
if m.Match(line) {
key, err := m.KeyFunc(line)
if err != nil {
level.Debug(p.logger).Log(
"msg", "key match failed",
"line", line,
"err", err,
)
continue
}
val, err := m.ValFunc(line)
if err != nil {
level.Debug(p.logger).Log(
"msg", "value match failed",
"line", line,
"err", err,
)
continue
}
row[key] = val
continue
}
}
if len(row) == 0 {
level.Debug(p.logger).Log("msg", "No matched keys", "line", line)
continue
}
results = append(results, row)
}
if err := scanner.Err(); err != nil {
level.Debug(p.logger).Log("msg", "scanner error", "err", err)
}
return results
}

Some files were not shown because too many files have changed in this diff Show More