Skip to content

Commit 73956dd

Browse files
committed
allow to run checks in debug mode
1 parent 9435add commit 73956dd

File tree

8 files changed

+114
-26
lines changed

8 files changed

+114
-26
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,40 @@ vulcan-local -checktypes "./vulcan-checks/cmd" -t example.com -a Hostname -l deb
257257
At this moment, all the available checks are implemented in [Go](https://go.dev).
258258
For that reason it's required to have `go` installed in the system.
259259

260+
We allow to start the check running in the container in debug mode with the `-debug` flag.
261+
262+
For that to work the check image must be based on `alpine` in the same architecture.
263+
264+
```sh
265+
vulcan-local -checktypes "./vulcan-checks/cmd" -t example.com -a Hostname -l debug -i vulcan-nuclei -debug
266+
```
267+
268+
The check will start in debug mode exposing the port `2345` it will wait until a debug session is attached.
269+
270+
In another terminal open dlv to connect to the check running inside the container.
271+
272+
```sh
273+
dlv connect 127.0.0.1:2345
274+
```
275+
276+
Or debug vulcan-checks in VS Code with a configuration like this and some breakpoints.
277+
278+
```json
279+
{
280+
"version": "0.2.0",
281+
"configurations": [
282+
{
283+
"name": "Connect to server",
284+
"type": "go",
285+
"request": "attach",
286+
"mode": "remote",
287+
"port": 2345,
288+
"host": "127.0.0.1"
289+
}
290+
]
291+
}
292+
```
293+
260294
## Docker usage
261295

262296
Using the existing docker image:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/adevinta/vulcan-report v1.0.0
1010
github.com/adevinta/vulcan-types v1.2.16
1111
github.com/docker/docker v26.1.2+incompatible
12+
github.com/docker/go-connections v0.5.0
1213
github.com/drone/envsubst v1.0.3
1314
github.com/go-git/go-git/v5 v5.12.0
1415
github.com/google/go-cmp v0.6.0
@@ -41,7 +42,6 @@ require (
4142
github.com/docker/distribution v2.8.3+incompatible // indirect
4243
github.com/docker/docker-credential-helpers v0.6.4 // indirect
4344
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
44-
github.com/docker/go-connections v0.5.0 // indirect
4545
github.com/docker/go-metrics v0.0.1 // indirect
4646
github.com/docker/go-units v0.4.0 // indirect
4747
github.com/emirpasic/gods v1.18.1 // indirect

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func main() {
9090
var showHelp, showVersion bool
9191
flag.BoolVar(&showHelp, "h", false, "print usage")
9292
flag.BoolVar(&showVersion, "version", false, "print version")
93+
flag.BoolVar(&cfg.Conf.Debug, "debug", false, "activate debug mode for the check. Checktypes must point to a source code directory.")
9394
flag.Func("c", genFlagMsg("config file", "vulcan.yaml", "", envDefaultVulcanLocalUri, nil), func(s string) error {
9495
cmdConfigs = append(cmdConfigs, s)
9596
return nil
@@ -238,6 +239,10 @@ func main() {
238239
}
239240
}
240241

242+
if cfg.Conf.Debug {
243+
log.Info("Setting concurrency to 1 to allow debug.")
244+
cfg.Conf.Concurrency = 1
245+
}
241246
exitCode, err = cmd.Run(cfg, log)
242247
if err != nil {
243248
log.Error(err)

pkg/checktypes/code.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,52 @@ func ParseCode(u string) (Code, bool) {
4343
// Build builds the checktype defined in a directory. It builds the binary and
4444
// the docker image of the checktype and returns the name of the docker image
4545
// built.
46-
func (c Code) Build(logger log.Logger) (string, error) {
47-
modified, err := c.isModified(logger)
46+
func (c Code) Build(debug bool, logger log.Logger) (string, error) {
47+
modified, err := c.isModified(debug, logger)
4848
if err != nil {
4949
return "", err
5050
}
5151
if !modified {
52-
logger.Infof("No changes in checktype in dir %s, reusing image %s", string(c), c.imageName())
53-
return c.imageName(), nil
52+
logger.Infof("No changes in checktype in dir %s, reusing image %s", string(c), c.imageName(debug))
53+
return c.imageName(debug), nil
5454
}
5555
logger.Infof("Compiling checktype in dir %s", c)
5656
dir := string(c)
5757
// Run go build in the checktype dir.
5858
if err := goBuildDir(dir); err != nil {
5959
return "", err
6060
}
61+
dockerfile := "Dockerfile"
62+
63+
// In case of debug, we need to add the delve debugger to the image.
64+
// We create a new Dockerfile with the necessary changes.
65+
// The original Dockerfile is kept in the dir but excluded from the Tar file.
66+
// The new Dockerfile is removed after the image is built.
67+
if debug {
68+
b, err := os.ReadFile(path.Join(dir, "Dockerfile"))
69+
if err != nil {
70+
return "", err
71+
}
72+
dockerfile = "Dockerfile.debug"
73+
add := fmt.Sprintf(`
74+
RUN apk add --no-cache go
75+
RUN go install github.com/go-delve/delve/cmd/dlv@latest
76+
EXPOSE 2345
77+
ENTRYPOINT /root/go/bin/dlv --listen=:2345 --headless=true --api-version=2 --log exec %s
78+
`, filepath.Base(dir))
79+
80+
b = append(b, []byte(add)...)
81+
if err := os.WriteFile(path.Join(dir, dockerfile), b, 0644); err != nil {
82+
return "", err
83+
}
84+
85+
defer func() {
86+
if err := os.Remove(path.Join(dir, dockerfile)); err != nil {
87+
logger.Errorf("error removing %s: %v", dockerfile, err)
88+
}
89+
}()
90+
}
91+
6192
// Build a Tar file with the docker image contents.
6293
logger.Infof("Building image for checktype in dir %s", dir)
6394
contents, err := buildTarFromDir(dir)
@@ -72,8 +103,8 @@ func (c Code) Build(logger log.Logger) (string, error) {
72103
t := modif.Format(time.RFC822)
73104
logger.Debugf("Last modified time for checktype in dir %s is %s", dir, t)
74105
labels := map[string]string{modifTimeLabel: t}
75-
image := c.imageName()
76-
r, err := buildDockerdImage(contents, []string{image}, labels)
106+
image := c.imageName(debug)
107+
r, err := buildDockerdImage(contents, []string{image}, labels, dockerfile)
77108
if err != nil {
78109
return "", err
79110
}
@@ -82,19 +113,19 @@ func (c Code) Build(logger log.Logger) (string, error) {
82113
return image, nil
83114
}
84115

85-
func (c Code) isModified(logger log.Logger) (bool, error) {
86-
labels, err := imageInfo(c.imageName())
116+
func (c Code) isModified(debug bool, logger log.Logger) (bool, error) {
117+
labels, err := imageInfo(c.imageName(debug))
87118
if err != nil {
88119
return false, err
89120
}
90121
imageTimeS, ok := labels[modifTimeLabel]
91122
if !ok {
92-
logger.Infof("Image %s does not contain the label %s", c.imageName(), modifTimeLabel)
123+
logger.Infof("Image %s does not contain the label %s", c.imageName(debug), modifTimeLabel)
93124
return true, nil
94125
}
95126
_, err = time.Parse(time.RFC822, imageTimeS)
96127
if err != nil {
97-
logger.Infof("invalid time, %+w defined in the label %s of the image %s", err, modifTimeLabel, c.imageName())
128+
logger.Infof("invalid time, %+w defined in the label %s of the image %s", err, modifTimeLabel, c.imageName(debug))
98129
return true, nil
99130
}
100131
dirTime, err := c.lastModified(logger)
@@ -142,14 +173,16 @@ func (c Code) lastModified(logger log.Logger) (time.Time, error) {
142173
return *latest, nil
143174
}
144175

145-
func (c Code) imageName() string {
146-
dir := string(c)
147-
image := path.Base(dir)
148-
return fmt.Sprintf("%s-%s", image, "local")
176+
func (c Code) imageName(debug bool) string {
177+
base := path.Base(string(c))
178+
if debug {
179+
return fmt.Sprintf("%s-local-debug", base)
180+
}
181+
return fmt.Sprintf("%s-local", base)
149182
}
150183

151184
func goBuildDir(dir string) error {
152-
args := []string{"build", "-a", "-ldflags", "-extldflags -static", "."}
185+
args := []string{"build", "-gcflags", "all=-N -l", "."}
153186
cmd := exec.Command("go", args...)
154187
cmd.Env = os.Environ()
155188
cmd.Env = append(cmd.Env, "GOOS=linux", "CGO_ENABLED=0")

pkg/checktypes/docker.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@ import (
1414

1515
"github.com/docker/docker/api/types"
1616
"github.com/docker/docker/api/types/filters"
17+
"github.com/docker/docker/api/types/image"
1718
"github.com/docker/docker/client"
1819
)
1920

2021
// buildDockerImage builds and image given a tar, a list of tags and labels.
21-
func buildDockerdImage(tarFile io.Reader, tags []string, labels map[string]string) (response string, err error) {
22+
func buildDockerdImage(tarFile io.Reader, tags []string, labels map[string]string, dockerfile string) (response string, err error) {
2223
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
2324
if err != nil {
2425
return "", err
2526
}
2627

2728
ctx := context.Background()
2829
buildOptions := types.ImageBuildOptions{
29-
Tags: tags,
30-
Labels: labels,
31-
Remove: true,
30+
Dockerfile: dockerfile,
31+
Tags: tags,
32+
Labels: labels,
33+
Remove: true,
3234
}
3335

3436
re, err := cli.ImageBuild(ctx, tarFile, buildOptions)
@@ -40,7 +42,7 @@ func buildDockerdImage(tarFile io.Reader, tags []string, labels map[string]strin
4042
return strings.Join(lines, "\n"), err
4143
}
4244

43-
func imageInfo(image string) (map[string]string, error) {
45+
func imageInfo(imageName string) (map[string]string, error) {
4446
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
4547
if err != nil {
4648
return nil, err
@@ -49,9 +51,9 @@ func imageInfo(image string) (map[string]string, error) {
4951
ctx := context.Background()
5052
filter := filters.KeyValuePair{
5153
Key: "reference",
52-
Value: image,
54+
Value: imageName,
5355
}
54-
options := types.ImageListOptions{
56+
options := image.ListOptions{
5557
Filters: filters.NewArgs(filter),
5658
}
5759
infos, error := cli.ImageList(ctx, options)

pkg/cmd/main.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/adevinta/vulcan-agent/queue"
2525
"github.com/adevinta/vulcan-agent/queue/chanqueue"
2626
"github.com/docker/docker/client"
27+
"github.com/docker/go-connections/nat"
2728
"github.com/sirupsen/logrus"
2829

2930
"github.com/adevinta/vulcan-local/pkg/checktypes"
@@ -182,7 +183,7 @@ func Run(cfg *config.Config, log *logrus.Logger) (int, error) {
182183
},
183184
}
184185
beforeRun := func(params backend.RunParams, rc *docker.RunConfig) error {
185-
return beforeCheckRun(params, rc, agentIP, gs, hostIP, cfg.Checks, log)
186+
return beforeCheckRun(params, rc, agentIP, gs, hostIP, cfg.Checks, cfg.Conf.Debug, log)
186187
}
187188
backend, err := docker.NewBackend(log, agentConfig, beforeRun)
188189
if err != nil {
@@ -334,7 +335,7 @@ func getHostIP(l agentlog.Logger) string {
334335
// properly when they are executed locally.
335336
func beforeCheckRun(params backend.RunParams, rc *docker.RunConfig,
336337
agentIP string, gs gitservice.GitService, hostIP string,
337-
checks []config.Check, log *logrus.Logger) error {
338+
checks []config.Check, debug bool, log *logrus.Logger) error {
338339
newTarget := params.Target
339340
// If the asset type is a DockerImage mount the docker socket in case the image is already there,
340341
// and the check can access it.
@@ -387,6 +388,18 @@ func beforeCheckRun(params backend.RunParams, rc *docker.RunConfig,
387388
// depending on the target/assettype.
388389
rc.ContainerConfig.Env = upsertEnv(rc.ContainerConfig.Env, "VULCAN_ALLOW_PRIVATE_IPS", strconv.FormatBool(true))
389390

391+
if debug {
392+
log.Infof("Exposing port 2345 for debugging")
393+
394+
rc.ContainerConfig.ExposedPorts = map[nat.Port]struct{}{
395+
nat.Port("2345/tcp"): {},
396+
}
397+
398+
rc.HostConfig.PortBindings = nat.PortMap{
399+
nat.Port("2345/tcp"): []nat.PortBinding{{HostIP: "127.0.0.1", HostPort: "2345"}},
400+
}
401+
}
402+
390403
return nil
391404
}
392405

pkg/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type Registry struct {
6464
}
6565

6666
type Conf struct {
67+
Debug bool
6768
DockerBin string `yaml:"dockerBin"`
6869
GitBin string `yaml:"gitBin"`
6970
PullPolicy agentconfig.PullPolicy `yaml:"pullPolicy"`

pkg/generator/generator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func GenerateJobs(cfg *config.Config, agentIp, hostIp string, gs gitservice.GitS
7070
continue
7171
}
7272
if code, ok := checktypes.ParseCode(ch.Image); ok {
73-
image, err := code.Build(l)
73+
image, err := code.Build(cfg.Conf.Debug, l)
7474
if err != nil {
7575
return nil, err
7676
}

0 commit comments

Comments
 (0)