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 = "

" + data.adalanche.program + "

" + + statustext = + "

" + + 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 @@ - - - - -Twitter Logo - - 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
- @lkarlslund / @lkarlslund / @lkarlslund
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 +}