diff --git a/lefthook.yml b/lefthook.yml index 0888c9e0f..8754b6dd1 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,15 +1,15 @@ -# see https://github.com/evilmartians/lefthook for references -pre-push: - follow: true - piped: true # Stop if one of the steps fail - commands: - 1_generate: - run: make generate - 2_lint: - run: make lint - 3_format: - run: make format - 4_check_mod_change: - run: go mod tidy - 5_check_local_changes: - run: make no-local-changes +## see https://github.com/evilmartians/lefthook for references +#pre-push: +# follow: true +# piped: true # Stop if one of the steps fail +# commands: +# 1_generate: +# run: make generate +# 2_lint: +# run: make lint +# 3_format: +# run: make format +# 4_check_mod_change: +# run: go mod tidy +# 5_check_local_changes: +# run: make no-local-changes diff --git a/pkg/nginxprocess/process.go b/pkg/nginxprocess/process.go index aebf0fd57..745c19148 100644 --- a/pkg/nginxprocess/process.go +++ b/pkg/nginxprocess/process.go @@ -7,13 +7,26 @@ package nginxprocess import ( + "bufio" "context" + "fmt" + "github.com/nginx/agent/v3/internal/datasource/host/exec" + "log/slog" + "os" + "strconv" "strings" "time" "github.com/shirou/gopsutil/v4/process" ) +const ( + keyValueLen = 2 + flagLen = 1 + workerString = "nginx: worker %s" + masterString = "nginx: master %s" +) + // Process contains a snapshot of read-only data about an OS-level NGINX process. Create using [List] or [Find]. type Process struct { // Created is when this process was created, precision varies by platform and is at best to the millisecond. On @@ -25,6 +38,8 @@ type Process struct { Status string // process status, only present if this process was created using [WithStatus] PID int32 PPID int32 // parent PID + // TODO: after this is out of spike I will be using this for returning a map + Master bool } // IsWorker returns true if the process is a NGINX worker process. @@ -81,7 +96,19 @@ func convert(ctx context.Context, p *process.Process, o options) (*Process, erro return nil, errNotAnNginxProcess } - if strings.HasPrefix(cmdLine, "nginx:") || strings.HasPrefix(cmdLine, "{nginx-debug} nginx:") { + var sbin string + var pidPath string + configArgs := nginxArgs(ctx) + if configArgs["sbin-path"] != nil { + sbin = configArgs["sbin-path"].(string) + } + + if configArgs["pid-path"] != nil { + pidPath = configArgs["pid-path"].(string) + } + + if strings.HasPrefix(cmdLine, "nginx:") || strings.HasPrefix(cmdLine, "{nginx-debug} nginx:") || + strings.HasPrefix(cmdLine, "/run/rosetta/rosetta") { var status string if o.loadStatus { flags, _ := p.StatusWithContext(ctx) // slow: shells out to ps @@ -96,7 +123,7 @@ func convert(ctx context.Context, p *process.Process, o options) (*Process, erro ppid, _ := p.PpidWithContext(ctx) exe, _ := p.ExeWithContext(ctx) - return &Process{ + nginxProcess := &Process{ PID: p.Pid, PPID: ppid, Name: name, @@ -104,12 +131,102 @@ func convert(ctx context.Context, p *process.Process, o options) (*Process, erro Created: created, Status: status, Exe: exe, - }, ctx.Err() + } + + // Check if process is master process + if strings.HasPrefix(cmdLine, "nginx: master") || + strings.HasPrefix(cmdLine, "{nginx-debug} nginx: master") { + slog.InfoContext(ctx, "Nginx master process - cmd", "pid", nginxProcess) + nginxProcess.Master = true + // Check if process is worker process + } else if strings.HasPrefix(cmdLine, "nginx: worker") { + nginxProcess.Master = false + slog.InfoContext(ctx, "Nginx worker process - cmd", "pid", nginxProcess) + // if process is not either could be running on rosetta check nginx pid file + // Check if process is master by checking pid matches nginx pid file + } else if p.Pid == nginxPID(ctx, pidPath) { + nginxProcess.Master = true + nginxProcess.Cmd = fmt.Sprintf(masterString, sbin) + nginxProcess.Exe = sbin + slog.InfoContext(ctx, "Nginx master process - file", "pid", nginxProcess) + // Check if process is worker by checking ppid matches nginx pid file + } else if ppid == nginxPID(ctx, pidPath) { + slog.InfoContext(ctx, "Nginx worker process - file", "pid", nginxProcess) + nginxProcess.Master = false + nginxProcess.Cmd = fmt.Sprintf(workerString, sbin) + } + + return nginxProcess, ctx.Err() } return nil, errNotAnNginxProcess } +func nginxArgs(ctx context.Context) map[string]interface{} { + configArgs := make(map[string]interface{}) + executer := &exec.Exec{} + outputBuffer, err := executer.RunCmd(ctx, "nginx", "-V") + if err != nil { + return nil + } + + scanner := bufio.NewScanner(outputBuffer) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + switch { + case strings.HasPrefix(line, "configure arguments"): + configArgs = parseConfigureArguments(line) + } + } + + return configArgs +} + +func nginxPID(ctx context.Context, pidPath string) int32 { + if _, err := os.Stat(pidPath); os.IsNotExist(err) { + return 0 + } + pidBytes, err := os.ReadFile(pidPath) + if err != nil { + return 0 + } + + pidString := string(pidBytes) + pidString = strings.TrimSpace(pidString) + pid, err := strconv.ParseInt(pidString, 10, 32) + + if err != nil { + return 0 + } + + return int32(pid) +} + +func parseConfigureArguments(line string) map[string]interface{} { + // need to check for empty strings + flags := strings.Split(line[len("configure arguments:"):], " --") + result := make(map[string]interface{}) + + for _, flag := range flags { + vals := strings.Split(flag, "=") + if isFlag(vals) { + result[vals[0]] = true + } else if isKeyValueFlag(vals) { + result[vals[0]] = vals[1] + } + } + + return result +} + +func isFlag(vals []string) bool { + return len(vals) == flagLen && vals[0] != "" +} + +func isKeyValueFlag(vals []string) bool { + return len(vals) == keyValueLen +} + // List returns a slice of all NGINX processes. Returns a zero-length slice if no NGINX processes are found. func List(ctx context.Context, opts ...Option) (ret []*Process, err error) { processes, err := process.ProcessesWithContext(ctx) diff --git a/test/docker/nginx-plus-and-nap/deb/Dockerfile b/test/docker/nginx-plus-and-nap/deb/Dockerfile index 163a6fbce..71c271f83 100644 --- a/test/docker/nginx-plus-and-nap/deb/Dockerfile +++ b/test/docker/nginx-plus-and-nap/deb/Dockerfile @@ -22,7 +22,7 @@ RUN --mount=type=secret,id=nginx-crt,dst=nginx-repo.crt \ && apt-get update --allow-releaseinfo-change \ && apt-get install --no-install-recommends --no-install-suggests -y \ ca-certificates \ - gnupg1 \ + gpg \ lsb-release \ apt-transport-https \ git \ @@ -32,18 +32,6 @@ RUN --mount=type=secret,id=nginx-crt,dst=nginx-repo.crt \ ubuntu-keyring \ && wget -qO - https://cs.nginx.com/static/keys/app-protect-security-updates.key | gpg --dearmor | tee /usr/share/keyrings/app-protect-security-updates.gpg >/dev/null \ && wget -qO - https://cs.nginx.com/static/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null \ - && \ - NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \ - found=''; \ - for server in \ - hkp://keyserver.ubuntu.com:80 \ - pgp.mit.edu \ - ; do \ - echo "Fetching GPG key $NGINX_GPGKEY from $server"; \ - apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \ - done; \ - test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \ - apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \ # Install the latest release of NGINX Plus and/or NGINX Plus modules # Uncomment individual modules if necessary # Use versioned packages over defaults to specify a release @@ -54,7 +42,8 @@ RUN --mount=type=secret,id=nginx-crt,dst=nginx-repo.crt \ && echo "Acquire::https::pkgs.nginx.com::Verify-Host \"true\";" >> /etc/apt/apt.conf.d/90nginx \ && echo "Acquire::https::pkgs.nginx.com::SslCert \"/etc/ssl/nginx/nginx-repo.crt\";" >> /etc/apt/apt.conf.d/90nginx \ && echo "Acquire::https::pkgs.nginx.com::SslKey \"/etc/ssl/nginx/nginx-repo.key\";" >> /etc/apt/apt.conf.d/90nginx \ - && printf "deb https://pkgs.nginx.com/plus/${PLUS_VERSION}/ubuntu/ `lsb_release -cs` nginx-plus\n" > /etc/apt/sources.list.d/nginx-plus.list \ + && printf "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://pkgs.nginx.com/plus/${PLUS_VERSION}/ubuntu/ `lsb_release -cs` nginx-plus\n" > /etc/apt/sources.list.d/nginx-plus.list \ + && wget -qO - https://cs.nginx.com/static/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null \ && printf "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://pkgs.nginx.com/app-protect/${PLUS_VERSION}/ubuntu `lsb_release -cs` nginx-plus\n" | tee /etc/apt/sources.list.d/nginx-app-protect.list \ && printf "deb [signed-by=/usr/share/keyrings/app-protect-security-updates.gpg] https://pkgs.nginx.com/app-protect-security-updates/ubuntu `lsb_release -cs` nginx-plus\n" | tee -a /etc/apt/sources.list.d/nginx-app-protect.list \ && mkdir -p /etc/ssl/nginx \ @@ -66,6 +55,7 @@ RUN --mount=type=secret,id=nginx-crt,dst=nginx-repo.crt \ curl \ gettext-base \ jq \ + gnupg2 \ && apt-get remove --purge -y lsb-release \ && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx-plus.list /etc/apt/sources.list.d/nginx-app-protect.list \ && rm -rf /etc/apt/apt.conf.d/90nginx /etc/ssl/nginx