From 2c07c19c43c09ed0d1ea093649c85dbed4cc4294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 13:29:52 +0300 Subject: [PATCH 1/7] rename pkg/templates pkg/template --- .gitignore | 3 + CONTRIBUTING.md | 2 +- cmd/vt/main.go | 4 +- go.mod | 6 +- go.sum | 44 +---- internal/cli/list.go | 6 +- internal/cli/ps.go | 4 +- internal/cli/start.go | 4 +- internal/cli/stop.go | 4 +- pkg/provider/dockercompose/dockercompose.go | 8 +- pkg/provider/dockercompose/utils.go | 4 +- pkg/provider/provider.go | 8 +- pkg/template/downloader.go | 63 +++++++ pkg/template/parser.go | 24 +++ pkg/template/parser_test.go | 59 ++++++ pkg/template/template.go | 195 ++++++++++++++++++++ pkg/template/template_test.go | 33 ++++ pkg/template/validator.go | 92 +++++++++ pkg/templates/downloader.go | 2 +- pkg/templates/parser.go | 4 +- pkg/templates/parser_test.go | 2 +- pkg/templates/template.go | 2 +- pkg/templates/template_test.go | 2 +- pkg/templates/validator.go | 2 +- 24 files changed, 508 insertions(+), 69 deletions(-) create mode 100644 pkg/template/downloader.go create mode 100644 pkg/template/parser.go create mode 100644 pkg/template/parser_test.go create mode 100644 pkg/template/template.go create mode 100644 pkg/template/template_test.go create mode 100644 pkg/template/validator.go diff --git a/.gitignore b/.gitignore index 9ab6def..125f4ee 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ go.work.sum *.db .vscode + +CLAUDE.md +.claude \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e972e7..651602c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ We welcome contributions from everyone! Here's how you can contribute: 2. **Create a New Branch** - Make your changes - - If you want to add a new vulnerable target template, check [the example template](./templates/example-template/index.yaml) + - If you want to add a new vulnerable target template, please contribute to our separate templates repository: [vt-templates](https://github.com/HappyHackingSpace/vt-templates). You can check the [example template](https://github.com/HappyHackingSpace/vt-templates/blob/main/cves/vt-2024-53995/index.yaml). - Test your changes locally before submitting 3. **Submit a Pull Request** diff --git a/cmd/vt/main.go b/cmd/vt/main.go index c019883..3ab593f 100644 --- a/cmd/vt/main.go +++ b/cmd/vt/main.go @@ -5,13 +5,13 @@ import ( "github.com/happyhackingspace/vulnerable-target/internal/banner" "github.com/happyhackingspace/vulnerable-target/internal/cli" "github.com/happyhackingspace/vulnerable-target/internal/logger" - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" ) func main() { // Initialize logger and templates explicitly logger.Init() - templates.Init() + tmpl.Init() banner.Print() diff --git a/go.mod b/go.mod index 247a470..dbe0f83 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/compose-spec/compose-go/v2 v2.8.1 github.com/docker/cli v28.3.3+incompatible github.com/docker/compose/v2 v2.39.2 + github.com/go-git/go-git/v5 v5.16.4 github.com/jedib0t/go-pretty/v6 v6.6.6 github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.9.1 @@ -73,7 +74,6 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.16.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -114,7 +114,6 @@ require ( github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/miekg/pkcs11 v1.1.1 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db // indirect @@ -158,7 +157,6 @@ require ( github.com/skeema/knownhosts v1.3.1 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/pflag v1.0.7 // indirect - github.com/src-d/gcfg v1.4.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect @@ -205,8 +203,6 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect - gopkg.in/src-d/go-git.v4 v4.13.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/api v0.32.3 // indirect k8s.io/apimachinery v0.32.3 // indirect diff --git a/go.sum b/go.sum index ea994ae..a5b2093 100644 --- a/go.sum +++ b/go.sum @@ -25,12 +25,12 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6D github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -122,7 +122,6 @@ github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++ github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= @@ -168,15 +167,15 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNE github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c= github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -184,13 +183,14 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= -github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -226,8 +226,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -285,7 +283,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.6.6 h1:LyezkL+1SuqH2z47e5IMQkYUIcs2BD+MnpdPRiRcN0c= github.com/jedib0t/go-pretty/v6 v6.6.6/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc= @@ -303,7 +300,6 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -318,7 +314,6 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -350,8 +345,6 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -423,7 +416,6 @@ github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplU github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -462,9 +454,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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= @@ -473,7 +464,6 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+x github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA= github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= @@ -505,8 +495,6 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= -github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= -github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -541,7 +529,6 @@ github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnn github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -599,10 +586,8 @@ go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -621,7 +606,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -644,10 +628,8 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -674,7 +656,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -684,7 +665,6 @@ golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -709,7 +689,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -724,11 +703,6 @@ 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/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM= gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw= -gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= -gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= -gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= diff --git a/internal/cli/list.go b/internal/cli/list.go index e22f501..270bf77 100644 --- a/internal/cli/list.go +++ b/internal/cli/list.go @@ -1,7 +1,7 @@ package cli import ( - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -46,12 +46,12 @@ var templateCmd = &cobra.Command{ } if list { - templates.ListWithFilter(filter) + tmpl.ListWithFilter(filter) return } if update { - templates.SyncTemplates() + tmpl.SyncTemplates() return } }, diff --git a/internal/cli/ps.go b/internal/cli/ps.go index 766233b..45ae1be 100644 --- a/internal/cli/ps.go +++ b/internal/cli/ps.go @@ -7,7 +7,7 @@ import ( "github.com/happyhackingspace/vulnerable-target/internal/state" "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" "github.com/happyhackingspace/vulnerable-target/pkg/store/disk" - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/jedib0t/go-pretty/v6/table" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -42,7 +42,7 @@ var psCmd = &cobra.Command{ log.Error().Msgf("provider %q not found", deployment.ProviderName) continue } - template, err := templates.GetByID(deployment.TemplateID) + template, err := tmpl.GetByID(deployment.TemplateID) if err != nil { log.Error().Msgf("%v", err) continue diff --git a/internal/cli/start.go b/internal/cli/start.go index 821f828..4befcb1 100644 --- a/internal/cli/start.go +++ b/internal/cli/start.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -38,7 +38,7 @@ var startCmd = &cobra.Command{ log.Fatal().Msgf("provider %s not found", providerName) } - template, err := templates.GetByID(templateID) + template, err := tmpl.GetByID(templateID) if err != nil { log.Fatal().Msgf("%v", err) } diff --git a/internal/cli/stop.go b/internal/cli/stop.go index 5f1bbb8..bd5c003 100644 --- a/internal/cli/stop.go +++ b/internal/cli/stop.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -32,7 +32,7 @@ var stopCmd = &cobra.Command{ log.Fatal().Msgf("provider %s not found", providerName) } - template, err := templates.GetByID(templateID) + template, err := tmpl.GetByID(templateID) if err != nil { log.Fatal().Msgf("%v", err) } diff --git a/pkg/provider/dockercompose/dockercompose.go b/pkg/provider/dockercompose/dockercompose.go index 174cb9c..a635a97 100644 --- a/pkg/provider/dockercompose/dockercompose.go +++ b/pkg/provider/dockercompose/dockercompose.go @@ -7,7 +7,7 @@ import ( "github.com/happyhackingspace/vulnerable-target/internal/state" "github.com/happyhackingspace/vulnerable-target/pkg/provider" "github.com/happyhackingspace/vulnerable-target/pkg/store/disk" - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" ) var _ provider.Provider = &DockerCompose{} @@ -21,7 +21,7 @@ func (d *DockerCompose) Name() string { } // Start launches the vulnerable target environment using Docker Compose. -func (d *DockerCompose) Start(template *templates.Template) error { +func (d *DockerCompose) Start(template *tmpl.Template) error { cfg := disk.NewConfig().WithFileName("deployments.db").WithBucketName("deployments") st, err := state.NewManager(cfg) if err != nil { @@ -57,7 +57,7 @@ func (d *DockerCompose) Start(template *templates.Template) error { } // Stop shuts down the vulnerable target environment using Docker Compose. -func (d *DockerCompose) Stop(template *templates.Template) error { +func (d *DockerCompose) Stop(template *tmpl.Template) error { cfg := disk.NewConfig().WithFileName("deployments.db").WithBucketName("deployments") st, err := state.NewManager(cfg) if err != nil { @@ -97,7 +97,7 @@ func (d *DockerCompose) Stop(template *templates.Template) error { } // Status returns status the vulnerable target environment using Docker Compose. -func (d *DockerCompose) Status(template *templates.Template) (string, error) { +func (d *DockerCompose) Status(template *tmpl.Template) (string, error) { dockerCli, err := createDockerCLI() if err != nil { return "unknown", err diff --git a/pkg/provider/dockercompose/utils.go b/pkg/provider/dockercompose/utils.go index ac4c54b..87ae126 100644 --- a/pkg/provider/dockercompose/utils.go +++ b/pkg/provider/dockercompose/utils.go @@ -13,7 +13,7 @@ import ( "github.com/docker/cli/cli/flags" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose" - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" ) func createDockerCLI() (command.Cli, error) { @@ -31,7 +31,7 @@ func createDockerCLI() (command.Cli, error) { return dockerCli, nil } -func loadComposeProject(template templates.Template) (*types.Project, error) { +func loadComposeProject(template tmpl.Template) (*types.Project, error) { providerConfig, exists := template.Providers["docker-compose"] if !exists { return nil, fmt.Errorf("template %q missing docker-compose provider configuration", template.ID) diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 54ca148..e114b7f 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -2,13 +2,13 @@ package provider import ( - "github.com/happyhackingspace/vulnerable-target/pkg/templates" + tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" ) // Provider defines the interface for managing vulnerable target environments. type Provider interface { Name() string - Start(template *templates.Template) error - Stop(template *templates.Template) error - Status(template *templates.Template) (string, error) + Start(template *tmpl.Template) error + Stop(template *tmpl.Template) error + Status(template *tmpl.Template) (string, error) } diff --git a/pkg/template/downloader.go b/pkg/template/downloader.go new file mode 100644 index 0000000..8643540 --- /dev/null +++ b/pkg/template/downloader.go @@ -0,0 +1,63 @@ +package template + +import ( + "errors" + "fmt" + "os" + + "github.com/go-git/go-git/v5" +) + +func cloneTemplatesRepo(repoPath string, force bool) error { + repo, err := git.PlainOpen(repoPath) + if err == nil { + worktree, err := repo.Worktree() + if err != nil { + return err + } + + status, err := worktree.Status() + if err != nil { + return err + } + + if !force && !status.IsClean() { + return fmt.Errorf("detected uncommitted changes in %s", repoPath) + } + + err = worktree.Pull(&git.PullOptions{ + RemoteName: "origin", + Force: true, + }) + + if err != nil && err != git.NoErrAlreadyUpToDate { + return err + } + + return nil + } + + err = os.RemoveAll(repoPath) + if err != nil { + return err + } + + if err := os.MkdirAll(repoPath, 0750); err != nil { + return err + } + + _, err = git.PlainClone(repoPath, false, &git.CloneOptions{ + URL: TemplateRemoteRepoistory, + Depth: 1, + }) + + if err != nil { + removeErr := os.RemoveAll(repoPath) + if removeErr != nil { + return errors.Join(err, fmt.Errorf("cleanup failed: %w", removeErr)) + } + return err + } + + return nil +} diff --git a/pkg/template/parser.go b/pkg/template/parser.go new file mode 100644 index 0000000..23b8988 --- /dev/null +++ b/pkg/template/parser.go @@ -0,0 +1,24 @@ +// Package template provides functionality for loading and managing vulnerable target environment templates. +package template + +import ( + "os" + "path" + + yaml "gopkg.in/yaml.v3" +) + +// LoadTemplate loads a template from the specified filepath by reading the index.yaml file. +func LoadTemplate(filepath string) (Template, error) { + var template Template + file, err := os.ReadFile(path.Join(filepath, "index.yaml")) // #nosec: G304 + if err != nil { + return template, err + } + err = yaml.Unmarshal(file, &template) + if err != nil { + return template, err + } + err = template.Validate() + return template, err +} diff --git a/pkg/template/parser_test.go b/pkg/template/parser_test.go new file mode 100644 index 0000000..5d9fba1 --- /dev/null +++ b/pkg/template/parser_test.go @@ -0,0 +1,59 @@ +package template + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadTemplate(t *testing.T) { + // create dummy content + templateContent := ` +id: example-template + +info: + name: Vulnerable Target + author: hhsteam + description: | + Vulnerable Target + references: + - http://www.vulnerabletarget.com + type: Lab + targets: + - php + - mysql + tags: + - owasp + - web + - vulnerabilities + metadata: + +providers: + docker-compose: + path: "docker-compose.yaml" +` + // create temp dir + tempDir := filepath.Join(t.TempDir(), "example-template") + + err := os.Mkdir(tempDir, 0750) + assert.NoError(t, err) + + err = os.WriteFile(filepath.Join(tempDir, "index.yaml"), []byte(templateContent), 0644) + assert.NoError(t, err) + + tpl, err := LoadTemplate(tempDir) + assert.NoError(t, err) + assert.Equal(t, "example-template", tpl.ID) + assert.Equal(t, "Vulnerable Target", tpl.Info.Name) + assert.Equal(t, "hhsteam", tpl.Info.Author) + assert.Equal(t, 1, len(tpl.Info.References)) + assert.Equal(t, 3, len(tpl.Info.Tags)) + assert.Contains(t, tpl.Providers, "docker-compose") + + // case of none exist path + _, err = LoadTemplate("/non/existent/path") + assert.Error(t, err) + +} diff --git a/pkg/template/template.go b/pkg/template/template.go new file mode 100644 index 0000000..9b0fd38 --- /dev/null +++ b/pkg/template/template.go @@ -0,0 +1,195 @@ +package template + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/rs/zerolog/log" +) + +// TemplateRemoteRepoistory is a constant for repo url. +const TemplateRemoteRepoistory string = "https://github.com/HappyHackingSpace/vt-templates" + +// Template represents a vulnerable target environment configuration. +type Template struct { + ID string `yaml:"id"` + Info Info `yaml:"info"` + ProofOfConcept map[string][]string `yaml:"poc"` + Remediation []string `yaml:"remediation"` + Providers map[string]ProviderConfig `yaml:"providers"` + PostInstall []string `yaml:"post-install"` +} + +// Info contains metadata about a template. +type Info struct { + Name string `yaml:"name"` + Description string `yaml:"description"` + Author string `yaml:"author"` + Targets []string `yaml:"targets"` + Type string `yaml:"type"` + AffectedVersions []string `yaml:"affected_versions"` + FixedVersion string `yaml:"fixed_version"` + Cwe string `yaml:"cwe"` + Cvss Cvss `yaml:"cvss"` + Tags []string `yaml:"tags"` + References []string `yaml:"references"` +} + +// ProviderConfig contains configuration for a specific provider. +type ProviderConfig struct { + Path string `yaml:"path"` +} + +// Cvss represents Common Vulnerability Scoring System information. +type Cvss struct { + Score string `yaml:"score"` + Metrics string `yaml:"metrics"` +} + +// Templates contains all loaded templates indexed by their ID. +var Templates = make(map[string]Template) + +// Init initializes the templates repository and loads all templates. +func Init() { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatal().Msgf("%v", err) + } + + repoPath := filepath.Join(homeDir, "vt-templates") + if _, err := os.Stat(repoPath); os.IsNotExist(err) { + log.Info().Msg("Fetching templates for the first time") + err = cloneTemplatesRepo(repoPath, false) + if err != nil { + log.Fatal().Msgf("%v", err) + } + } + + loadTemplatesFromDirectory(repoPath) +} + +// loadTemplatesFromDirectory reads all templates from the given path and populates the Templates map. +func loadTemplatesFromDirectory(repoPath string) { + dirEntry, err := os.ReadDir(repoPath) + if err != nil { + log.Fatal().Msgf("%v", err) + } + + for _, categoryEntry := range dirEntry { + if strings.HasPrefix(categoryEntry.Name(), ".") || !categoryEntry.IsDir() { + continue + } + + categoryPath := filepath.Join(repoPath, categoryEntry.Name()) + loadTemplatesFromCategory(categoryPath, categoryEntry.Name()) + } +} + +// loadTemplatesFromCategory loads all templates within a single category directory. +func loadTemplatesFromCategory(categoryPath, categoryName string) { + templateEntries, err := os.ReadDir(categoryPath) + if err != nil { + log.Fatal().Msgf("Error reading category %s: %v", categoryName, err) + } + + for _, entry := range templateEntries { + if strings.HasPrefix(entry.Name(), ".") || !entry.IsDir() { + continue + } + + templatePath := filepath.Join(categoryPath, entry.Name()) + template, err := LoadTemplate(templatePath) + if err != nil { + log.Fatal().Msgf("Error loading template %s: %v", entry.Name(), err) + } + if template.ID != entry.Name() { + log.Fatal().Msgf("id and directory name should match") + } + Templates[template.ID] = template + } +} + +// SyncTemplates downloads or updates all templates from the remote repository. +func SyncTemplates() { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatal().Msgf("%v", err) + } + + repoPath := filepath.Join(homeDir, "vt-templates") + log.Info().Msgf("cloning %s", TemplateRemoteRepoistory) + err = cloneTemplatesRepo(repoPath, true) + if err != nil { + log.Fatal().Msgf("%v", err) + } +} + +// List displays all available templates in a table format. +func List() { + ListWithFilter("") +} + +// ListWithFilter displays all available templates in a table format, optionally filtered by tag. +func ListWithFilter(filterTag string) { + t := table.NewWriter() + t.SetStyle(table.StyleDefault) + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ID", "Name", "Author", "Targets", "Type", "Tags"}) + + count := 0 + for _, template := range Templates { + if filterTag != "" { + hasTag := false + for _, tag := range template.Info.Tags { + if strings.EqualFold(tag, filterTag) || strings.Contains(strings.ToLower(tag), strings.ToLower(filterTag)) { + hasTag = true + break + } + } + if !hasTag { + continue + } + } + + tags := strings.Join(template.Info.Tags, ", ") + targets := strings.Join(template.Info.Targets, ", ") + t.AppendRow(table.Row{ + template.ID, + template.Info.Name, + template.Info.Author, + targets, + template.Info.Type, + tags, + }) + count++ + } + + if count == 0 { + if filterTag != "" { + fmt.Printf("No templates found with tag matching '%s'\n", filterTag) + } else { + fmt.Println("No templates found") + } + return + } + + if filterTag != "" { + t.SetCaption("Found %d templates with tag matching '%s'", count, filterTag) + } else { + t.SetCaption("there are %d templates", count) + } + t.SetIndexColumn(0) + t.Render() +} + +// GetByID retrieves a template by its ID. +func GetByID(templateID string) (*Template, error) { + template := Templates[templateID] + if template.ID == "" { + return nil, fmt.Errorf("template %s not found", templateID) + } + return &template, nil +} diff --git a/pkg/template/template_test.go b/pkg/template/template_test.go new file mode 100644 index 0000000..f415d26 --- /dev/null +++ b/pkg/template/template_test.go @@ -0,0 +1,33 @@ +package template + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetByID(t *testing.T) { + Templates = map[string]Template{ + "example-template-1": { + ID: "example-template-1", + Info: Info{ + Name: "example-template-name", + Author: "example-template-author", + Tags: []string{ + "test", + "example", + }, + }, + }, + } + firstTemplate, err := GetByID("example-template-1") + assert.Nil(t, err) + assert.Equal(t, "example-template-1", firstTemplate.ID) + noneExistTemplateID := "none-exist-template" + noneExistingTemplate, err := GetByID(noneExistTemplateID) + assert.Nil(t, noneExistingTemplate) + assert.Error(t, err) + assert.EqualError(t, err, fmt.Sprintf("template %s not found", noneExistTemplateID)) + +} diff --git a/pkg/template/validator.go b/pkg/template/validator.go new file mode 100644 index 0000000..65a7b0e --- /dev/null +++ b/pkg/template/validator.go @@ -0,0 +1,92 @@ +package template + +import ( + "fmt" + "path/filepath" + "regexp" + "strings" +) + +var ( + templateIDRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]*$`) + allowedProviderExts = map[string]bool{ + ".yml": true, + ".yaml": true, + } +) + +// Validate validates the template structure and content. +func (template Template) Validate() error { + if template.ID == "" { + return fmt.Errorf("id can not be empty") + } + + if !templateIDRegex.MatchString(template.ID) { + return fmt.Errorf("template '%s': id contains invalid characters", template.ID) + } + + if len(template.Providers) == 0 { + return fmt.Errorf("template '%s': no providers specified in the template", template.ID) + } + + for k, v := range template.Providers { + pcError := v.Validate(template.ID, k) + if pcError != nil { + return pcError + } + } + + infoError := template.Info.Validate(template.ID) + if infoError != nil { + return infoError + } + + return nil +} + +// Validate validates the info structure and content. +func (info Info) Validate(templateID string) error { + if info.Name == "" { + return fmt.Errorf("template '%s': name can not be empty", templateID) + } + if info.Author == "" { + return fmt.Errorf("template '%s': author can not be empty", templateID) + } + if len(info.Targets) == 0 { + return fmt.Errorf("template '%s': targets can not be empty", templateID) + } + if info.Type == "" { + return fmt.Errorf("template '%s': type can not be empty", templateID) + } + if len(info.Tags) == 0 { + return fmt.Errorf("template '%s': tags can not be empty", templateID) + } + return nil +} + +// Validate validates the provider configuration structure and content. +func (pc ProviderConfig) Validate(templateID, name string) error { + providerPath := pc.Path + if providerPath == "" { + return fmt.Errorf("template '%s', provider '%s': path is empty", templateID, name) + } + + if filepath.IsAbs(providerPath) { + return fmt.Errorf("template '%s', provider '%s': absolute paths are not allowed", templateID, name) + } + + if strings.Contains(providerPath, "..") { + return fmt.Errorf("template '%s', provider '%s': path contains invalid '..' segments", templateID, name) + } + + ext := filepath.Ext(providerPath) + if !isAllowedExtension(ext) { + return fmt.Errorf("template '%s', provider '%s': provider file must have one of the allowed extensions: %v", templateID, name, allowedProviderExts) + } + + return nil +} + +func isAllowedExtension(ext string) bool { + return allowedProviderExts[ext] +} diff --git a/pkg/templates/downloader.go b/pkg/templates/downloader.go index 0e485f1..8643540 100644 --- a/pkg/templates/downloader.go +++ b/pkg/templates/downloader.go @@ -1,4 +1,4 @@ -package templates +package template import ( "errors" diff --git a/pkg/templates/parser.go b/pkg/templates/parser.go index 41f384d..23b8988 100644 --- a/pkg/templates/parser.go +++ b/pkg/templates/parser.go @@ -1,5 +1,5 @@ -// Package templates provides functionality for loading and managing vulnerable target environment templates. -package templates +// Package template provides functionality for loading and managing vulnerable target environment templates. +package template import ( "os" diff --git a/pkg/templates/parser_test.go b/pkg/templates/parser_test.go index c5a2dfd..5d9fba1 100644 --- a/pkg/templates/parser_test.go +++ b/pkg/templates/parser_test.go @@ -1,4 +1,4 @@ -package templates +package template import ( "os" diff --git a/pkg/templates/template.go b/pkg/templates/template.go index 50a00c5..9b0fd38 100644 --- a/pkg/templates/template.go +++ b/pkg/templates/template.go @@ -1,4 +1,4 @@ -package templates +package template import ( "fmt" diff --git a/pkg/templates/template_test.go b/pkg/templates/template_test.go index a1119bc..f415d26 100644 --- a/pkg/templates/template_test.go +++ b/pkg/templates/template_test.go @@ -1,4 +1,4 @@ -package templates +package template import ( "fmt" diff --git a/pkg/templates/validator.go b/pkg/templates/validator.go index f148ea0..65a7b0e 100644 --- a/pkg/templates/validator.go +++ b/pkg/templates/validator.go @@ -1,4 +1,4 @@ -package templates +package template import ( "fmt" From 3fc1d86961a5c691ffae9a1b4e6696a736231241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 13:33:03 +0300 Subject: [PATCH 2/7] update README --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2ea6429..38ca300 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,18 @@ Vulnerable Target (VT) is a specialized tool designed for security professionals > This tool creates intentionally vulnerable environments. Running this on a public server or an insecure network can expose you to severe security risks. Use only in an isolated, local environment (sandbox/VM). ## Features -- Community-Curated List of Vulnerable Targets -- Interactive Vulnerability Playground (TODO) -- CLI (In-Progress) +- CLI for managing vulnerable environments +- Docker Compose provider for container orchestration +- Community-curated templates from [vt-templates](https://github.com/HappyHackingSpace/vt-templates) +- Template filtering by tags +- Deployment state tracking + +## Prerequisites +- Go 1.24+ +- Docker & Docker Compose + +## Installation -## Quickstart 1. Clone the repository ```bash git clone https://github.com/HappyHackingSpace/vulnerable-target.git @@ -28,10 +35,45 @@ cd vulnerable-target go mod download ``` -3. Run the application with: +3. Build the binary +```bash +go build -o vt cmd/vt/main.go +``` + +4. (Optional) Move to your PATH +```bash +mv vt /usr/local/bin/ +``` + +## Usage + ```bash -go run cmd/vt/main.go +# List available templates +vt template --list + +# Filter templates by tag +vt template --list --filter sql + +# Update templates from remote repository +vt template --update + +# Start a vulnerable environment +vt start --id --provider docker-compose + +# List running environments +vt ps + +# Stop an environment +vt stop --id --provider docker-compose + +# Set verbosity level +vt -v debug ``` + +## Templates + +Templates are automatically cloned to `~/vt-templates` on first run. To contribute new vulnerable target templates, visit the [vt-templates repository](https://github.com/HappyHackingSpace/vt-templates). + ## Documentation Check the full documentation here: [Vulnerable Target Wiki](https://github.com/HappyHackingSpace/vulnerable-target/wiki) From 98b0ed84d11e81edc0329232d02a6febaa4e4082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 16:50:50 +0300 Subject: [PATCH 3/7] refactor: eliminate global state and introduce dependency injection - Add App struct as central dependency container (internal/app/app.go) - Remove global Templates map from pkg/template, use pure functions - Remove global Providers map from pkg/provider/registry, use factory - Inject StateManager into DockerCompose provider - Refactor CLI to struct-based approach with dependency injection - Add logger factory functions (New, NewWithLevel, SetGlobal) - Delete duplicate pkg/templates/ package - Update tests to work with new function signatures All dependencies are now explicitly wired in main.go at startup, making the codebase more testable and eliminating hidden state. --- cmd/vt/main.go | 47 ++++- internal/app/app.go | 70 +++++++ internal/cli/cli.go | 79 ++++++++ internal/cli/list.go | 107 ++++++----- internal/cli/ps.go | 100 +++++----- internal/cli/root.go | 85 --------- internal/cli/start.go | 89 +++++---- internal/cli/stop.go | 86 ++++----- internal/logger/logger.go | 60 +++++- pkg/provider/dockercompose/dockercompose.go | 30 ++- pkg/provider/registry/registry.go | 17 +- pkg/template/downloader.go | 2 +- pkg/template/template.go | 117 ++++++------ pkg/template/template_test.go | 7 +- pkg/templates/downloader.go | 63 ------- pkg/templates/parser.go | 24 --- pkg/templates/parser_test.go | 59 ------ pkg/templates/template.go | 195 -------------------- pkg/templates/template_test.go | 33 ---- pkg/templates/validator.go | 92 --------- 20 files changed, 520 insertions(+), 842 deletions(-) create mode 100644 internal/app/app.go create mode 100644 internal/cli/cli.go delete mode 100644 internal/cli/root.go delete mode 100644 pkg/templates/downloader.go delete mode 100644 pkg/templates/parser.go delete mode 100644 pkg/templates/parser_test.go delete mode 100644 pkg/templates/template.go delete mode 100644 pkg/templates/template_test.go delete mode 100644 pkg/templates/validator.go diff --git a/cmd/vt/main.go b/cmd/vt/main.go index 3ab593f..4bfde93 100644 --- a/cmd/vt/main.go +++ b/cmd/vt/main.go @@ -2,19 +2,54 @@ package main import ( + "os" + + "github.com/happyhackingspace/vulnerable-target/internal/app" "github.com/happyhackingspace/vulnerable-target/internal/banner" "github.com/happyhackingspace/vulnerable-target/internal/cli" "github.com/happyhackingspace/vulnerable-target/internal/logger" - tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" + "github.com/happyhackingspace/vulnerable-target/internal/state" + "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" + "github.com/happyhackingspace/vulnerable-target/pkg/store/disk" + "github.com/happyhackingspace/vulnerable-target/pkg/template" + "github.com/rs/zerolog/log" ) func main() { - // Initialize logger and templates explicitly - logger.Init() - tmpl.Init() + // Initialize configuration + cfg := app.DefaultConfig() + + // Initialize logger (sets global logger for zerolog) + appLogger := logger.NewWithLevel(cfg.LogLevel) + logger.SetGlobal(appLogger) + // Print banner banner.Print() - // Run the CLI - cli.Run() + // Load templates from repository + templates, err := template.LoadTemplates(cfg.TemplatesPath) + if err != nil { + log.Fatal().Err(err).Msg("failed to load templates") + } + + // Create state manager + storeCfg := disk.NewConfig(). + WithFileName("deployments.db"). + WithBucketName("deployments") + stateManager, err := state.NewManager(storeCfg) + if err != nil { + log.Fatal().Err(err).Msg("failed to create state manager") + } + + // Create providers with injected dependencies + providers := registry.NewProviders(stateManager) + + // Create application context + application := app.NewApp(templates, providers, stateManager, cfg) + + // Create and run CLI + if err := cli.New(application).Run(); err != nil { + log.Fatal().Err(err).Msg("CLI error") + os.Exit(1) + } } diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..50ee602 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,70 @@ +// Package app provides the application dependency container and configuration. +package app + +import ( + "os" + "path/filepath" + + "github.com/happyhackingspace/vulnerable-target/internal/state" + "github.com/happyhackingspace/vulnerable-target/pkg/provider" + "github.com/happyhackingspace/vulnerable-target/pkg/template" +) + +// Config holds application configuration. +type Config struct { + TemplatesPath string + StoragePath string + LogLevel string +} + +// App is the dependency container for the application. +type App struct { + Templates map[string]template.Template + Providers map[string]provider.Provider + StateManager *state.Manager + Config *Config +} + +// DefaultConfig returns the default application configuration. +func DefaultConfig() *Config { + homeDir, err := os.UserHomeDir() + if err != nil { + homeDir = "." + } + + return &Config{ + TemplatesPath: filepath.Join(homeDir, "vt-templates"), + StoragePath: filepath.Join(homeDir, ".vt-cli"), + LogLevel: "info", + } +} + +// NewApp creates a new App instance with the given dependencies. +func NewApp( + templates map[string]template.Template, + providers map[string]provider.Provider, + stateManager *state.Manager, + config *Config, +) *App { + return &App{ + Templates: templates, + Providers: providers, + StateManager: stateManager, + Config: config, + } +} + +// GetTemplate retrieves a template by ID from the app's template map. +func (a *App) GetTemplate(id string) (*template.Template, bool) { + t, ok := a.Templates[id] + if !ok { + return nil, false + } + return &t, true +} + +// GetProvider retrieves a provider by name from the app's provider map. +func (a *App) GetProvider(name string) (provider.Provider, bool) { + p, ok := a.Providers[name] + return p, ok +} diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..b71e598 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,79 @@ +// Package cli provides command-line interface functionality for the vulnerable target application. +package cli + +import ( + "fmt" + "maps" + "slices" + "strings" + + "github.com/happyhackingspace/vulnerable-target/internal/app" + "github.com/happyhackingspace/vulnerable-target/internal/banner" + "github.com/happyhackingspace/vulnerable-target/internal/logger" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +// logLevels defines the valid log levels supported by the application. +var logLevels = map[string]bool{ + zerolog.DebugLevel.String(): true, + zerolog.InfoLevel.String(): true, + zerolog.WarnLevel.String(): true, + zerolog.ErrorLevel.String(): true, + zerolog.FatalLevel.String(): true, + zerolog.PanicLevel.String(): true, +} + +// CLI encapsulates the command-line interface with its dependencies. +type CLI struct { + app *app.App + rootCmd *cobra.Command +} + +// New creates a new CLI instance with the given application context. +func New(application *app.App) *CLI { + c := &CLI{ + app: application, + } + c.setupCommands() + return c +} + +// setupCommands initializes all CLI commands and their configurations. +func (c *CLI) setupCommands() { + c.rootCmd = &cobra.Command{ + Use: "vt", + Short: "Create vulnerable environment", + Version: banner.AppVersion, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + verbosityLevel, err := cmd.Flags().GetString("verbosity") + if err != nil { + log.Fatal().Msgf("%v", err) + } + logger.InitWithLevel(verbosityLevel) + }, + SilenceErrors: true, + } + + // Setup root flags + c.rootCmd.PersistentFlags().StringP("verbosity", "v", zerolog.InfoLevel.String(), + fmt.Sprintf("Set the verbosity level for logs (%s)", + strings.Join(slices.Collect(maps.Keys(logLevels)), ", "))) + + // Register all subcommands + c.rootCmd.AddCommand(c.newStartCommand()) + c.rootCmd.AddCommand(c.newStopCommand()) + c.rootCmd.AddCommand(c.newPsCommand()) + c.rootCmd.AddCommand(c.newTemplateCommand()) +} + +// Run executes the CLI and returns any error. +func (c *CLI) Run() error { + return c.rootCmd.Execute() +} + +// providerNames returns a slice of registered provider names. +func (c *CLI) providerNames() []string { + return slices.Collect(maps.Keys(c.app.Providers)) +} diff --git a/internal/cli/list.go b/internal/cli/list.go index 270bf77..5b48121 100644 --- a/internal/cli/list.go +++ b/internal/cli/list.go @@ -6,59 +6,74 @@ import ( "github.com/spf13/cobra" ) -var templateCmd = &cobra.Command{ - Use: "template", - Short: "template operations", - Run: func(cmd *cobra.Command, _ []string) { - list, err := cmd.Flags().GetBool("list") - if err != nil { - log.Error().Err(err).Msg("failed to get list flag") - return - } +// newTemplateCommand creates the template command. +func (c *CLI) newTemplateCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "template", + Short: "template operations", + Run: func(cmd *cobra.Command, _ []string) { + list, err := cmd.Flags().GetBool("list") + if err != nil { + log.Error().Err(err).Msg("failed to get list flag") + return + } - update, err := cmd.Flags().GetBool("update") - if err != nil { - log.Error().Err(err).Msg("failed to get update flag") - return - } + update, err := cmd.Flags().GetBool("update") + if err != nil { + log.Error().Err(err).Msg("failed to get update flag") + return + } - filter, err := cmd.Flags().GetString("filter") - if err != nil { - log.Error().Err(err).Msg("failed to get filter flag") - return - } + filter, err := cmd.Flags().GetString("filter") + if err != nil { + log.Error().Err(err).Msg("failed to get filter flag") + return + } - if list && update { - log.Error().Msg("only one of --list, or --update can be specified") - return - } + if list && update { + log.Error().Msg("only one of --list, or --update can be specified") + return + } - if filter != "" && !list { - log.Error().Msg("--filter can only be used with --list") - return - } + if filter != "" && !list { + log.Error().Msg("--filter can only be used with --list") + return + } - if !list && !update { - if err := cmd.Help(); err != nil { - log.Error().Err(err).Msg("failed to display help") + if !list && !update { + if err := cmd.Help(); err != nil { + log.Error().Err(err).Msg("failed to display help") + } + return } - return - } - if list { - tmpl.ListWithFilter(filter) - return - } + if list { + tmpl.ListTemplatesWithFilter(c.app.Templates, filter) + return + } - if update { - tmpl.SyncTemplates() - return - } - }, -} + if update { + if err := tmpl.SyncTemplates(c.app.Config.TemplatesPath); err != nil { + log.Error().Err(err).Msg("failed to sync templates") + return + } + // Reload templates after sync + templates, err := tmpl.LoadTemplates(c.app.Config.TemplatesPath) + if err != nil { + log.Error().Err(err).Msg("failed to reload templates") + return + } + // Update the app's templates + c.app.Templates = templates + log.Info().Msg("Templates updated successfully") + return + } + }, + } + + cmd.Flags().BoolP("list", "l", false, "List available templates") + cmd.Flags().BoolP("update", "u", false, "Fetch templates repository to local working directory") + cmd.Flags().StringP("filter", "f", "", "Filter templates by tag or keyword (only works with --list)") -func setupTemplateCommand() { - templateCmd.Flags().BoolP("list", "l", false, "List available templates") - templateCmd.Flags().BoolP("update", "u", false, "Fetch templates repository to local working directory") - templateCmd.Flags().StringP("filter", "f", "", "Filter templates by tag or keyword (only works with --list)") + return cmd } diff --git a/internal/cli/ps.go b/internal/cli/ps.go index 45ae1be..c509bb9 100644 --- a/internal/cli/ps.go +++ b/internal/cli/ps.go @@ -4,72 +4,64 @@ import ( "os" "time" - "github.com/happyhackingspace/vulnerable-target/internal/state" - "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" - "github.com/happyhackingspace/vulnerable-target/pkg/store/disk" tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/jedib0t/go-pretty/v6/table" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) -var psCmd = &cobra.Command{ - Use: "ps", - Short: "List running deployments and their status", - Run: func(_ *cobra.Command, _ []string) { - cfg := disk.NewConfig().WithFileName("deployments.db").WithBucketName("deployments") - st, err := state.NewManager(cfg) - if err != nil { - log.Error().Msgf("%v", err) - return - } - - deployments, err := st.ListDeployments() - if err != nil { - log.Error().Msgf("%v", err) - return - } - - t := table.NewWriter() - t.SetStyle(table.StyleDefault) - t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"Provider Name", "Template ID", "Status", "Created At"}) - - count := 0 - for _, deployment := range deployments { - provider := registry.GetProvider(deployment.ProviderName) - if provider == nil { - log.Error().Msgf("provider %q not found", deployment.ProviderName) - continue - } - template, err := tmpl.GetByID(deployment.TemplateID) +// newPsCommand creates the ps command. +func (c *CLI) newPsCommand() *cobra.Command { + return &cobra.Command{ + Use: "ps", + Short: "List running deployments and their status", + Run: func(_ *cobra.Command, _ []string) { + deployments, err := c.app.StateManager.ListDeployments() if err != nil { log.Error().Msgf("%v", err) - continue + return } - status := "unknown" - if s, err := provider.Status(template); err != nil { - log.Error().Msgf("%v", err) - } else { - status = s - } + t := table.NewWriter() + t.SetStyle(table.StyleDefault) + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Provider Name", "Template ID", "Status", "Created At"}) + + count := 0 + for _, deployment := range deployments { + provider, ok := c.app.GetProvider(deployment.ProviderName) + if !ok { + log.Error().Msgf("provider %q not found", deployment.ProviderName) + continue + } + template, err := tmpl.GetByID(c.app.Templates, deployment.TemplateID) + if err != nil { + log.Error().Msgf("%v", err) + continue + } - t.AppendRow(table.Row{ - deployment.ProviderName, - deployment.TemplateID, - status, - deployment.CreatedAt.Format(time.DateTime), - }) - count++ - } + status := "unknown" + if s, err := provider.Status(template); err != nil { + log.Error().Msgf("%v", err) + } else { + status = s + } - if count == 0 { - log.Info().Msg("there is no running environment") - return - } + t.AppendRow(table.Row{ + deployment.ProviderName, + deployment.TemplateID, + status, + deployment.CreatedAt.Format(time.DateTime), + }) + count++ + } - t.Render() + if count == 0 { + log.Info().Msg("there is no running environment") + return + } - }, + t.Render() + }, + } } diff --git a/internal/cli/root.go b/internal/cli/root.go deleted file mode 100644 index 303ae6e..0000000 --- a/internal/cli/root.go +++ /dev/null @@ -1,85 +0,0 @@ -// Package cli provides command-line interface functionality for the vulnerable target application. -package cli - -import ( - "fmt" - "maps" - "slices" - "strings" - - "github.com/happyhackingspace/vulnerable-target/internal/banner" - "github.com/happyhackingspace/vulnerable-target/internal/logger" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "github.com/spf13/cobra" -) - -// LogLevels defines the valid log levels supported by the application. -var LogLevels = map[string]bool{ - zerolog.DebugLevel.String(): true, - zerolog.InfoLevel.String(): true, - zerolog.WarnLevel.String(): true, - zerolog.ErrorLevel.String(): true, - zerolog.FatalLevel.String(): true, - zerolog.PanicLevel.String(): true, -} - -// setupRootFlags configures the root command flags -func setupRootFlags() { - rootCmd.PersistentFlags().StringP("verbosity", "v", zerolog.InfoLevel.String(), - fmt.Sprintf("Set the verbosity level for logs (%s)", - strings.Join(slices.Collect(maps.Keys(LogLevels)), ", "))) -} - -var rootCmd = &cobra.Command{ - Use: "vt", - Short: "Create vulnerable environment", - Version: banner.AppVersion, - PersistentPreRun: func(cmd *cobra.Command, _ []string) { - verbosityLevel, err := cmd.Flags().GetString("verbosity") - if err != nil { - log.Fatal().Msgf("%v", err) - } - logger.InitWithLevel(verbosityLevel) - }, - SilenceErrors: true, -} - -// InitCLI initializes all CLI commands and their configurations -func InitCLI() { - // Setup root command flags - setupRootFlags() - - // Register all subcommands - registerCommands() -} - -// registerCommands registers all CLI subcommands and configures their flags -func registerCommands() { - // Register commands - rootCmd.AddCommand(startCmd) - rootCmd.AddCommand(stopCmd) - rootCmd.AddCommand(psCmd) - rootCmd.AddCommand(templateCmd) - - // Setup command-specific flags - setupStartCommand() - setupStopCommand() - setupTemplateCommand() -} - -// Run executes the root command and handles the application lifecycle. -func Run() { - // Initialize CLI before running - InitCLI() - - originalHelp := rootCmd.HelpFunc() - rootCmd.SetHelpFunc(func(c *cobra.Command, s []string) { - originalHelp(c, s) - }) - - if err := rootCmd.Execute(); err != nil { - log.Fatal().Msg(err.Error()) - } -} diff --git a/internal/cli/start.go b/internal/cli/start.go index 4befcb1..b170d61 100644 --- a/internal/cli/start.go +++ b/internal/cli/start.go @@ -2,75 +2,72 @@ package cli import ( "fmt" - "maps" - "slices" "strings" - "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) -var startCmd = &cobra.Command{ - Use: "start", - Short: "Runs selected template on chosen provider", - Run: func(cmd *cobra.Command, _ []string) { - providerName, err := cmd.Flags().GetString("provider") - if err != nil { - log.Fatal().Msgf("%v", err) - } +// newStartCommand creates the start command. +func (c *CLI) newStartCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "start", + Short: "Runs selected template on chosen provider", + Run: func(cmd *cobra.Command, _ []string) { + providerName, err := cmd.Flags().GetString("provider") + if err != nil { + log.Fatal().Msgf("%v", err) + } - templateID, err := cmd.Flags().GetString("id") - if err != nil { - log.Fatal().Msgf("%v", err) - } - provider := registry.GetProvider(providerName) - if len(templateID) == 0 { - err := cmd.Help() + templateID, err := cmd.Flags().GetString("id") if err != nil { log.Fatal().Msgf("%v", err) } - return - } - if provider == nil { - log.Fatal().Msgf("provider %s not found", providerName) - } + if len(templateID) == 0 { + if err := cmd.Help(); err != nil { + log.Fatal().Msgf("%v", err) + } + return + } - template, err := tmpl.GetByID(templateID) - if err != nil { - log.Fatal().Msgf("%v", err) - } + provider, ok := c.app.GetProvider(providerName) + if !ok { + log.Fatal().Msgf("provider %s not found", providerName) + } - err = provider.Start(template) - if err != nil { - log.Fatal().Msgf("%v", err) - } + template, err := tmpl.GetByID(c.app.Templates, templateID) + if err != nil { + log.Fatal().Msgf("%v", err) + } - if len(template.PostInstall) > 0 { + err = provider.Start(template) + if err != nil { + log.Fatal().Msgf("%v", err) + } - log.Info().Msg("Post-installation instructions:") - for _, instruction := range template.PostInstall { - fmt.Printf(" %s\n", instruction) + if len(template.PostInstall) > 0 { + log.Info().Msg("Post-installation instructions:") + for _, instruction := range template.PostInstall { + fmt.Printf(" %s\n", instruction) + } } - } - log.Info().Msgf("%s template is running on %s", templateID, providerName) - }, -} + log.Info().Msgf("%s template is running on %s", templateID, providerName) + }, + } -// setupStartCommand configures the start command flags -func setupStartCommand() { - startCmd.Flags().StringP("provider", "p", "docker-compose", + cmd.Flags().StringP("provider", "p", "docker-compose", fmt.Sprintf("Specify the provider for building a vulnerable environment (%s)", - strings.Join(slices.Collect(maps.Keys(registry.Providers)), ", "))) + strings.Join(c.providerNames(), ", "))) - startCmd.Flags().String("id", "", + cmd.Flags().String("id", "", "Specify a template ID for targeted vulnerable environment") - err := startCmd.MarkFlagRequired("id") - if err != nil { + if err := cmd.MarkFlagRequired("id"); err != nil { log.Fatal().Msgf("%v", err) } + + return cmd } diff --git a/internal/cli/stop.go b/internal/cli/stop.go index bd5c003..3a9e341 100644 --- a/internal/cli/stop.go +++ b/internal/cli/stop.go @@ -2,66 +2,62 @@ package cli import ( "fmt" - "maps" - "slices" "strings" - "github.com/happyhackingspace/vulnerable-target/pkg/provider/registry" tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) -var stopCmd = &cobra.Command{ - Use: "stop", - Short: "Stop vulnerable environment by template id and provider", - Run: func(cmd *cobra.Command, _ []string) { - providerName, err := cmd.Flags().GetString("provider") - if err != nil { - log.Fatal().Msgf("%v", err) - } - - templateID, err := cmd.Flags().GetString("id") - if err != nil { - log.Fatal().Msgf("%v", err) - } - - provider := registry.GetProvider(providerName) - - if provider == nil { - log.Fatal().Msgf("provider %s not found", providerName) - } - - template, err := tmpl.GetByID(templateID) - if err != nil { - log.Fatal().Msgf("%v", err) - } - - err = provider.Stop(template) - if err != nil { - log.Fatal().Msgf("%v", err) - } - - log.Info().Msgf("%s template stopped on %s", templateID, providerName) - }, -} +// newStopCommand creates the stop command. +func (c *CLI) newStopCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "stop", + Short: "Stop vulnerable environment by template id and provider", + Run: func(cmd *cobra.Command, _ []string) { + providerName, err := cmd.Flags().GetString("provider") + if err != nil { + log.Fatal().Msgf("%v", err) + } + + templateID, err := cmd.Flags().GetString("id") + if err != nil { + log.Fatal().Msgf("%v", err) + } + + provider, ok := c.app.GetProvider(providerName) + if !ok { + log.Fatal().Msgf("provider %s not found", providerName) + } + + template, err := tmpl.GetByID(c.app.Templates, templateID) + if err != nil { + log.Fatal().Msgf("%v", err) + } + + err = provider.Stop(template) + if err != nil { + log.Fatal().Msgf("%v", err) + } + + log.Info().Msgf("%s template stopped on %s", templateID, providerName) + }, + } -// setupStopCommand configures the stop command flags -func setupStopCommand() { - stopCmd.Flags().StringP("provider", "p", "", + cmd.Flags().StringP("provider", "p", "", fmt.Sprintf("Specify the provider for building a vulnerable environment (%s)", - strings.Join(slices.Collect(maps.Keys(registry.Providers)), ", "))) + strings.Join(c.providerNames(), ", "))) - stopCmd.Flags().String("id", "", + cmd.Flags().String("id", "", "Specify a template ID for targeted vulnerable environment") - err := stopCmd.MarkFlagRequired("provider") - if err != nil { + if err := cmd.MarkFlagRequired("provider"); err != nil { log.Fatal().Msgf("%v", err) } - err = stopCmd.MarkFlagRequired("id") - if err != nil { + if err := cmd.MarkFlagRequired("id"); err != nil { log.Fatal().Msgf("%v", err) } + + return cmd } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index bd355c6..e58b454 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -2,6 +2,7 @@ package logger import ( + "io" "os" "time" @@ -9,20 +10,40 @@ import ( "github.com/rs/zerolog/log" ) -// InitWithLevel initializes the logger with the specified verbosity level. -func InitWithLevel(verbosityLevel string) { - level, err := zerolog.ParseLevel(verbosityLevel) +// Config holds logger configuration options. +type Config struct { + Level string + Output io.Writer + TimeFormat string + NoColor bool +} + +// DefaultConfig returns the default logger configuration. +func DefaultConfig() *Config { + return &Config{ + Level: "info", + Output: os.Stdout, + TimeFormat: time.TimeOnly, + NoColor: false, + } +} + +// New creates a new zerolog.Logger with the given configuration. +// This is a pure factory function that returns a configured logger +// without modifying any global state. +func New(cfg *Config) zerolog.Logger { + level, err := zerolog.ParseLevel(cfg.Level) if err != nil { level = zerolog.InfoLevel } output := zerolog.ConsoleWriter{ - Out: os.Stdout, - TimeFormat: time.TimeOnly, - NoColor: false, + Out: cfg.Output, + TimeFormat: cfg.TimeFormat, + NoColor: cfg.NoColor, } - log.Logger = zerolog. + return zerolog. New(output). Level(level). With(). @@ -30,7 +51,30 @@ func InitWithLevel(verbosityLevel string) { Logger() } -// Init initializes the logger with the default info level. +// NewWithLevel creates a new zerolog.Logger with the specified level +// and default settings for other options. +func NewWithLevel(level string) zerolog.Logger { + cfg := DefaultConfig() + cfg.Level = level + return New(cfg) +} + +// SetGlobal sets the global log.Logger to the provided logger. +// This is the only function that modifies global state, and should +// be called once at application startup. +func SetGlobal(logger zerolog.Logger) { + log.Logger = logger +} + +// InitWithLevel initializes the global logger with the specified verbosity level. +// Deprecated: Use New() or NewWithLevel() with SetGlobal() instead. +func InitWithLevel(verbosityLevel string) { + logger := NewWithLevel(verbosityLevel) + SetGlobal(logger) +} + +// Init initializes the global logger with the default info level. +// Deprecated: Use New() or NewWithLevel() with SetGlobal() instead. func Init() { InitWithLevel("info") } diff --git a/pkg/provider/dockercompose/dockercompose.go b/pkg/provider/dockercompose/dockercompose.go index a635a97..1affdb3 100644 --- a/pkg/provider/dockercompose/dockercompose.go +++ b/pkg/provider/dockercompose/dockercompose.go @@ -6,14 +6,20 @@ import ( "github.com/happyhackingspace/vulnerable-target/internal/state" "github.com/happyhackingspace/vulnerable-target/pkg/provider" - "github.com/happyhackingspace/vulnerable-target/pkg/store/disk" tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" ) var _ provider.Provider = &DockerCompose{} // DockerCompose implements the Provider interface using Docker Compose. -type DockerCompose struct{} +type DockerCompose struct { + stateManager *state.Manager +} + +// NewDockerCompose creates a new DockerCompose provider with the given state manager. +func NewDockerCompose(sm *state.Manager) *DockerCompose { + return &DockerCompose{stateManager: sm} +} // Name returns the provider name. func (d *DockerCompose) Name() string { @@ -22,13 +28,7 @@ func (d *DockerCompose) Name() string { // Start launches the vulnerable target environment using Docker Compose. func (d *DockerCompose) Start(template *tmpl.Template) error { - cfg := disk.NewConfig().WithFileName("deployments.db").WithBucketName("deployments") - st, err := state.NewManager(cfg) - if err != nil { - return err - } - - exist, _ := st.DeploymentExist(d.Name(), template.ID) //nolint:errcheck + exist, _ := d.stateManager.DeploymentExist(d.Name(), template.ID) //nolint:errcheck if exist { return fmt.Errorf("already running") } @@ -48,7 +48,7 @@ func (d *DockerCompose) Start(template *tmpl.Template) error { return err } - err = st.AddNewDeployment(d.Name(), template.ID) + err = d.stateManager.AddNewDeployment(d.Name(), template.ID) if err != nil { return err } @@ -58,13 +58,7 @@ func (d *DockerCompose) Start(template *tmpl.Template) error { // Stop shuts down the vulnerable target environment using Docker Compose. func (d *DockerCompose) Stop(template *tmpl.Template) error { - cfg := disk.NewConfig().WithFileName("deployments.db").WithBucketName("deployments") - st, err := state.NewManager(cfg) - if err != nil { - return err - } - - exist, err := st.DeploymentExist(d.Name(), template.ID) + exist, err := d.stateManager.DeploymentExist(d.Name(), template.ID) if err != nil { return err } @@ -88,7 +82,7 @@ func (d *DockerCompose) Stop(template *tmpl.Template) error { return err } - err = st.RemoveDeployment(d.Name(), template.ID) + err = d.stateManager.RemoveDeployment(d.Name(), template.ID) if err != nil { return err } diff --git a/pkg/provider/registry/registry.go b/pkg/provider/registry/registry.go index 9368108..a384c29 100644 --- a/pkg/provider/registry/registry.go +++ b/pkg/provider/registry/registry.go @@ -2,16 +2,21 @@ package registry import ( + "github.com/happyhackingspace/vulnerable-target/internal/state" "github.com/happyhackingspace/vulnerable-target/pkg/provider" "github.com/happyhackingspace/vulnerable-target/pkg/provider/dockercompose" ) -// Providers contains all available providers registered in the system. -var Providers = map[string]provider.Provider{ - "docker-compose": &dockercompose.DockerCompose{}, +// NewProviders creates and returns a map of all available providers. +// Each provider is initialized with the given state manager. +func NewProviders(sm *state.Manager) map[string]provider.Provider { + return map[string]provider.Provider{ + "docker-compose": dockercompose.NewDockerCompose(sm), + } } -// GetProvider returns the provider with the specified name. -func GetProvider(name string) provider.Provider { - return Providers[name] +// GetProvider retrieves a provider by name from the given providers map. +func GetProvider(providers map[string]provider.Provider, name string) (provider.Provider, bool) { + p, ok := providers[name] + return p, ok } diff --git a/pkg/template/downloader.go b/pkg/template/downloader.go index 8643540..332194b 100644 --- a/pkg/template/downloader.go +++ b/pkg/template/downloader.go @@ -47,7 +47,7 @@ func cloneTemplatesRepo(repoPath string, force bool) error { } _, err = git.PlainClone(repoPath, false, &git.CloneOptions{ - URL: TemplateRemoteRepoistory, + URL: TemplateRemoteRepository, Depth: 1, }) diff --git a/pkg/template/template.go b/pkg/template/template.go index 9b0fd38..1cdeef8 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -10,8 +10,8 @@ import ( "github.com/rs/zerolog/log" ) -// TemplateRemoteRepoistory is a constant for repo url. -const TemplateRemoteRepoistory string = "https://github.com/HappyHackingSpace/vt-templates" +// TemplateRemoteRepository is a constant for repo url. +const TemplateRemoteRepository string = "https://github.com/HappyHackingSpace/vt-templates" // Template represents a vulnerable target environment configuration. type Template struct { @@ -49,33 +49,28 @@ type Cvss struct { Metrics string `yaml:"metrics"` } -// Templates contains all loaded templates indexed by their ID. -var Templates = make(map[string]Template) - -// Init initializes the templates repository and loads all templates. -func Init() { - homeDir, err := os.UserHomeDir() - if err != nil { - log.Fatal().Msgf("%v", err) - } - - repoPath := filepath.Join(homeDir, "vt-templates") +// LoadTemplates loads all templates from the given repository path. +// If the repository doesn't exist, it clones it first. +// Returns a map of templates indexed by their ID. +func LoadTemplates(repoPath string) (map[string]Template, error) { if _, err := os.Stat(repoPath); os.IsNotExist(err) { log.Info().Msg("Fetching templates for the first time") - err = cloneTemplatesRepo(repoPath, false) - if err != nil { - log.Fatal().Msgf("%v", err) + if err := cloneTemplatesRepo(repoPath, false); err != nil { + return nil, fmt.Errorf("failed to clone templates repository: %w", err) } } - loadTemplatesFromDirectory(repoPath) + return loadTemplatesFromDirectory(repoPath) } -// loadTemplatesFromDirectory reads all templates from the given path and populates the Templates map. -func loadTemplatesFromDirectory(repoPath string) { +// loadTemplatesFromDirectory reads all templates from the given path. +// Returns a map of templates indexed by their ID. +func loadTemplatesFromDirectory(repoPath string) (map[string]Template, error) { + templates := make(map[string]Template) + dirEntry, err := os.ReadDir(repoPath) if err != nil { - log.Fatal().Msgf("%v", err) + return nil, fmt.Errorf("failed to read directory %s: %w", repoPath, err) } for _, categoryEntry := range dirEntry { @@ -84,15 +79,27 @@ func loadTemplatesFromDirectory(repoPath string) { } categoryPath := filepath.Join(repoPath, categoryEntry.Name()) - loadTemplatesFromCategory(categoryPath, categoryEntry.Name()) + categoryTemplates, err := loadTemplatesFromCategory(categoryPath, categoryEntry.Name()) + if err != nil { + return nil, err + } + + for id, tmpl := range categoryTemplates { + templates[id] = tmpl + } } + + return templates, nil } // loadTemplatesFromCategory loads all templates within a single category directory. -func loadTemplatesFromCategory(categoryPath, categoryName string) { +// Returns a map of templates indexed by their ID. +func loadTemplatesFromCategory(categoryPath, categoryName string) (map[string]Template, error) { + templates := make(map[string]Template) + templateEntries, err := os.ReadDir(categoryPath) if err != nil { - log.Fatal().Msgf("Error reading category %s: %v", categoryName, err) + return nil, fmt.Errorf("error reading category %s: %w", categoryName, err) } for _, entry := range templateEntries { @@ -101,49 +108,45 @@ func loadTemplatesFromCategory(categoryPath, categoryName string) { } templatePath := filepath.Join(categoryPath, entry.Name()) - template, err := LoadTemplate(templatePath) + tmpl, err := LoadTemplate(templatePath) if err != nil { - log.Fatal().Msgf("Error loading template %s: %v", entry.Name(), err) + return nil, fmt.Errorf("error loading template %s: %w", entry.Name(), err) } - if template.ID != entry.Name() { - log.Fatal().Msgf("id and directory name should match") + if tmpl.ID != entry.Name() { + return nil, fmt.Errorf("template id '%s' and directory name '%s' should match", tmpl.ID, entry.Name()) } - Templates[template.ID] = template + templates[tmpl.ID] = tmpl } + + return templates, nil } // SyncTemplates downloads or updates all templates from the remote repository. -func SyncTemplates() { - homeDir, err := os.UserHomeDir() - if err != nil { - log.Fatal().Msgf("%v", err) - } - - repoPath := filepath.Join(homeDir, "vt-templates") - log.Info().Msgf("cloning %s", TemplateRemoteRepoistory) - err = cloneTemplatesRepo(repoPath, true) - if err != nil { - log.Fatal().Msgf("%v", err) +func SyncTemplates(repoPath string) error { + log.Info().Msgf("cloning %s", TemplateRemoteRepository) + if err := cloneTemplatesRepo(repoPath, true); err != nil { + return fmt.Errorf("failed to sync templates: %w", err) } + return nil } -// List displays all available templates in a table format. -func List() { - ListWithFilter("") +// ListTemplates displays all available templates in a table format. +func ListTemplates(templates map[string]Template) { + ListTemplatesWithFilter(templates, "") } -// ListWithFilter displays all available templates in a table format, optionally filtered by tag. -func ListWithFilter(filterTag string) { +// ListTemplatesWithFilter displays templates in a table format, optionally filtered by tag. +func ListTemplatesWithFilter(templates map[string]Template, filterTag string) { t := table.NewWriter() t.SetStyle(table.StyleDefault) t.SetOutputMirror(os.Stdout) t.AppendHeader(table.Row{"ID", "Name", "Author", "Targets", "Type", "Tags"}) count := 0 - for _, template := range Templates { + for _, tmpl := range templates { if filterTag != "" { hasTag := false - for _, tag := range template.Info.Tags { + for _, tag := range tmpl.Info.Tags { if strings.EqualFold(tag, filterTag) || strings.Contains(strings.ToLower(tag), strings.ToLower(filterTag)) { hasTag = true break @@ -154,14 +157,14 @@ func ListWithFilter(filterTag string) { } } - tags := strings.Join(template.Info.Tags, ", ") - targets := strings.Join(template.Info.Targets, ", ") + tags := strings.Join(tmpl.Info.Tags, ", ") + targets := strings.Join(tmpl.Info.Targets, ", ") t.AppendRow(table.Row{ - template.ID, - template.Info.Name, - template.Info.Author, + tmpl.ID, + tmpl.Info.Name, + tmpl.Info.Author, targets, - template.Info.Type, + tmpl.Info.Type, tags, }) count++ @@ -185,11 +188,11 @@ func ListWithFilter(filterTag string) { t.Render() } -// GetByID retrieves a template by its ID. -func GetByID(templateID string) (*Template, error) { - template := Templates[templateID] - if template.ID == "" { +// GetByID retrieves a template by its ID from the given templates map. +func GetByID(templates map[string]Template, templateID string) (*Template, error) { + tmpl, ok := templates[templateID] + if !ok || tmpl.ID == "" { return nil, fmt.Errorf("template %s not found", templateID) } - return &template, nil + return &tmpl, nil } diff --git a/pkg/template/template_test.go b/pkg/template/template_test.go index f415d26..18440ec 100644 --- a/pkg/template/template_test.go +++ b/pkg/template/template_test.go @@ -8,7 +8,7 @@ import ( ) func TestGetByID(t *testing.T) { - Templates = map[string]Template{ + templates := map[string]Template{ "example-template-1": { ID: "example-template-1", Info: Info{ @@ -21,13 +21,12 @@ func TestGetByID(t *testing.T) { }, }, } - firstTemplate, err := GetByID("example-template-1") + firstTemplate, err := GetByID(templates, "example-template-1") assert.Nil(t, err) assert.Equal(t, "example-template-1", firstTemplate.ID) noneExistTemplateID := "none-exist-template" - noneExistingTemplate, err := GetByID(noneExistTemplateID) + noneExistingTemplate, err := GetByID(templates, noneExistTemplateID) assert.Nil(t, noneExistingTemplate) assert.Error(t, err) assert.EqualError(t, err, fmt.Sprintf("template %s not found", noneExistTemplateID)) - } diff --git a/pkg/templates/downloader.go b/pkg/templates/downloader.go deleted file mode 100644 index 8643540..0000000 --- a/pkg/templates/downloader.go +++ /dev/null @@ -1,63 +0,0 @@ -package template - -import ( - "errors" - "fmt" - "os" - - "github.com/go-git/go-git/v5" -) - -func cloneTemplatesRepo(repoPath string, force bool) error { - repo, err := git.PlainOpen(repoPath) - if err == nil { - worktree, err := repo.Worktree() - if err != nil { - return err - } - - status, err := worktree.Status() - if err != nil { - return err - } - - if !force && !status.IsClean() { - return fmt.Errorf("detected uncommitted changes in %s", repoPath) - } - - err = worktree.Pull(&git.PullOptions{ - RemoteName: "origin", - Force: true, - }) - - if err != nil && err != git.NoErrAlreadyUpToDate { - return err - } - - return nil - } - - err = os.RemoveAll(repoPath) - if err != nil { - return err - } - - if err := os.MkdirAll(repoPath, 0750); err != nil { - return err - } - - _, err = git.PlainClone(repoPath, false, &git.CloneOptions{ - URL: TemplateRemoteRepoistory, - Depth: 1, - }) - - if err != nil { - removeErr := os.RemoveAll(repoPath) - if removeErr != nil { - return errors.Join(err, fmt.Errorf("cleanup failed: %w", removeErr)) - } - return err - } - - return nil -} diff --git a/pkg/templates/parser.go b/pkg/templates/parser.go deleted file mode 100644 index 23b8988..0000000 --- a/pkg/templates/parser.go +++ /dev/null @@ -1,24 +0,0 @@ -// Package template provides functionality for loading and managing vulnerable target environment templates. -package template - -import ( - "os" - "path" - - yaml "gopkg.in/yaml.v3" -) - -// LoadTemplate loads a template from the specified filepath by reading the index.yaml file. -func LoadTemplate(filepath string) (Template, error) { - var template Template - file, err := os.ReadFile(path.Join(filepath, "index.yaml")) // #nosec: G304 - if err != nil { - return template, err - } - err = yaml.Unmarshal(file, &template) - if err != nil { - return template, err - } - err = template.Validate() - return template, err -} diff --git a/pkg/templates/parser_test.go b/pkg/templates/parser_test.go deleted file mode 100644 index 5d9fba1..0000000 --- a/pkg/templates/parser_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package template - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLoadTemplate(t *testing.T) { - // create dummy content - templateContent := ` -id: example-template - -info: - name: Vulnerable Target - author: hhsteam - description: | - Vulnerable Target - references: - - http://www.vulnerabletarget.com - type: Lab - targets: - - php - - mysql - tags: - - owasp - - web - - vulnerabilities - metadata: - -providers: - docker-compose: - path: "docker-compose.yaml" -` - // create temp dir - tempDir := filepath.Join(t.TempDir(), "example-template") - - err := os.Mkdir(tempDir, 0750) - assert.NoError(t, err) - - err = os.WriteFile(filepath.Join(tempDir, "index.yaml"), []byte(templateContent), 0644) - assert.NoError(t, err) - - tpl, err := LoadTemplate(tempDir) - assert.NoError(t, err) - assert.Equal(t, "example-template", tpl.ID) - assert.Equal(t, "Vulnerable Target", tpl.Info.Name) - assert.Equal(t, "hhsteam", tpl.Info.Author) - assert.Equal(t, 1, len(tpl.Info.References)) - assert.Equal(t, 3, len(tpl.Info.Tags)) - assert.Contains(t, tpl.Providers, "docker-compose") - - // case of none exist path - _, err = LoadTemplate("/non/existent/path") - assert.Error(t, err) - -} diff --git a/pkg/templates/template.go b/pkg/templates/template.go deleted file mode 100644 index 9b0fd38..0000000 --- a/pkg/templates/template.go +++ /dev/null @@ -1,195 +0,0 @@ -package template - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/jedib0t/go-pretty/v6/table" - "github.com/rs/zerolog/log" -) - -// TemplateRemoteRepoistory is a constant for repo url. -const TemplateRemoteRepoistory string = "https://github.com/HappyHackingSpace/vt-templates" - -// Template represents a vulnerable target environment configuration. -type Template struct { - ID string `yaml:"id"` - Info Info `yaml:"info"` - ProofOfConcept map[string][]string `yaml:"poc"` - Remediation []string `yaml:"remediation"` - Providers map[string]ProviderConfig `yaml:"providers"` - PostInstall []string `yaml:"post-install"` -} - -// Info contains metadata about a template. -type Info struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Author string `yaml:"author"` - Targets []string `yaml:"targets"` - Type string `yaml:"type"` - AffectedVersions []string `yaml:"affected_versions"` - FixedVersion string `yaml:"fixed_version"` - Cwe string `yaml:"cwe"` - Cvss Cvss `yaml:"cvss"` - Tags []string `yaml:"tags"` - References []string `yaml:"references"` -} - -// ProviderConfig contains configuration for a specific provider. -type ProviderConfig struct { - Path string `yaml:"path"` -} - -// Cvss represents Common Vulnerability Scoring System information. -type Cvss struct { - Score string `yaml:"score"` - Metrics string `yaml:"metrics"` -} - -// Templates contains all loaded templates indexed by their ID. -var Templates = make(map[string]Template) - -// Init initializes the templates repository and loads all templates. -func Init() { - homeDir, err := os.UserHomeDir() - if err != nil { - log.Fatal().Msgf("%v", err) - } - - repoPath := filepath.Join(homeDir, "vt-templates") - if _, err := os.Stat(repoPath); os.IsNotExist(err) { - log.Info().Msg("Fetching templates for the first time") - err = cloneTemplatesRepo(repoPath, false) - if err != nil { - log.Fatal().Msgf("%v", err) - } - } - - loadTemplatesFromDirectory(repoPath) -} - -// loadTemplatesFromDirectory reads all templates from the given path and populates the Templates map. -func loadTemplatesFromDirectory(repoPath string) { - dirEntry, err := os.ReadDir(repoPath) - if err != nil { - log.Fatal().Msgf("%v", err) - } - - for _, categoryEntry := range dirEntry { - if strings.HasPrefix(categoryEntry.Name(), ".") || !categoryEntry.IsDir() { - continue - } - - categoryPath := filepath.Join(repoPath, categoryEntry.Name()) - loadTemplatesFromCategory(categoryPath, categoryEntry.Name()) - } -} - -// loadTemplatesFromCategory loads all templates within a single category directory. -func loadTemplatesFromCategory(categoryPath, categoryName string) { - templateEntries, err := os.ReadDir(categoryPath) - if err != nil { - log.Fatal().Msgf("Error reading category %s: %v", categoryName, err) - } - - for _, entry := range templateEntries { - if strings.HasPrefix(entry.Name(), ".") || !entry.IsDir() { - continue - } - - templatePath := filepath.Join(categoryPath, entry.Name()) - template, err := LoadTemplate(templatePath) - if err != nil { - log.Fatal().Msgf("Error loading template %s: %v", entry.Name(), err) - } - if template.ID != entry.Name() { - log.Fatal().Msgf("id and directory name should match") - } - Templates[template.ID] = template - } -} - -// SyncTemplates downloads or updates all templates from the remote repository. -func SyncTemplates() { - homeDir, err := os.UserHomeDir() - if err != nil { - log.Fatal().Msgf("%v", err) - } - - repoPath := filepath.Join(homeDir, "vt-templates") - log.Info().Msgf("cloning %s", TemplateRemoteRepoistory) - err = cloneTemplatesRepo(repoPath, true) - if err != nil { - log.Fatal().Msgf("%v", err) - } -} - -// List displays all available templates in a table format. -func List() { - ListWithFilter("") -} - -// ListWithFilter displays all available templates in a table format, optionally filtered by tag. -func ListWithFilter(filterTag string) { - t := table.NewWriter() - t.SetStyle(table.StyleDefault) - t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"ID", "Name", "Author", "Targets", "Type", "Tags"}) - - count := 0 - for _, template := range Templates { - if filterTag != "" { - hasTag := false - for _, tag := range template.Info.Tags { - if strings.EqualFold(tag, filterTag) || strings.Contains(strings.ToLower(tag), strings.ToLower(filterTag)) { - hasTag = true - break - } - } - if !hasTag { - continue - } - } - - tags := strings.Join(template.Info.Tags, ", ") - targets := strings.Join(template.Info.Targets, ", ") - t.AppendRow(table.Row{ - template.ID, - template.Info.Name, - template.Info.Author, - targets, - template.Info.Type, - tags, - }) - count++ - } - - if count == 0 { - if filterTag != "" { - fmt.Printf("No templates found with tag matching '%s'\n", filterTag) - } else { - fmt.Println("No templates found") - } - return - } - - if filterTag != "" { - t.SetCaption("Found %d templates with tag matching '%s'", count, filterTag) - } else { - t.SetCaption("there are %d templates", count) - } - t.SetIndexColumn(0) - t.Render() -} - -// GetByID retrieves a template by its ID. -func GetByID(templateID string) (*Template, error) { - template := Templates[templateID] - if template.ID == "" { - return nil, fmt.Errorf("template %s not found", templateID) - } - return &template, nil -} diff --git a/pkg/templates/template_test.go b/pkg/templates/template_test.go deleted file mode 100644 index f415d26..0000000 --- a/pkg/templates/template_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package template - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetByID(t *testing.T) { - Templates = map[string]Template{ - "example-template-1": { - ID: "example-template-1", - Info: Info{ - Name: "example-template-name", - Author: "example-template-author", - Tags: []string{ - "test", - "example", - }, - }, - }, - } - firstTemplate, err := GetByID("example-template-1") - assert.Nil(t, err) - assert.Equal(t, "example-template-1", firstTemplate.ID) - noneExistTemplateID := "none-exist-template" - noneExistingTemplate, err := GetByID(noneExistTemplateID) - assert.Nil(t, noneExistingTemplate) - assert.Error(t, err) - assert.EqualError(t, err, fmt.Sprintf("template %s not found", noneExistTemplateID)) - -} diff --git a/pkg/templates/validator.go b/pkg/templates/validator.go deleted file mode 100644 index 65a7b0e..0000000 --- a/pkg/templates/validator.go +++ /dev/null @@ -1,92 +0,0 @@ -package template - -import ( - "fmt" - "path/filepath" - "regexp" - "strings" -) - -var ( - templateIDRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]*$`) - allowedProviderExts = map[string]bool{ - ".yml": true, - ".yaml": true, - } -) - -// Validate validates the template structure and content. -func (template Template) Validate() error { - if template.ID == "" { - return fmt.Errorf("id can not be empty") - } - - if !templateIDRegex.MatchString(template.ID) { - return fmt.Errorf("template '%s': id contains invalid characters", template.ID) - } - - if len(template.Providers) == 0 { - return fmt.Errorf("template '%s': no providers specified in the template", template.ID) - } - - for k, v := range template.Providers { - pcError := v.Validate(template.ID, k) - if pcError != nil { - return pcError - } - } - - infoError := template.Info.Validate(template.ID) - if infoError != nil { - return infoError - } - - return nil -} - -// Validate validates the info structure and content. -func (info Info) Validate(templateID string) error { - if info.Name == "" { - return fmt.Errorf("template '%s': name can not be empty", templateID) - } - if info.Author == "" { - return fmt.Errorf("template '%s': author can not be empty", templateID) - } - if len(info.Targets) == 0 { - return fmt.Errorf("template '%s': targets can not be empty", templateID) - } - if info.Type == "" { - return fmt.Errorf("template '%s': type can not be empty", templateID) - } - if len(info.Tags) == 0 { - return fmt.Errorf("template '%s': tags can not be empty", templateID) - } - return nil -} - -// Validate validates the provider configuration structure and content. -func (pc ProviderConfig) Validate(templateID, name string) error { - providerPath := pc.Path - if providerPath == "" { - return fmt.Errorf("template '%s', provider '%s': path is empty", templateID, name) - } - - if filepath.IsAbs(providerPath) { - return fmt.Errorf("template '%s', provider '%s': absolute paths are not allowed", templateID, name) - } - - if strings.Contains(providerPath, "..") { - return fmt.Errorf("template '%s', provider '%s': path contains invalid '..' segments", templateID, name) - } - - ext := filepath.Ext(providerPath) - if !isAllowedExtension(ext) { - return fmt.Errorf("template '%s', provider '%s': provider file must have one of the allowed extensions: %v", templateID, name, allowedProviderExts) - } - - return nil -} - -func isAllowedExtension(ext string) bool { - return allowedProviderExts[ext] -} From 7df30a9c2df5114d3cebf6ca4ba8842187fd96f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 17:03:15 +0300 Subject: [PATCH 4/7] fix: code quality improvements - Remove unreachable os.Exit(1) after log.Fatal() in main.go - Remove unused os import from main.go - Remove dead GetTemplate method from App struct - Add nil check guard clause in logger.New() to prevent panic - Update go-git dependency from v5.16.4 to v5.16.3 --- cmd/vt/main.go | 3 --- go.mod | 2 +- go.sum | 4 ++-- internal/app/app.go | 9 --------- internal/logger/logger.go | 5 +++++ 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/cmd/vt/main.go b/cmd/vt/main.go index 4bfde93..c56018f 100644 --- a/cmd/vt/main.go +++ b/cmd/vt/main.go @@ -2,8 +2,6 @@ package main import ( - "os" - "github.com/happyhackingspace/vulnerable-target/internal/app" "github.com/happyhackingspace/vulnerable-target/internal/banner" "github.com/happyhackingspace/vulnerable-target/internal/cli" @@ -50,6 +48,5 @@ func main() { // Create and run CLI if err := cli.New(application).Run(); err != nil { log.Fatal().Err(err).Msg("CLI error") - os.Exit(1) } } diff --git a/go.mod b/go.mod index dbe0f83..095539f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/compose-spec/compose-go/v2 v2.8.1 github.com/docker/cli v28.3.3+incompatible github.com/docker/compose/v2 v2.39.2 - github.com/go-git/go-git/v5 v5.16.4 + github.com/go-git/go-git/v5 v5.16.3 github.com/jedib0t/go-pretty/v6 v6.6.6 github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.9.1 diff --git a/go.sum b/go.sum index a5b2093..c858abe 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= -github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= +github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= diff --git a/internal/app/app.go b/internal/app/app.go index 50ee602..7cd5ae2 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -54,15 +54,6 @@ func NewApp( } } -// GetTemplate retrieves a template by ID from the app's template map. -func (a *App) GetTemplate(id string) (*template.Template, bool) { - t, ok := a.Templates[id] - if !ok { - return nil, false - } - return &t, true -} - // GetProvider retrieves a provider by name from the app's provider map. func (a *App) GetProvider(name string) (provider.Provider, bool) { p, ok := a.Providers[name] diff --git a/internal/logger/logger.go b/internal/logger/logger.go index e58b454..b95a9ea 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -31,7 +31,12 @@ func DefaultConfig() *Config { // New creates a new zerolog.Logger with the given configuration. // This is a pure factory function that returns a configured logger // without modifying any global state. +// If cfg is nil, DefaultConfig() is used. func New(cfg *Config) zerolog.Logger { + if cfg == nil { + cfg = DefaultConfig() + } + level, err := zerolog.ParseLevel(cfg.Level) if err != nil { level = zerolog.InfoLevel From bbc142bc70406b76a5702b54cf2dee755452283d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 18:11:47 +0300 Subject: [PATCH 5/7] minor --- cmd/vt/main.go | 11 ----------- internal/cli/cli.go | 1 + internal/cli/start.go | 4 ++++ internal/cli/stop.go | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/cmd/vt/main.go b/cmd/vt/main.go index c56018f..eb9f7c7 100644 --- a/cmd/vt/main.go +++ b/cmd/vt/main.go @@ -3,7 +3,6 @@ package main import ( "github.com/happyhackingspace/vulnerable-target/internal/app" - "github.com/happyhackingspace/vulnerable-target/internal/banner" "github.com/happyhackingspace/vulnerable-target/internal/cli" "github.com/happyhackingspace/vulnerable-target/internal/logger" "github.com/happyhackingspace/vulnerable-target/internal/state" @@ -14,23 +13,16 @@ import ( ) func main() { - // Initialize configuration cfg := app.DefaultConfig() - // Initialize logger (sets global logger for zerolog) appLogger := logger.NewWithLevel(cfg.LogLevel) logger.SetGlobal(appLogger) - // Print banner - banner.Print() - - // Load templates from repository templates, err := template.LoadTemplates(cfg.TemplatesPath) if err != nil { log.Fatal().Err(err).Msg("failed to load templates") } - // Create state manager storeCfg := disk.NewConfig(). WithFileName("deployments.db"). WithBucketName("deployments") @@ -39,13 +31,10 @@ func main() { log.Fatal().Err(err).Msg("failed to create state manager") } - // Create providers with injected dependencies providers := registry.NewProviders(stateManager) - // Create application context application := app.NewApp(templates, providers, stateManager, cfg) - // Create and run CLI if err := cli.New(application).Run(); err != nil { log.Fatal().Err(err).Msg("CLI error") } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index b71e598..fadffdb 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -55,6 +55,7 @@ func (c *CLI) setupCommands() { }, SilenceErrors: true, } + c.rootCmd.SetHelpTemplate(banner.Banner() + "\n" + c.rootCmd.HelpTemplate()) // Setup root flags c.rootCmd.PersistentFlags().StringP("verbosity", "v", zerolog.InfoLevel.String(), diff --git a/internal/cli/start.go b/internal/cli/start.go index b170d61..e4c7bdf 100644 --- a/internal/cli/start.go +++ b/internal/cli/start.go @@ -65,6 +65,10 @@ func (c *CLI) newStartCommand() *cobra.Command { cmd.Flags().String("id", "", "Specify a template ID for targeted vulnerable environment") + if err := cmd.MarkFlagRequired("provider"); err != nil { + log.Fatal().Msgf("%v", err) + } + if err := cmd.MarkFlagRequired("id"); err != nil { log.Fatal().Msgf("%v", err) } diff --git a/internal/cli/stop.go b/internal/cli/stop.go index 3a9e341..73e064b 100644 --- a/internal/cli/stop.go +++ b/internal/cli/stop.go @@ -44,7 +44,7 @@ func (c *CLI) newStopCommand() *cobra.Command { }, } - cmd.Flags().StringP("provider", "p", "", + cmd.Flags().StringP("provider", "p", "docker-compose", fmt.Sprintf("Specify the provider for building a vulnerable environment (%s)", strings.Join(c.providerNames(), ", "))) From d11e4e5f9e3477094e403a2dcdc02f74a5b24c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 18:12:07 +0300 Subject: [PATCH 6/7] commit to last commit --- pkg/provider/dockercompose/utils.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/provider/dockercompose/utils.go b/pkg/provider/dockercompose/utils.go index 87ae126..e5d1e33 100644 --- a/pkg/provider/dockercompose/utils.go +++ b/pkg/provider/dockercompose/utils.go @@ -2,6 +2,7 @@ package dockercompose import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -13,6 +14,7 @@ import ( "github.com/docker/cli/cli/flags" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose" + "github.com/happyhackingspace/vulnerable-target/internal/app" tmpl "github.com/happyhackingspace/vulnerable-target/pkg/template" ) @@ -40,7 +42,7 @@ func loadComposeProject(template tmpl.Template) (*types.Project, error) { return nil, fmt.Errorf("template %q docker-compose.path is empty", template.ID) } - composePath, workingDir, err := resolveComposePath(template.ID, providerConfig.Path) + composePath, workingDir, err := resolveComposePath(template.ID, template.Info.Type, providerConfig.Path) if err != nil { return nil, err } @@ -165,17 +167,22 @@ func runComposeStats(dockerCli command.Cli, project *types.Project) (bool, error return true, nil } -func resolveComposePath(templateID, path string) (composePath string, workingDir string, err error) { +func resolveComposePath(templateID, templateType, path string) (composePath string, workingDir string, err error) { if filepath.IsAbs(path) { return path, filepath.Dir(path), nil } + var categoryMap = map[string]string{ + "Lab": "labs", + "CVE": "cves", + } - wd, err := os.Getwd() - if err != nil { - return "", "", err + category, ctgExist := categoryMap[templateType] + if !ctgExist { + return "", "", errors.New("undefined category for template") } - composePath = filepath.Join(wd, "templates", templateID, path) + cfg := app.DefaultConfig() + composePath = filepath.Join(cfg.TemplatesPath, category, templateID, path) if _, statErr := os.Stat(composePath); statErr != nil { return "", "", fmt.Errorf("compose file %q not accessible: %w", composePath, statErr) From 903340fc57b14f3613306538614a5a51c14d260d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 2 Jan 2026 18:21:06 +0300 Subject: [PATCH 7/7] fix: add case-insensitive template type matching in resolveComposePath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The categoryMap lookup now normalizes templateType to lowercase before matching, preventing failures when templates use different casing like "lab" instead of "Lab". Error message now lists accepted values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- pkg/provider/dockercompose/utils.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/provider/dockercompose/utils.go b/pkg/provider/dockercompose/utils.go index e5d1e33..9e4f32e 100644 --- a/pkg/provider/dockercompose/utils.go +++ b/pkg/provider/dockercompose/utils.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" "github.com/compose-spec/compose-go/v2/loader" @@ -172,13 +173,13 @@ func resolveComposePath(templateID, templateType, path string) (composePath stri return path, filepath.Dir(path), nil } var categoryMap = map[string]string{ - "Lab": "labs", - "CVE": "cves", + "lab": "labs", + "cve": "cves", } - category, ctgExist := categoryMap[templateType] + category, ctgExist := categoryMap[strings.ToLower(templateType)] if !ctgExist { - return "", "", errors.New("undefined category for template") + return "", "", errors.New("undefined category for template: type must be one of 'lab', 'cve'") } cfg := app.DefaultConfig()