diff --git a/adalanche/main.go b/adalanche/main.go
index 159e1d3c..9ed6f5f3 100644
--- a/adalanche/main.go
+++ b/adalanche/main.go
@@ -12,7 +12,7 @@ import (
)
func main() {
- err := cli.Run()
+ err := cli.CliMainEntryPoint()
if err != nil {
ui.Error().Msg(err.Error())
diff --git a/build.ps1 b/build.ps1
index b4618693..e7d89ea6 100644
--- a/build.ps1
+++ b/build.ps1
@@ -42,6 +42,6 @@ if ("$DIRTYFILES" -ne "") {
$LDFLAGS = "-X github.com/lkarlslund/adalanche/modules/version.Commit=$COMMIT -X github.com/lkarlslund/adalanche/modules/version.Version=$VERSION"
# Release
-BuildVariants -ldflags "$LDFLAGS -s" -prefix adalanche-collector -path ./collector -arch @("386") -os @("windows") -suffix ".exe"
+BuildVariants -ldflags "$LDFLAGS -s" -compileflags "-tags 32bit" -prefix adalanche-collector -path ./collector -arch @("386") -os @("windows") -suffix ".exe"
BuildVariants -ldflags "$LDFLAGS -s" -prefix adalanche -path ./adalanche -arch @("amd64", "arm64") -os @("windows") -suffix ".exe"
BuildVariants -ldflags "$LDFLAGS -s" -prefix adalanche -path ./adalanche -arch @("amd64", "arm64") -os @("darwin", "linux")
diff --git a/go.mod b/go.mod
index c90b19b3..524143b6 100644
--- a/go.mod
+++ b/go.mod
@@ -69,7 +69,7 @@ require (
github.com/rickb777/plural v1.4.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/scjalliance/comshim v0.0.0-20240712181150-e070933cb68e // indirect
- github.com/spf13/pflag v1.0.5 // indirect
+ github.com/spf13/pflag v1.0.5
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
@@ -92,35 +92,53 @@ require (
github.com/gin-contrib/pprof v1.5.0
github.com/gin-contrib/static v1.1.2
github.com/golang-auth/go-channelbinding v1.0.1
+ github.com/google/certtostore v1.0.3
github.com/gorilla/websocket v1.5.3
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/lkarlslund/gonk v0.0.0-20240227175124-4dc0aa78e98a
+ github.com/spf13/viper v1.19.0
www.velocidex.com/golang/go-ese v0.2.1-0.20240207005444-85d57b555f8b
)
require (
atomicgo.dev/schedule v0.1.0 // indirect
github.com/DataDog/gostackparse v0.7.0 // indirect
+ github.com/StackExchange/wmi v1.2.0 // indirect
github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a // indirect
github.com/Velocidex/yaml/v2 v2.2.8 // indirect
github.com/bytedance/sonic v1.12.1 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/goccy/go-json v0.10.3 // indirect
+ github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ github.com/spf13/afero v1.11.0 // indirect
+ github.com/spf13/cast v1.6.0 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.9.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index e68f70ae..a2486043 100644
--- a/go.sum
+++ b/go.sum
@@ -27,6 +27,8 @@ github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20230201052002-6c5833b989be h1
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20230201052002-6c5833b989be/go.mod h1:ihkm1viTbO/LOsgdGoFPBSvzqvx7ibvkMzYp3CgtHik=
github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM=
github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko=
+github.com/StackExchange/wmi v1.2.0 h1:noJEYkMQVlFCEAc+2ma5YyRhlfjcWfZqk5sBRYozdyM=
+github.com/StackExchange/wmi v1.2.0/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a h1:AeXPUzhU0yhID/v5JJEIkjaE85ASe+Vh4Kuv1RSLL+4=
github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a/go.mod h1:ukJBuruT9b24pdgZwWDvOaCYHeS03B7oQPCUWh25bwM=
github.com/Velocidex/ordereddict v0.0.0-20220107075049-3dbe58412844/go.mod h1:Y5Tfx5SKGOzkulpqfonrdILSPIuNg+GqKE/DhVJgnpg=
@@ -68,14 +70,19 @@ github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
github.com/felixge/fgtrace v0.2.0 h1:lq7RO6ELjR+S74+eD+ai/vhYvsjno7Vb84yzU6RPSeU=
github.com/felixge/fgtrace v0.2.0/go.mod h1:q9vMuItthu3CRfNhirTCTwzBcJ8atUFkrJUhgQbjg8c=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
@@ -128,6 +135,10 @@ github.com/google/aukera v0.0.0-20201117230544-d145c8357fea/go.mod h1:oXqTZORBzd
github.com/google/cabbie v1.0.2/go.mod h1:6MmHaUrgfabehCHAIaxdrbmvHSxUVXj3Abs08FMABSo=
github.com/google/cabbie v1.0.5 h1:j+JWBiMpzJCTkVLKrzsNBQLkRff55sjzXc0AQOTV2JU=
github.com/google/cabbie v1.0.5/go.mod h1:WytqVAbQee3vvDZQSROF6ZsPGrUsmpot9tKNtxnr/lk=
+github.com/google/certtostore v1.0.3 h1:LD3lNKt4wU3V50qo6wXmr/6ER+KX+rmK30ueyctaZM8=
+github.com/google/certtostore v1.0.3/go.mod h1:6YomPQrPy09/fTFbjJt+x1RqMADyc1ceU91z19Zk3No=
+github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae h1:Iy1Ad7L9qPtNAFJad+Ch2kwDXrcwu7QUBR0bfChjnEM=
+github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae/go.mod h1:DoDv8G58DuLNZF0KysYn0bA/6ZWhmRW3fZE2VnGEH0w=
github.com/google/glazier v0.0.0-20210617205946-bf91b619f5d4/go.mod h1:g7oyIhindbeebnBh0hbFua5rv6XUt/nweDwIWdvxirg=
github.com/google/glazier v0.0.0-20240702204548-ecffa77d6f73 h1:4r5VJu4db2UkVoOQG29YZDWthNRjLTb8Iyr3NsBKpR4=
github.com/google/glazier v0.0.0-20240702204548-ecffa77d6f73/go.mod h1:g/uB9yDfCoH08pu7+Isrzaeh35vCvAY0SUFEJCJ1+5I=
@@ -155,9 +166,16 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gravwell/gravwell/v3 v3.8.34 h1:3Cctgw3RAjZBxvm1rUlZybzKPxrVdmC6nt6vlozOMbI=
github.com/gravwell/gravwell/v3 v3.8.34/go.mod h1:FsIn6mNCcY7wEswbhxRpLchB9cF5jjaQIb/V3jh1YOg=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iamacarpet/go-win64api v0.0.0-20210311141720-fe38760bed28/go.mod h1:oGJx9dz0Ny7HC7U55RZ0Smd6N9p3hXP/+hOFtuYrAxM=
github.com/icza/gox v0.0.0-20230924165045-adcb03233bb5 h1:K7KEFpKgVcjj98jOu2Z3xMBTtTwfYVT90Zmo3ZuWmbE=
@@ -187,8 +205,8 @@ github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -213,6 +231,8 @@ github.com/lkarlslund/time-timespan v0.0.0-20210712111050-6e7c565fa001 h1:S9Xs8F
github.com/lkarlslund/time-timespan v0.0.0-20210712111050-6e7c565fa001/go.mod h1:Ckyc7Him7fKmtsSkOJoUNNU7UgBtd/DDH+/xkfX808Q=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -225,6 +245,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -247,8 +269,9 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
@@ -270,12 +293,16 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e/go.mod h1:9Tc1SKnfACJb9N7cw2eyuI6xzy845G7uZONBsi5uPEA=
github.com/scjalliance/comshim v0.0.0-20240712181150-e070933cb68e h1:DHQTQhd+UU97hLiIaH5oDf61NqH6iBoHBgZoeWc1olc=
github.com/scjalliance/comshim v0.0.0-20240712181150-e070933cb68e/go.mod h1:RS825256UevDX5P1oImjU4qUY3fwF6HDLHUD+Zbbd/A=
@@ -288,10 +315,18 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
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/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
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.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -306,6 +341,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tinylib/msgp v1.2.0 h1:0uKB/662twsVBpYUPbokj4sTSKhWFKB7LopO2kWK8lY=
github.com/tinylib/msgp v1.2.0/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
@@ -324,6 +361,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
@@ -426,6 +465,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 h1:FpCr9V8wuOei4BAen+93HtVJ+XSi+KPbaPKm0Vj5R64=
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79/go.mod h1:gWkaRU7CoXpezCBWfWjm3999QqS+1pYPXGbqQCTMzo8=
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 h1:MZF6J7CV6s/h0HBkfqebrYfKCVEo5iN+wzE4QhV3Evo=
diff --git a/modules/analyze/certificate_windows.go b/modules/analyze/certificate_windows.go
new file mode 100644
index 00000000..31283d30
--- /dev/null
+++ b/modules/analyze/certificate_windows.go
@@ -0,0 +1,59 @@
+//go:build !32bit
+// +build !32bit
+
+package analyze
+
+import (
+ "crypto/tls"
+
+ "github.com/google/certtostore"
+ "github.com/lkarlslund/adalanche/modules/ui"
+)
+
+var UseWindowsCert = Command.Flags().Bool("usewindowscert", true, "Try to autoload a certificate from the Windows Certificate store and use that for https")
+
+func init() {
+ AddOption(func(ws *WebService) error {
+ if !*UseWindowsCert {
+ return nil
+ }
+
+ // Open the local cert store. Provider generally shouldn't matter, so use Software which is ubiquitous. See comments in getHostKey.
+ store, err := certtostore.OpenWinCertStore(certtostore.ProviderMSSoftware, "", []string{"localhost"}, nil, false)
+
+ if err != nil {
+ ui.Warn().Msgf("Opening Windows certificate store failed: %v, continuing without it", err)
+ return nil
+ }
+
+ crt, context, err := store.CertWithContext()
+ if err != nil {
+ ui.Warn().Msgf("Auto-loading Windows certificate failed: %v, continuing without it", err)
+ return nil
+ }
+
+ if crt == nil {
+ ui.Warn().Msgf("No usable certificate found in Windows certificate store")
+ return nil
+ }
+
+ key, err := store.CertKey(context)
+ if err != nil {
+ ui.Warn().Msgf("Auto-loading Windows certificate private key failed: %v, continuing without it", err)
+ return nil
+ }
+
+ if key == nil {
+ ui.Warn().Msgf("No usable certificate private key found in Windows certificate store")
+ return nil
+ }
+
+ ws.srv.TLSConfig = &tls.Config{
+ Certificates: []tls.Certificate{tls.Certificate{
+ Certificate: [][]byte{crt.Raw},
+ PrivateKey: key}},
+ }
+
+ return nil
+ })
+}
diff --git a/modules/analyze/cli.go b/modules/analyze/cli.go
index 908d6ba0..bc684dc6 100644
--- a/modules/analyze/cli.go
+++ b/modules/analyze/cli.go
@@ -16,11 +16,11 @@ var (
Short: "Lanunches the interactive discovery tool in your browser",
}
- Bind = Command.Flags().String("bind", "127.0.0.1:8080", "Address and port of webservice to bind to")
- NoBrowser = Command.Flags().Bool("nobrowser", false, "Don't launch browser after starting webservice")
- LocalHTML = Command.Flags().StringSlice("localhtml", nil, "Override embedded HTML and use a local folders for webservice (for development)")
-
- WebService = NewWebservice()
+ Bind = Command.Flags().String("bind", "127.0.0.1:8080", "Address and port of webservice to bind to")
+ NoBrowser = Command.Flags().Bool("nobrowser", false, "Don't launch browser after starting webservice")
+ LocalHTML = Command.Flags().StringSlice("localhtml", nil, "Override embedded HTML and use a local folders for webservice (for development)")
+ Certificate = Command.Flags().String("certificate", "", "Path to certificate file")
+ PrivateKey = Command.Flags().String("privatekey", "", "Path to private key file")
)
func init() {
@@ -32,15 +32,21 @@ func init() {
}
func Execute(cmd *cobra.Command, args []string) error {
+ datapath := cmd.Flag("datapath").Value.String()
+
+ if *Certificate != "" && *PrivateKey == "" {
+ AddOption(WithCert(*Certificate, *PrivateKey))
+ }
+
// allow debug runs to use local paths for html
for _, localhtmlpath := range *LocalHTML {
- WebService.AddLocalHTML(localhtmlpath)
+ AddOption(WithLocalHTML(localhtmlpath))
}
- datapath := cmd.InheritedFlags().Lookup("datapath").Value.String()
-
// Fire up the web interface with incomplete results
- err := WebService.Start(*Bind)
+ ws := NewWebservice()
+
+ err := ws.Start(*Bind)
if err != nil {
return err
}
@@ -48,7 +54,7 @@ func Execute(cmd *cobra.Command, args []string) error {
// Launch browser
if !*NoBrowser {
var err error
- url := "http://" + *Bind
+ url := ws.protocol + "://" + *Bind
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
@@ -64,12 +70,12 @@ func Execute(cmd *cobra.Command, args []string) error {
}
}
- err = WebService.Analyze(datapath)
+ err = ws.Analyze(datapath)
if err != nil {
return err
}
// Wait for webservice to end
- <-WebService.QuitChan()
+ <-ws.QuitChan()
return nil
}
diff --git a/modules/analyze/debugfuncs.go b/modules/analyze/debugfuncs.go
index d2880d05..60f19047 100644
--- a/modules/analyze/debugfuncs.go
+++ b/modules/analyze/debugfuncs.go
@@ -5,7 +5,7 @@ import (
"github.com/lkarlslund/adalanche/modules/engine"
)
-func debugfuncs(ws *webservice) {
+func debugfuncs(ws *WebService) {
ws.Router.GET("/debug/attributes", func(c *gin.Context) {
c.JSON(200, engine.AttributeInfos())
})
diff --git a/modules/analyze/html/custom.js b/modules/analyze/html/custom.js
index 1b01c5e5..c6fd9777 100644
--- a/modules/analyze/html/custom.js
+++ b/modules/analyze/html/custom.js
@@ -246,7 +246,7 @@ function analyze(e) {
$.ajax({
type: 'POST',
- url: 'analyzegraph',
+ url: 'api/graphquery',
contentType: 'charset=utf-8',
data: JSON.stringify(
$('#ldapqueryform, #analysisoptionsform, #analysispwnform, #analysistypeform')
@@ -339,8 +339,11 @@ function refreshProgress() {
progressSocket.onmessage = function (message) {
$("#offlineblur").hide()
- progressbars = $.parseJSON(message.data);
+ progress = $.parseJSON(message.data);
+ status = progress.status;
+
+ progressbars = progress.progressbars;
if (progressbars.length > 0) {
lastwasidle = false
keepProgressbars = new Set()
@@ -431,7 +434,7 @@ $(function () {
core: {
multiple: false,
data: {
- url: '/tree',
+ url: '/api/tree',
dataType: 'json',
data: function (node) {
return { id: node.id };
@@ -455,7 +458,7 @@ $(function () {
if (d.event.type == 'click') {
$.ajax({
type: "GET",
- url: "details/id/" + d.node.id, // n123 format -> 123
+ url: "api/details/id/" + d.node.id, // n123 format -> 123
dataType: "json",
success: function (data) {
// details = rendernode(data)
@@ -502,7 +505,7 @@ $(function () {
// check query for errors when user has been idle for 200ms
$.ajax({
type: 'GET',
- url: '/validatequery',
+ url: '/backend/validatequery',
data: {
query: e.target.value,
},
@@ -521,14 +524,22 @@ $(function () {
// Display stats on screen
$.ajax({
- url: 'statistics',
+ url: 'backend/statistics',
dataType: 'json',
success: function (data) {
- statustext = "
![](icons/adalanche-logo.svg)
" + data.adalanche.program + "
" +
+ statustext =
+ "![](icons/adalanche-logo.svg)
" +
+ data.adalanche.program +
+ "
" +
data.adalanche.shortversion +
- '' +
- data.statistics.Total +
+ "
";
+
+ if (data.adalanche.status == "Ready") {
+ statustext += data.statistics.Total +
' objects connected by '+data.statistics.PwnConnections+' links
';
+ } else {
+ statustext += "Backend status: "+data.adalanche.status;
+ }
first = true
for (datatype in data.statistics) {
@@ -577,7 +588,7 @@ $(function () {
$.ajax({
type: 'GET',
- url: '/filteroptions',
+ url: '/backend/filteroptions',
dataType: 'json',
success: function (data) {
buttons = `
`;
diff --git a/modules/analyze/html/graph.js b/modules/analyze/html/graph.js
index c37f3ec9..ad9e0f4f 100644
--- a/modules/analyze/html/graph.js
+++ b/modules/analyze/html/graph.js
@@ -836,7 +836,7 @@ function initgraph(data) {
onClickFunction: function (evt) {
$.ajax({
type: "GET",
- url: "details/id/" + evt.target.id().substring(1), // n123 format -> 123
+ url: "api/details/id/" + evt.target.id().substring(1), // n123 format -> 123
dataType: "json",
success: function (data) {
if (data.attributes["distinguishedName"]) {
@@ -866,7 +866,7 @@ function initgraph(data) {
onClickFunction: function (evt) {
$.ajax({
type: "GET",
- url: "details/id/" + evt.target.id().substring(1), // n123 format -> 123
+ url: "api/details/id/" + evt.target.id().substring(1), // n123 format -> 123
dataType: "json",
success: function (data) {
if (data.attributes["distinguishedName"]) {
@@ -906,7 +906,7 @@ function initgraph(data) {
// console.log('clicked node ' + this.id());
$.ajax({
type: "GET",
- url: "details/id/" + (evt.target.id().substring(1)), // n123 format -> 123
+ url: "api/details/id/" + (evt.target.id().substring(1)), // n123 format -> 123
dataType: "json",
success: function (data) {
details = rendernode(evt.target)
@@ -975,6 +975,19 @@ function initgraph(data) {
});
});
+ // Distribute all objects to predictable locations
+ data.forEach(function (ele) {
+ if (ele.group == "nodes") {
+ // set x to node id modulus 1024
+ x = ele.data.id.substr(1) % 1024;
+ y = Math.floor(ele.data.id.substr(1) / 1024);
+ ele.position = {
+ x: x,
+ y: y
+ }
+ }
+ });
+
// Load data into Cytoscape
cy.add(data);
diff --git a/modules/analyze/html/icons/twitter.svg b/modules/analyze/html/icons/twitter.svg
deleted file mode 100644
index 2e39d2e2..00000000
--- a/modules/analyze/html/icons/twitter.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
diff --git a/modules/analyze/html/icons/x-logo.svg b/modules/analyze/html/icons/x-logo.svg
new file mode 100644
index 00000000..437e2bfd
--- /dev/null
+++ b/modules/analyze/html/icons/x-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/modules/analyze/html/index.html b/modules/analyze/html/index.html
index d7c82768..a87673b0 100644
--- a/modules/analyze/html/index.html
+++ b/modules/analyze/html/index.html
@@ -81,8 +81,8 @@
Adalanche
diff --git a/modules/analyze/webservice.go b/modules/analyze/webservice.go
index ea8d8daa..b242c09a 100644
--- a/modules/analyze/webservice.go
+++ b/modules/analyze/webservice.go
@@ -1,11 +1,13 @@
package analyze
import (
+ "crypto/tls"
"embed"
"errors"
"fmt"
"io"
"io/fs"
+ "net"
"net/http"
"os"
"text/template"
@@ -17,6 +19,7 @@ import (
jsoniter "github.com/json-iterator/go"
"github.com/lkarlslund/adalanche/modules/engine"
"github.com/lkarlslund/adalanche/modules/ui"
+ "github.com/lkarlslund/adalanche/modules/util"
)
//go:embed html/*
@@ -50,12 +53,18 @@ func (ufs UnionFS) Exists(prefix, filename string) bool {
type handlerfunc func(*engine.Objects, http.ResponseWriter, *http.Request)
-type webservice struct {
+type optionsetter func(ws *WebService) error
+
+type WebService struct {
Initialized bool
quit chan bool
- srv *http.Server
- Router *gin.Engine
+ srv http.Server
+
+ protocol string
+
+ engine *gin.Engine
+ Router *gin.RouterGroup
localhtmlused bool
UnionFS
@@ -69,13 +78,21 @@ type webservice struct {
AdditionalHeaders []string // Additional things to add to the main page
}
-func NewWebservice() *webservice {
+var globaloptions []optionsetter
+
+func AddOption(os optionsetter) {
+ globaloptions = append(globaloptions, os)
+}
+
+func NewWebservice() *WebService {
gin.SetMode(gin.ReleaseMode) // Has to happen first
- ws := &webservice{
- quit: make(chan bool),
- Router: gin.New(),
+ ws := &WebService{
+ quit: make(chan bool),
+ engine: gin.New(),
+ protocol: "http",
}
- ws.Router.Use(func(c *gin.Context) {
+
+ ws.engine.Use(func(c *gin.Context) {
start := time.Now() // Start timer
path := c.Request.URL.Path
@@ -89,7 +106,10 @@ func NewWebservice() *webservice {
logger.Msgf("%s %s (%v) %v, %v bytes", c.Request.Method, path, c.Writer.Status(), time.Since(start), c.Writer.Size())
})
- ws.Router.Use(gin.Recovery()) // adds the default recovery middleware
+ ws.engine.Use(gin.Recovery()) // adds the default recovery middleware
+
+ ws.Router = ws.engine.Group("")
+
htmlFs, _ := fs.Sub(embeddedassets, "html")
ws.AddFS(http.FS(htmlFs))
@@ -97,76 +117,110 @@ func NewWebservice() *webservice {
if ui.GetLoglevel() >= ui.LevelDebug {
debugfuncs(ws)
}
+
+ // Change settings
+ for _, os := range globaloptions {
+ os(ws)
+ }
+
return ws
}
-func (ws *webservice) Init(r gin.IRoutes) {
- // Add stock functions
- ws.Initialized = true
- ws.AddUIEndpoints(r)
- ws.AddPreferencesEndpoints(r)
- ws.AddAnalysisEndpoints(r)
+func (ws *WebService) RequireData(minimumStatus WebServiceStatus) func(ctx *gin.Context) {
+ return func(ctx *gin.Context) {
+ if ws.status < minimumStatus {
+ ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "no data"})
+ }
+ }
}
-func (w *webservice) AddLocalHTML(path string) error {
- if !w.localhtmlused {
- // Clear embedded html filesystem
- w.UnionFS = UnionFS{}
- w.localhtmlused = true
- }
- // Override embedded HTML if asked to
- stat, err := os.Stat(path)
- if err == nil && stat.IsDir() {
- // Use local files if they exist
- ui.Info().Msgf("Adding local HTML folder %v", path)
- w.AddFS(http.FS(os.DirFS(path)))
+func WithCert(certfile, keyfile string) optionsetter {
+ return func(ws *WebService) error {
+ // create certificate from pem strings directly
+ var cert tls.Certificate
+ var err error
+ if util.FileExists(certfile) && util.FileExists(keyfile) {
+ cert, err = tls.LoadX509KeyPair(certfile, keyfile)
+ } else {
+ cert, err = tls.X509KeyPair([]byte(certfile), []byte(keyfile))
+ }
+
+ if err != nil {
+ return err
+ }
+
+ ws.protocol = "https"
+ ws.srv.TLSConfig = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ }
+
return nil
}
- return fmt.Errorf("could not add local HTML folder %v, failure: %v", path, err)
}
-func (ws *webservice) Analyze(path string) error {
- if ws.status != NoData && ws.status == Ready {
- return errors.New("Adalanche is not ready to load new data")
+func WithLocalHTML(path string) optionsetter {
+ return func(ws *WebService) error {
+ if !ws.localhtmlused {
+ // Clear embedded html filesystem
+ ws.UnionFS = UnionFS{}
+ ws.localhtmlused = true
+ }
+ // Override embedded HTML if asked to
+ stat, err := os.Stat(path)
+ if err == nil && stat.IsDir() {
+ // Use local files if they exist
+ ui.Info().Msgf("Adding local HTML folder %v", path)
+ ws.AddFS(http.FS(os.DirFS(path)))
+ return nil
+ }
+ return fmt.Errorf("could not add local HTML folder %v, failure: %v", path, err)
}
+}
- ws.status = Analyzing
- objs, err := engine.Run(path)
- ws.Objs = objs
+func (ws *WebService) Init(r gin.IRoutes) {
+ // Add stock functions
+ ws.Initialized = true
+
+ AddUIEndpoints(ws)
+ AddPreferencesEndpoints(ws)
+ AddAnalysisEndpoints(ws)
+}
+func (ws *WebService) Analyze(paths ...string) error {
+ if ws.status != NoData && ws.status != Ready {
+ return errors.New("Adalanche is already busy loading data")
+ }
+
+ ws.status = Analyzing
+ objs, err := engine.Run(paths...)
if err != nil {
ws.status = Error
return err
}
+ ws.Objs = objs
ws.status = PostAnalyzing
engine.PostProcess(objs)
ws.status = Ready
-
return nil
}
-func (ws *webservice) QuitChan() <-chan bool {
+func (ws *WebService) QuitChan() <-chan bool {
return ws.quit
}
-func (ws *webservice) Quit() {
+func (ws *WebService) Quit() {
close(ws.quit)
}
-func (ws *webservice) Start(bind string) error {
+func (ws *WebService) Start(bind string) error {
if !ws.Initialized {
ws.Init(ws.Router)
}
- // Profiling
- pprof.Register(ws.Router)
-
- ws.srv = &http.Server{
- Addr: bind,
- Handler: ws.Router,
- }
+ ws.srv.Addr = bind
+ ws.srv.Handler = ws.engine
ws.Router.GET("/", func(c *gin.Context) {
indexfile, err := ws.UnionFS.Open("index.html")
@@ -185,22 +239,35 @@ func (ws *webservice) Start(bind string) error {
ui.Error().Msgf("Could not render template index.html: %v", err)
}
})
- ws.Router.Use(static.Serve("/", ws.UnionFS))
+ ws.engine.Use(static.Serve("", ws.UnionFS))
- // w.Router.StaticFS("/", http.FS(w.UnionFS))
+ // bind to port and start listening for requests
+ conn, err := net.Listen("tcp", ws.srv.Addr)
+ if err != nil {
+ return err
+ }
- go func() {
- if err := ws.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- ui.Fatal().Msgf("Problem launching webservice listener: %s", err)
- }
- }()
+ switch ws.protocol {
+ case "http":
+ go func() {
+ if err := ws.srv.Serve(conn); err != nil && err != http.ErrServerClosed {
+ ui.Fatal().Msgf("Problem launching webservice listener: %s", err)
+ }
+ }()
+ case "https":
+ go func() {
+ if err := ws.srv.ServeTLS(conn, "", ""); err != nil && err != http.ErrServerClosed {
+ ui.Fatal().Msgf("Problem launching webservice listener: %s", err)
+ }
+ }()
+ }
- ui.Info().Msgf("Listening - navigate to http://%v/ ... (ctrl-c or similar to quit)", bind)
+ ui.Info().Msgf("Adalanche Web Service listening at %v://%v/ ... (ctrl-c or similar to quit)", ws.protocol, bind)
return nil
}
-func (ws *webservice) ServeTemplate(c *gin.Context, path string, data any) {
+func (ws *WebService) ServeTemplate(c *gin.Context, path string, data any) {
templatefile, err := ws.UnionFS.Open(path)
if err != nil {
ui.Fatal().Msgf("Could not open template %v: %v", path, err)
@@ -213,3 +280,10 @@ func (ws *webservice) ServeTemplate(c *gin.Context, path string, data any) {
}
template.Execute(c.Writer, data)
}
+
+func WithProfiling() func(*WebService) {
+ return func(ws *WebService) {
+ // Profiling
+ pprof.Register(ws.Router)
+ }
+}
diff --git a/modules/analyze/webservicefuncs.go b/modules/analyze/webservicefuncs.go
index ad5a6903..e71b81c0 100644
--- a/modules/analyze/webservicefuncs.go
+++ b/modules/analyze/webservicefuncs.go
@@ -22,10 +22,12 @@ import (
"github.com/lkarlslund/adalanche/modules/windowssecurity"
)
-func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
+func AddUIEndpoints(ws *WebService) {
// Lists available edges that Adalanche understands - this allows us to expand functionality
// in the code, without touching the HTML
- router.GET("/filteroptions", func(c *gin.Context) {
+ backend := ws.Router.Group("backend")
+
+ backend.GET("/filteroptions", func(c *gin.Context) {
type filterinfo struct {
Name string `json:"name"`
Lookup string `json:"lookup"`
@@ -66,7 +68,7 @@ func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
})
// Checks a LDAP style query for input errors, and returns a hint to the user
// It supports the include,exclude syntax specific to this program
- router.GET("/validatequery", func(c *gin.Context) {
+ backend.GET("/validatequery", func(c *gin.Context) {
querytext := strings.Trim(c.Query("query"), " \n\r")
if querytext != "" {
_, err := query.ParseLDAPQueryStrict(querytext, ws.Objs)
@@ -78,11 +80,11 @@ func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
c.JSON(200, gin.H{"success": true})
})
- router.GET("/types", func(c *gin.Context) {
+ backend.GET("/types", func(c *gin.Context) {
c.JSON(200, typeInfos)
})
- router.GET("/statistics", func(c *gin.Context) {
+ backend.GET("/statistics", func(c *gin.Context) {
var result struct {
Adalanche map[string]string `json:"adalanche"`
Statistics map[string]int `json:"statistics"`
@@ -92,6 +94,7 @@ func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
result.Adalanche["program"] = version.Program
result.Adalanche["version"] = version.Version
result.Adalanche["commit"] = version.Commit
+ result.Adalanche["status"] = ws.status.String()
result.Statistics = make(map[string]int)
@@ -117,7 +120,7 @@ func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
c.JSON(200, result)
})
- router.GET("/progress", func(c *gin.Context) {
+ backend.GET("/progress", func(c *gin.Context) {
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
@@ -134,8 +137,16 @@ func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
return pbr[i].StartTime.Before(pbr[j].StartTime)
})
+ output := struct {
+ Status string `json:"status"`
+ Progress []ui.ProgressReport `json:"progressbars"`
+ }{
+ Status: ws.status.String(),
+ Progress: pbr,
+ }
+
conn.SetWriteDeadline(time.Now().Add(time.Second * 15))
- err = conn.WriteJSON(pbr)
+ err = conn.WriteJSON(output)
if err != nil {
return
}
@@ -144,19 +155,43 @@ func (ws *webservice) AddUIEndpoints(router gin.IRoutes) {
}
})
+ // Ready status
+ backend.GET("/status", func(c *gin.Context) {
+ c.JSON(200, gin.H{"status": ws.status.String()})
+ })
+ backend.GET("/await/:status", func(c *gin.Context) {
+ waitfor, err := WebServiceStatusString(c.Param("status"))
+ if err != nil {
+ c.Status(500)
+ return
+ }
+
+ for ws.status != waitfor {
+ time.Sleep(time.Millisecond * 10)
+ }
+
+ c.JSON(200, gin.H{"status": ws.status.String()})
+ })
+
+ // Shutdown
+ backend.GET("/quit", func(c *gin.Context) {
+ ws.quit <- true
+ })
}
-func (ws *webservice) AddPreferencesEndpoints(router gin.IRoutes) {
+func AddPreferencesEndpoints(ws *WebService) {
// Saved preferences
err := settings.Load()
if err != nil {
ui.Warn().Msgf("Problem loading preferences: %v", err)
}
- router.GET("/preferences", func(c *gin.Context) {
+ preferences := ws.Router.Group("preferences")
+
+ preferences.GET("", func(c *gin.Context) {
c.JSON(200, settings.All())
})
- router.POST("/preferences", func(c *gin.Context) {
+ preferences.POST("", func(c *gin.Context) {
var prefsmap = make(map[string]any)
err := c.BindJSON(&prefsmap)
if err != nil {
@@ -169,13 +204,13 @@ func (ws *webservice) AddPreferencesEndpoints(router gin.IRoutes) {
settings.Save()
})
- router.GET("/preferences/:key", func(c *gin.Context) {
+ preferences.GET(":key", func(c *gin.Context) {
key := c.Param("key")
out, _ := json.Marshal(settings.Get(key))
c.Writer.Write(out)
})
- router.GET("/preferences/:key/:value", func(c *gin.Context) {
+ preferences.GET(":key/:value", func(c *gin.Context) {
key := c.Param("key")
value := c.Param("value")
settings.Set(key, value)
@@ -183,10 +218,13 @@ func (ws *webservice) AddPreferencesEndpoints(router gin.IRoutes) {
})
}
-func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
+func AddAnalysisEndpoints(ws *WebService) {
- // Returns JSON descruibing an object located by distinguishedName, sid or guid
- router.GET("/details/:locateby/:id", func(c *gin.Context) {
+ api := ws.Router.Group("/api")
+ api.Use(ws.RequireData(Ready))
+
+ // Returns JSON describing an object located by distinguishedName, sid or guid
+ api.GET("/details/:locateby/:id", func(c *gin.Context) {
var o *engine.Object
var found bool
switch strings.ToLower(c.Param("locateby")) {
@@ -262,7 +300,7 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
})
// Graph based query analysis - core functionality
- router.POST("/analyzegraph", func(c *gin.Context) {
+ api.POST("/graphquery", func(c *gin.Context) {
params := make(map[string]string)
err := c.ShouldBindJSON(¶ms)
// err := c.Request.ParseForm()
@@ -362,14 +400,14 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
}
// var methods engine.EdgeBitmap
- var edges_f, egdes_m, edges_l engine.EdgeBitmap
+ var edges_f, edges_m, edges_l engine.EdgeBitmap
var objecttypes_f, objecttypes_m, objecttypes_l []engine.ObjectType
- for potentialfilter := range c.Request.PostForm {
- if len(potentialfilter) < 7 {
+ for potentialfilter, _ := range params {
+ if len(potentialfilter) < 8 {
continue
}
if strings.HasPrefix(potentialfilter, "edge_") {
- prefix := potentialfilter[4 : len(potentialfilter)-2]
+ prefix := potentialfilter[5 : len(potentialfilter)-2]
suffix := potentialfilter[len(potentialfilter)-2:]
edge := engine.LookupEdge(prefix)
if edge == engine.NonExistingEdge {
@@ -379,7 +417,7 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
case "_f":
edges_f = edges_f.Set(edge)
case "_m":
- egdes_m = egdes_m.Set(edge)
+ edges_m = edges_m.Set(edge)
case "_l":
edges_l = edges_l.Set(edge)
}
@@ -404,16 +442,16 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
}
// Are we using the new format FML? The just choose the old format methods for FML
- if edges_f.Count() == 0 && egdes_m.Count() == 0 && edges_l.Count() == 0 {
+ if edges_f.Count() == 0 && edges_m.Count() == 0 && edges_l.Count() == 0 {
// Spread the choices to FML
edges_f = engine.AllEdgesBitmap
- egdes_m = engine.AllEdgesBitmap
+ edges_m = engine.AllEdgesBitmap
edges_l = engine.AllEdgesBitmap
}
opts.Objects = ws.Objs
opts.MethodsF = edges_f
- opts.MethodsM = egdes_m
+ opts.MethodsM = edges_m
opts.MethodsL = edges_l
opts.ObjectTypesF = objecttypes_f
opts.ObjectTypesM = objecttypes_m
@@ -484,56 +522,37 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
c.JSON(200, response)
})
- router.GET("/query/objects/:query", func(c *gin.Context) {
- querystr := c.Param("query")
-
- rest, includequery, err := query.ParseLDAPQuery(querystr, ws.Objs)
- if err != nil {
- c.String(500, err.Error())
- return
- }
- if rest != "" {
- if rest[0] != ',' {
- c.JSON(400, gin.H{"error": fmt.Sprintf("Error parsing ldap query: %v", err)})
- return
- }
- }
-
- objects := ws.Objs.Filter(func(o *engine.Object) bool {
- return includequery.Evaluate(o)
- })
-
- dns := make([]string, 0, objects.Len())
-
- objects.Iterate(func(o *engine.Object) bool {
- dns = append(dns, o.DN())
- return true
- })
-
- c.JSON(200, dns)
- })
- router.GET("/query/details/:query", func(c *gin.Context) {
- querystr := c.Param("query")
-
- rest, includequery, err := query.ParseLDAPQuery(querystr, ws.Objs)
- if err != nil {
- c.String(500, err.Error())
- return
- }
- if rest != "" {
- if rest[0] != ',' {
- c.JSON(400, gin.H{"error": fmt.Sprintf("Error parsing ldap query: %v", err)})
- return
- }
- }
-
- objects := ws.Objs.Filter(func(o *engine.Object) bool {
- return includequery.Evaluate(o)
- })
-
- c.JSON(200, objects.AsSlice())
- })
- router.GET("/tree", func(c *gin.Context) {
+ // Get list of objects
+ // api.GET("/listdns/:query", func(c *gin.Context) {
+ // querystr := c.Param("query")
+
+ // rest, includequery, err := query.ParseLDAPQuery(querystr, ws.Objs)
+ // if err != nil {
+ // c.String(500, err.Error())
+ // return
+ // }
+ // if rest != "" {
+ // if rest[0] != ',' {
+ // c.JSON(400, gin.H{"error": fmt.Sprintf("Error parsing ldap query: %v", err)})
+ // return
+ // }
+ // }
+
+ // objects := ws.Objs.Filter(func(o *engine.Object) bool {
+ // return includequery.Evaluate(o)
+ // })
+
+ // dns := make([]string, 0, objects.Len())
+
+ // objects.Iterate(func(o *engine.Object) bool {
+ // dns = append(dns, o.DN())
+ // return true
+ // })
+
+ // c.JSON(200, dns)
+ // })
+
+ api.GET("/tree", func(c *gin.Context) {
idstr := c.Query("id")
var children engine.ObjectSlice
@@ -577,7 +596,7 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
// Shutdown
- router.GET("/export-words", func(c *gin.Context) {
+ api.GET("/export-words", func(c *gin.Context) {
split := c.Query("split") == "true"
// Set header for download as a text file
@@ -659,29 +678,6 @@ func (ws *webservice) AddAnalysisEndpoints(router gin.IRoutes) {
}
})
- // Ready status
- router.GET("/status", func(c *gin.Context) {
- c.JSON(200, gin.H{"status": ws.status})
- })
- router.GET("/await/:status", func(c *gin.Context) {
- waitfor, err := WebServiceStatusString(c.Param("status"))
- if err != nil {
- c.Status(500)
- return
- }
-
- for ws.status != waitfor {
- time.Sleep(time.Millisecond * 10)
- }
-
- c.JSON(200, gin.H{"status": ws.status})
- })
-
- // Shutdown
- router.GET("/quit", func(c *gin.Context) {
- ws.quit <- true
- })
-
}
func extractwords(input string, split bool) []string {
diff --git a/modules/cli/main.go b/modules/cli/main.go
index f58fcc0a..a454ac49 100644
--- a/modules/cli/main.go
+++ b/modules/cli/main.go
@@ -15,17 +15,20 @@ import (
"github.com/lkarlslund/adalanche/modules/ui"
"github.com/lkarlslund/adalanche/modules/version"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+ "github.com/spf13/viper"
)
var (
Root = &cobra.Command{
- Use: "adalanche",
- Short: version.VersionStringShort(),
- SilenceErrors: true,
- SilenceUsage: true,
+ Use: "adalanche",
+ Short: version.VersionStringShort(),
+ SilenceErrors: true,
+ SilenceUsage: true,
+ TraverseChildren: true,
}
+ loglevel = Root.PersistentFlags().String("loglevel", "info", "Console log level")
- loglevel = Root.PersistentFlags().String("loglevel", "info", "Console log level")
logfile = Root.PersistentFlags().String("logfile", "", "File to log to")
logfilelevel = Root.PersistentFlags().String("logfilelevel", "info", "Log file log level")
logzerotime = Root.PersistentFlags().Bool("logzerotime", false, "Logged timestamps start from zero when program launches")
@@ -35,7 +38,8 @@ var (
dofgtrace = Root.PersistentFlags().Bool("fgtrace", false, "Save CPU trace start to end of processing in datapath")
cpuprofiletimeout = Root.PersistentFlags().Int32("cpuprofiletimeout", 0, "CPU profiling timeout in seconds (0 means no timeout)")
- datapath = Root.PersistentFlags().String("datapath", "data", "folder to store and read data")
+ // also available for subcommands
+ Datapath = Root.PersistentFlags().String("datapath", "data", "folder to store and read data")
versionCmd = &cobra.Command{
Use: "version",
@@ -46,126 +50,171 @@ var (
},
}
- OverrideArgs []string
+ OverrideArgs []string
+ stopprofile = make(chan bool, 5)
+ stopfgtrace = make(chan bool, 5)
+ profilewriters sync.WaitGroup
)
-func init() {
- Root.AddCommand(versionCmd)
+func bindFlags(cmd *cobra.Command) {
+ cmd.PersistentFlags().VisitAll(func(f *pflag.Flag) {
+ // Apply the viper config value to the flag when the flag is not set and viper has a value
+ if !f.Changed && viper.IsSet(f.Name) {
+ if sv, ok := f.Value.(pflag.SliceValue); ok {
+ sv.Replace(viper.GetStringSlice(f.Name))
+ } else {
+ f.Value.Set(viper.GetString(f.Name))
+ }
+ }
+ })
+ cmd.Flags().VisitAll(func(f *pflag.Flag) {
+ // Apply the viper config value to the flag when the flag is not set and viper has a value
+ if !f.Changed && viper.IsSet(f.Name) {
+ if sv, ok := f.Value.(pflag.SliceValue); ok {
+ sv.Replace(viper.GetStringSlice(f.Name))
+ } else {
+ f.Value.Set(viper.GetString(f.Name))
+ }
+ }
+ })
+ for _, subCommand := range cmd.Commands() {
+ bindFlags(subCommand)
+ }
}
-func Run() error {
- args := os.Args[1:]
- if len(args) == 0 {
- args = OverrideArgs
+func loadConfiguration(cmd *cobra.Command) {
+ // Bind environment variables
+ viper.SetEnvPrefix("ADALANCHE_")
+ viper.AutomaticEnv()
+
+ // Use config file from the flag.
+ viper.SetConfigFile(filepath.Join(*Datapath, "configuration.yaml"))
+ if err := viper.ReadInConfig(); err == nil {
+ ui.Info().Msgf("Using configuration file: %v", viper.ConfigFileUsed())
+ } else {
+ ui.Error().Msgf("Problem loading settings: %v", err.Error())
}
- Root.SetArgs(args)
- Root.ParseFlags(args)
+ bindFlags(cmd)
+}
- ui.Zerotime = *logzerotime
+func init() {
+ cobra.OnInitialize(func() {
+ loadConfiguration(Root)
+ })
- ll, err := ui.LogLevelString(*loglevel)
- if err != nil {
- ui.Error().Msgf("Invalid log level: %v - use one of: %v", *loglevel, ui.LogLevelStrings())
- } else {
- ui.SetLoglevel(ll)
- }
+ Root.AddCommand(versionCmd)
+ Root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
+
+ ui.Zerotime = *logzerotime
- if *logfile != "" {
- ll, err = ui.LogLevelString(*logfilelevel)
+ ll, err := ui.LogLevelString(*loglevel)
if err != nil {
- ui.Error().Msgf("Invalid log file log level: %v - use one of: %v", *logfilelevel, ui.LogLevelStrings())
+ ui.Error().Msgf("Invalid log level: %v - use one of: %v", *loglevel, ui.LogLevelStrings())
} else {
- ui.SetLogFile(*logfile, ll)
+ ui.SetLoglevel(ll)
}
- }
-
- ui.Info().Msg(version.VersionString())
- if *embeddedprofiler {
- go func() {
- port := 6060
- for {
- err := http.ListenAndServe(fmt.Sprintf("localhost:%v", port), nil)
- if err != nil {
- ui.Error().Msgf("Profiling listener failed: %v, trying with new port", err)
- port++
- } else {
- break
- }
+ if *logfile != "" {
+ ll, err = ui.LogLevelString(*logfilelevel)
+ if err != nil {
+ ui.Error().Msgf("Invalid log file log level: %v - use one of: %v", *logfilelevel, ui.LogLevelStrings())
+ } else {
+ ui.SetLogFile(*logfile, ll)
}
- ui.Info().Msgf("Profiling listener started on port %v", port)
- }()
- }
+ }
- stopprofile := make(chan bool, 5)
- stopfgtrace := make(chan bool, 5)
- var profilewriters sync.WaitGroup
+ ui.Info().Msg(version.VersionString())
- if *dofgtrace {
- tracefile := filepath.Join(*datapath, "adalanche-fgtrace-"+time.Now().Format("06010215040506")+".json")
- trace := fgtrace.Config{Dst: fgtrace.File(tracefile)}.Trace()
+ if *embeddedprofiler {
+ go func() {
+ port := 6060
+ for {
+ err := http.ListenAndServe(fmt.Sprintf("localhost:%v", port), nil)
+ if err != nil {
+ ui.Error().Msgf("Profiling listener failed: %v, trying with new port", err)
+ port++
+ } else {
+ break
+ }
+ }
+ ui.Info().Msgf("Profiling listener started on port %v", port)
+ }()
+ }
- profilewriters.Add(1)
+ if *dofgtrace {
+ tracefile := filepath.Join(*Datapath, "adalanche-fgtrace-"+time.Now().Format("06010215040506")+".json")
+ trace := fgtrace.Config{Dst: fgtrace.File(tracefile)}.Trace()
- go func() {
- <-stopfgtrace
- err = trace.Stop()
- if err != nil {
- ui.Error().Msgf("Problem stopping fgtrace: %v", err)
- }
- profilewriters.Done()
- }()
+ profilewriters.Add(1)
- if *cpuprofiletimeout > 0 {
go func() {
- <-time.After(time.Second * (time.Duration(*cpuprofiletimeout)))
- stopfgtrace <- true
+ <-stopfgtrace
+ err = trace.Stop()
+ if err != nil {
+ ui.Error().Msgf("Problem stopping fgtrace: %v", err)
+ }
+ profilewriters.Done()
}()
- }
- }
+ if *cpuprofiletimeout > 0 {
+ go func() {
+ <-time.After(time.Second * (time.Duration(*cpuprofiletimeout)))
+ stopfgtrace <- true
+ }()
+ }
- if *cpuprofile {
- pproffile := filepath.Join(*datapath, "adalanche-cpuprofile-"+time.Now().Format("06010215040506")+".pprof")
- f, err := os.Create(pproffile)
- if err != nil {
- return fmt.Errorf("Could not set up CPU profiling in file %v: %v", pproffile, err)
}
- pprof.StartCPUProfile(f)
- profilewriters.Add(1)
+ if *cpuprofile {
+ pproffile := filepath.Join(*Datapath, "adalanche-cpuprofile-"+time.Now().Format("06010215040506")+".pprof")
+ f, err := os.Create(pproffile)
+ if err != nil {
+ return fmt.Errorf("Could not set up CPU profiling in file %v: %v", pproffile, err)
+ }
+ pprof.StartCPUProfile(f)
- go func() {
- <-stopprofile
- pprof.StopCPUProfile()
- profilewriters.Done()
- }()
+ profilewriters.Add(1)
- if *cpuprofiletimeout > 0 {
go func() {
- <-time.After(time.Second * (time.Duration(*cpuprofiletimeout)))
- stopprofile <- true
+ <-stopprofile
+ pprof.StopCPUProfile()
+ profilewriters.Done()
}()
+
+ if *cpuprofiletimeout > 0 {
+ go func() {
+ <-time.After(time.Second * (time.Duration(*cpuprofiletimeout)))
+ stopprofile <- true
+ }()
+ }
}
- }
- debug.SetGCPercent(10)
+ debug.SetGCPercent(10)
- // Ensure the data folder is available
- if _, err := os.Stat(*datapath); os.IsNotExist(err) {
- err = os.MkdirAll(*datapath, 0711)
- if err != nil {
- return fmt.Errorf("Could not create data folder %v: %v", datapath, err)
+ // Ensure the data folder is available
+ if _, err := os.Stat(*Datapath); os.IsNotExist(err) {
+ err = os.MkdirAll(*Datapath, 0711)
+ if err != nil {
+ return fmt.Errorf("Could not create data folder %v: %v", Datapath, err)
+ }
}
+ return nil
}
+ Root.PersistentPostRunE = func(cmd *cobra.Command, args []string) error {
+ stopfgtrace <- true
+ stopprofile <- true
+ profilewriters.Wait()
+ return nil
+ }
+}
- err = Root.Execute()
-
- stopfgtrace <- true
- stopprofile <- true
+func CliMainEntryPoint() error {
+ if len(os.Args[1:]) == 0 {
+ Root.SetArgs(OverrideArgs)
+ }
- profilewriters.Wait()
+ err := Root.Execute()
if err == nil {
ui.Info().Msgf("Terminating successfully")
diff --git a/modules/dedup/unique.go b/modules/dedup/unique.go
index 7c4b1a92..ac9aa186 100644
--- a/modules/dedup/unique.go
+++ b/modules/dedup/unique.go
@@ -1,6 +1,3 @@
-//go:build go1.23
-// +build go1.23
-
package dedup
import "unique"
diff --git a/modules/engine/edge.go b/modules/engine/edge.go
index f57a4c88..ffee6559 100644
--- a/modules/engine/edge.go
+++ b/modules/engine/edge.go
@@ -118,6 +118,18 @@ func (eb *EdgeBitmap) AtomicClear(edge Edge) {
}
}
+func (eb *EdgeBitmap) PartialAtomicLoad() (edges EdgeBitmap) {
+ index := 0
+ for {
+ edges[index] = atomic.LoadUint64(&eb[index])
+ index++
+ if index == PMBSIZE {
+ break
+ }
+ }
+ return edges
+}
+
func (eb *EdgeBitmap) AtomicAnd(edges EdgeBitmap) {
index := 0
for {
@@ -345,7 +357,7 @@ const (
func (m EdgeBitmap) IsSet(edge Edge) bool {
index, bits := bitIndex(edge)
- return (m[index] & bits) != 0
+ return (atomic.LoadUint64(&m[index]) & bits) != 0
}
func (m EdgeBitmap) MaxProbability(source, target *Object) Probability {
diff --git a/modules/engine/edgeconnplus.go b/modules/engine/edgeconnplus.go
index 5320cd4d..b9b6d3d7 100644
--- a/modules/engine/edgeconnplus.go
+++ b/modules/engine/edgeconnplus.go
@@ -23,7 +23,7 @@ func (c Connection) LessThan(c2 Connection) bool {
func (ecp *EdgeConnectionsPlus) Range(rf func(o *Object, eb EdgeBitmap) bool) {
ecp.Gonk.Range(func(c Connection) bool {
- return rf(c.target, c.edges)
+ return rf(c.target, c.edges.PartialAtomicLoad())
})
}
diff --git a/modules/engine/run.go b/modules/engine/run.go
index b2b18c1e..48e88562 100644
--- a/modules/engine/run.go
+++ b/modules/engine/run.go
@@ -14,7 +14,7 @@ import (
)
// Loads, processes and merges everything. It's magic, just in code
-func Run(path string) (*Objects, error) {
+func Run(paths ...string) (*Objects, error) {
starttime := time.Now()
var loaders []Loader
@@ -37,20 +37,24 @@ func Run(path string) (*Objects, error) {
// Enable deduplication
// DedupValues(true)
- lo, err := Load(loaders, path, func(cur, max int) {
- if max > 0 {
- loadbar.ChangeMax(int64(max))
- } else if max < 0 {
- loadbar.ChangeMax(loadbar.GetMax() + int64(-max))
- }
- if cur > 0 {
- loadbar.Set(int64(cur))
- } else {
- loadbar.Add(int64(-cur))
+ var lo []loaderobjects
+ for _, path := range paths {
+ los, err := Load(loaders, path, func(cur, max int) {
+ if max > 0 {
+ loadbar.ChangeMax(int64(max))
+ } else if max < 0 {
+ loadbar.ChangeMax(loadbar.GetMax() + int64(-max))
+ }
+ if cur > 0 {
+ loadbar.Set(int64(cur))
+ } else {
+ loadbar.Add(int64(-cur))
+ }
+ })
+ if err != nil {
+ return nil, err
}
- })
- if err != nil {
- return nil, err
+ lo = append(lo, los...)
}
loadbar.Finish()
@@ -100,9 +104,6 @@ func PostProcess(ao *Objects) {
Process(ao, fmt.Sprintf("Postprocessing global objects priority %v", priority.String()), -1, priority)
}
- // Free deduplication map
- // DedupValues(false)
-
ui.Info().Msgf("Post-processing completed in %v", time.Since(starttime))
type statentry struct {
@@ -146,15 +147,6 @@ func PostProcess(ao *Objects) {
ui.Debug().Msgf("%v: %v", se.name, se.count)
}
- // dedupStats := dedup.D.Statistics()
- // ui.Debug().Msgf("Deduplicator stats:")
- // ui.Debug().Msgf("%v items added using %v bytes in memory", dedupStats.ItemsAdded, dedupStats.BytesInMemory)
- // ui.Debug().Msgf("%v items not allocated saving %v bytes of memory", dedupStats.ItemsSaved, dedupStats.BytesSaved)
- // ui.Debug().Msgf("%v items removed (memory stats unavailable)", dedupStats.ItemsRemoved)
- // ui.Debug().Msgf("%v collisions detected (first at %v objects)", dedupStats.Collisions, dedupStats.FirstCollisionDetected)
- // ui.Debug().Msgf("%v keepalive objects added", dedupStats.KeepAliveItemsAdded)
- // ui.Debug().Msgf("%v keepalive objects removed", dedupStats.KeepAliveItemsRemoved)
-
// Try to recover some memory
dedup.D.Flush()
diff --git a/modules/integrations/activedirectory/collect/cli.go b/modules/integrations/activedirectory/collect/cli.go
index 3145f36f..1951232b 100644
--- a/modules/integrations/activedirectory/collect/cli.go
+++ b/modules/integrations/activedirectory/collect/cli.go
@@ -196,7 +196,7 @@ func PreRun(cmd *cobra.Command, args []string) error {
func Execute(cmd *cobra.Command, args []string) error {
datapath := "data"
- if idp := cmd.InheritedFlags().Lookup("datapath"); idp != nil {
+ if idp := cmd.Flag("datapath"); idp != nil {
datapath = idp.Value.String()
}
diff --git a/modules/integrations/activedirectory/collect/ldap_common.go b/modules/integrations/activedirectory/collect/ldap_common.go
index 78b28295..16116e8f 100644
--- a/modules/integrations/activedirectory/collect/ldap_common.go
+++ b/modules/integrations/activedirectory/collect/ldap_common.go
@@ -136,20 +136,24 @@ const (
)
type LDAPOptions struct {
- Domain string
- Server string
- Port uint16
- User string
- Password string
- AuthDomain string
- AuthMode AuthMode
- TLSMode TLSmode
- Channelbinding bool
- SizeLimit int
-
- IgnoreCert bool
-
- Debug bool
+ Domain string `json:"domain"`
+ Server string `json:"server"`
+ Port uint16 `json:"port"`
+ User string `json:"user"`
+ Password string `json:"password"`
+ AuthDomain string `json:"authdomain"`
+ AuthMode AuthMode `json:"authmode"`
+ TLSMode TLSmode `json:"tlsmode"`
+ Channelbinding bool `json:"channelbinding"`
+ SizeLimit int `json:"sizelimit"`
+
+ IgnoreCert bool `json:"ignorecert"`
+
+ Debug bool `json:"debug"`
+}
+
+func NewLDAPOptions() LDAPOptions {
+ return LDAPOptions{}
}
type objectCallbackFunc func(ro *activedirectory.RawObject) error
diff --git a/modules/integrations/localmachine/analyze/loader.go b/modules/integrations/localmachine/analyze/loader.go
index 08731a4f..6a251410 100644
--- a/modules/integrations/localmachine/analyze/loader.go
+++ b/modules/integrations/localmachine/analyze/loader.go
@@ -12,7 +12,7 @@ import (
"github.com/mailru/easyjson"
)
-const loadername = "LocalMachine JSON file"
+const loadername = "Local Machine JSON file"
var (
loader = engine.AddLoader(func() engine.Loader { return &LocalMachineLoader{} })
diff --git a/modules/util/json.go b/modules/util/json.go
new file mode 100644
index 00000000..3db25532
--- /dev/null
+++ b/modules/util/json.go
@@ -0,0 +1,31 @@
+package util
+
+import (
+ "encoding/json"
+ "os"
+)
+
+// Takes a filename and any object and writes its JSON representation to the file, returns an error if it fails.
+func WriteJSON(filename string, obj interface{}) (err error) {
+ file, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ encoder := json.NewEncoder(file)
+ encoder.SetIndent("", "\t")
+ err = encoder.Encode(obj)
+ return err
+}
+
+// Takes a filename and reads the JSON representation of an object into it, returns an error if it fails.
+func ReadJSON(filename string, obj interface{}) (err error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ decoder := json.NewDecoder(file)
+ err = decoder.Decode(obj)
+ return err
+}
diff --git a/modules/util/util.go b/modules/util/util.go
index 1f364dac..3608a6c5 100644
--- a/modules/util/util.go
+++ b/modules/util/util.go
@@ -1,7 +1,10 @@
package util
import (
+ "fmt"
"math/rand"
+ "os"
+ "path/filepath"
"regexp"
"strconv"
"strings"
@@ -45,7 +48,7 @@ func SwapUUIDEndianess(u uuid.UUID) uuid.UUID {
return r
}
-func ParseBool(input string) (bool, error) {
+func ParseBool(input string, defvalue ...bool) (bool, error) {
result, err := strconv.ParseBool(input)
if err == nil {
return result, err
@@ -56,6 +59,9 @@ func ParseBool(input string) (bool, error) {
case "off", "Off":
return false, nil
}
+ if len(defvalue) > 0 && err != nil {
+ return defvalue[0], err
+ }
return result, err
}
@@ -182,3 +188,37 @@ func DomainSuffixToDomainContext(domain string) string {
parts := strings.Split(domain, ".")
return strings.ToLower("dc=" + strings.Join(parts, ",dc="))
}
+
+func ExePath() (string, error) {
+ prog := os.Args[0]
+ p, err := filepath.Abs(prog)
+ if err != nil {
+ return "", err
+ }
+ fi, err := os.Stat(p)
+ if err == nil {
+ if !fi.Mode().IsDir() {
+ return p, nil
+ }
+ err = fmt.Errorf("%s is directory", p)
+ }
+ if filepath.Ext(p) == "" {
+ p += ".exe"
+ fi, err := os.Stat(p)
+ if err == nil {
+ if !fi.Mode().IsDir() {
+ return p, nil
+ }
+ err = fmt.Errorf("%s is directory", p)
+ }
+ }
+ return "", err
+}
+
+func FileExists(name string) bool {
+ _, err := os.Stat(name)
+ if os.IsNotExist(err) {
+ return false
+ }
+ return true
+}