Skip to content

Commit 3f63736

Browse files
committed
allow to run checks in debug mode
1 parent 76b0af2 commit 3f63736

File tree

7 files changed

+117
-24
lines changed

7 files changed

+117
-24
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:

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: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"os/exec"
1515
"path"
1616
"path/filepath"
17+
"slices"
1718
"strings"
1819
"time"
1920

@@ -43,24 +44,57 @@ func ParseCode(u string) (Code, bool) {
4344
// Build builds the checktype defined in a directory. It builds the binary and
4445
// the docker image of the checktype and returns the name of the docker image
4546
// built.
46-
func (c Code) Build(logger log.Logger) (string, error) {
47-
modified, err := c.isModified(logger)
47+
func (c Code) Build(debug bool, logger log.Logger) (string, error) {
48+
modified, err := c.isModified(debug, logger)
4849
if err != nil {
4950
return "", err
5051
}
5152
if !modified {
52-
logger.Infof("No changes in checktype in dir %s, reusing image %s", string(c), c.imageName())
53-
return c.imageName(), nil
53+
logger.Infof("No changes in checktype in dir %s, reusing image %s", string(c), c.imageName(debug))
54+
return c.imageName(debug), nil
5455
}
5556
logger.Infof("Compiling checktype in dir %s", c)
5657
dir := string(c)
5758
// Run go build in the checktype dir.
5859
if err := goBuildDir(dir); err != nil {
5960
return "", err
6061
}
62+
dockerfile := "Dockerfile"
63+
64+
// In case of debug, we need to add the delve debugger to the image.
65+
// We create a new Dockerfile with the necessary changes.
66+
// The original Dockerfile is kept in the dir but excluded from the Tar file.
67+
// The new Dockerfile is removed after the image is built.
68+
if debug {
69+
b, err := os.ReadFile(path.Join(dir, "Dockerfile"))
70+
if err != nil {
71+
return "", err
72+
}
73+
dockerfile = "Dockerfile.debug"
74+
add := fmt.Sprintf(`
75+
RUN apk add --no-cache go
76+
RUN go install github.com/go-delve/delve/cmd/dlv@latest
77+
EXPOSE 2345
78+
ENTRYPOINT /root/go/bin/dlv --listen=:2345 --headless=true --api-version=2 --log exec %s
79+
`, filepath.Base(dir))
80+
81+
b = append(b, []byte(add)...)
82+
if err := os.WriteFile(path.Join(dir, dockerfile), b, 0644); err != nil {
83+
return "", err
84+
}
85+
}
86+
87+
defer func() {
88+
if dockerfile != "Dockerfile" {
89+
if err := os.Remove(path.Join(dir, dockerfile)); err != nil {
90+
logger.Errorf("error removing %s: %v", dockerfile, err)
91+
}
92+
}
93+
}()
94+
6195
// Build a Tar file with the docker image contents.
6296
logger.Infof("Building image for checktype in dir %s", dir)
63-
contents, err := buildTarFromDir(dir)
97+
contents, err := buildTarFromDir(dir, debug)
6498
if err != nil {
6599
return "", err
66100
}
@@ -72,8 +106,8 @@ func (c Code) Build(logger log.Logger) (string, error) {
72106
t := modif.Format(time.RFC822)
73107
logger.Debugf("Last modified time for checktype in dir %s is %s", dir, t)
74108
labels := map[string]string{modifTimeLabel: t}
75-
image := c.imageName()
76-
r, err := buildDockerdImage(contents, []string{image}, labels)
109+
image := c.imageName(debug)
110+
r, err := buildDockerdImage(contents, []string{image}, labels, dockerfile)
77111
if err != nil {
78112
return "", err
79113
}
@@ -82,19 +116,19 @@ func (c Code) Build(logger log.Logger) (string, error) {
82116
return image, nil
83117
}
84118

85-
func (c Code) isModified(logger log.Logger) (bool, error) {
86-
labels, err := imageInfo(c.imageName())
119+
func (c Code) isModified(debug bool, logger log.Logger) (bool, error) {
120+
labels, err := imageInfo(c.imageName(debug))
87121
if err != nil {
88122
return false, err
89123
}
90124
imageTimeS, ok := labels[modifTimeLabel]
91125
if !ok {
92-
logger.Infof("Image %s does not contain the label %s", c.imageName(), modifTimeLabel)
126+
logger.Infof("Image %s does not contain the label %s", c.imageName(debug), modifTimeLabel)
93127
return true, nil
94128
}
95129
_, err = time.Parse(time.RFC822, imageTimeS)
96130
if err != nil {
97-
logger.Infof("invalid time, %+w defined in the label %s of the image %s", err, modifTimeLabel, c.imageName())
131+
logger.Infof("invalid time, %+w defined in the label %s of the image %s", err, modifTimeLabel, c.imageName(debug))
98132
return true, nil
99133
}
100134
dirTime, err := c.lastModified(logger)
@@ -142,14 +176,16 @@ func (c Code) lastModified(logger log.Logger) (time.Time, error) {
142176
return *latest, nil
143177
}
144178

145-
func (c Code) imageName() string {
146-
dir := string(c)
147-
image := path.Base(dir)
148-
return fmt.Sprintf("%s-%s", image, "local")
179+
func (c Code) imageName(debug bool) string {
180+
base := path.Base(string(c))
181+
if debug {
182+
return fmt.Sprintf("%s-local-debug", base)
183+
}
184+
return fmt.Sprintf("%s-local", base)
149185
}
150186

151187
func goBuildDir(dir string) error {
152-
args := []string{"build", "-a", "-ldflags", "-extldflags -static", "."}
188+
args := []string{"build", "-gcflags", "all=-N -l", "."}
153189
cmd := exec.Command("go", args...)
154190
cmd.Env = os.Environ()
155191
cmd.Env = append(cmd.Env, "GOOS=linux", "CGO_ENABLED=0")
@@ -160,7 +196,7 @@ func goBuildDir(dir string) error {
160196
return cmd.Run()
161197
}
162198

163-
func buildTarFromDir(dirPath string) (*bytes.Buffer, error) {
199+
func buildTarFromDir(dirPath string, excludeDockerfile bool) (*bytes.Buffer, error) {
164200
dir, err := os.Open(path.Clean(dirPath))
165201
if err != nil {
166202
return nil, err
@@ -172,6 +208,9 @@ func buildTarFromDir(dirPath string) (*bytes.Buffer, error) {
172208
return nil, err
173209
}
174210

211+
if excludeDockerfile {
212+
files = slices.DeleteFunc(files, func(f fs.FileInfo) bool { return f.Name() == "Dockerfile" })
213+
}
175214
var output bytes.Buffer
176215
tarfileWriter := tar.NewWriter(&output)
177216
defer tarfileWriter.Close() // nolint: errcheck

pkg/checktypes/docker.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@ import (
1818
)
1919

2020
// 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) {
21+
func buildDockerdImage(tarFile io.Reader, tags []string, labels map[string]string, dockerfile string) (response string, err error) {
2222
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
2323
if err != nil {
2424
return "", err
2525
}
2626

2727
ctx := context.Background()
2828
buildOptions := types.ImageBuildOptions{
29-
Tags: tags,
30-
Labels: labels,
31-
Remove: true,
29+
Dockerfile: dockerfile,
30+
Tags: tags,
31+
Labels: labels,
32+
Remove: true,
3233
}
3334

3435
re, err := cli.ImageBuild(ctx, tarFile, buildOptions)

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)