From e8372279269f06c2f8088980d0acb9562cd91ee1 Mon Sep 17 00:00:00 2001 From: Claudio Costa Date: Wed, 18 Jan 2023 09:30:31 -0600 Subject: [PATCH] [MM-49667] Enable sandboxing (#14) * Enable sandboxing * Refactor Dockerfile * Automated pinned packages generation (#15) --- Makefile | 4 ++ build/Dockerfile | 42 +++++++----- build/entrypoint.sh | 32 ++++++--- build/generator.go | 137 +++++++++++++++++++++++++++++++++++++++ build/pkgs_list | 7 ++ cmd/recorder/main.go | 6 ++ cmd/recorder/recorder.go | 2 - 7 files changed, 203 insertions(+), 27 deletions(-) create mode 100644 build/generator.go create mode 100644 build/pkgs_list diff --git a/Makefile b/Makefile index c58d589..29d46ed 100644 --- a/Makefile +++ b/Makefile @@ -352,6 +352,10 @@ else endif @$(OK) Generating github-release http://github.com/$(GITHUB_ORG)/$(GITHUB_REPO)/releases/tag/$(APP_VERSION) ... +.PHONY: go-pinned-packages +go-pinned-packages: ## to update pinned packages list for the Ubuntu based Docker image + go run ./build/generator.go + .PHONY: clean clean: ## to clean-up @$(INFO) cleaning /${GO_OUT_BIN_DIR} folder... diff --git a/build/Dockerfile b/build/Dockerfile index 3f5d318..abd0bcf 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -2,19 +2,8 @@ # A multi stage build, with golang used as a builder # and ubuntu:22.04 as runner ARG GO_IMAGE=golang:1.18@sha256:fa71e1447cb0241324162a6c51297206928d755b16142eceec7b809af55061e5 -# hadolint ignore=DL3006 -FROM ${GO_IMAGE} as builder -#GO_BUILD_PLATFORMS holds the platforms that we will build the docker image against -ARG GO_BUILD_PLATFORMS=linux-amd64 - -# Setup directories structure and compile -COPY . /src -WORKDIR /src -RUN make go-build - -FROM ubuntu:22.04@sha256:817cfe4672284dcbfee885b1a66094fd907630d610cab329114d036716be49ba as runner -COPY --from=builder /src/dist/calls-recorder-linux-amd64 /opt/calls-recorder/bin/calls-recorder +FROM ubuntu:22.04@sha256:817cfe4672284dcbfee885b1a66094fd907630d610cab329114d036716be49ba as base # Setup system dependencies WORKDIR /workdir @@ -23,21 +12,23 @@ WORKDIR /workdir # https://stackoverflow.com/questions/71941032/why-i-cannot-run-apt-update-inside-a-fresh-ubuntu22-04 RUN sed -i -e 's/^APT/# APT/' -e 's/^DPkg/# DPkg/' /etc/apt/apt.conf.d/docker-clean +ARG CHROMIUM_VERSION=109.0.5414.74-0 ARG DEBIAN_FRONTEND=noninteractive +COPY ./build/pkgs_list . +# hadolint ignore=DL3008,SC2046 RUN set -ex && \ apt-get update && \ - apt-get install --no-install-recommends -y ffmpeg=7:4.4.2-0ubuntu0.22.04.1 pulseaudio=1:15.99.1+dfsg1-1ubuntu2 \ - xvfb=2:21.1.3* wget=1.21.2-2ubuntu1 unzip=6.0-26ubuntu3.1 fonts-emojione=2.2.6+16.10.20160804-0ubuntu2 ca-certificates=20211016 && \ + apt-get install --no-install-recommends -y $(cat pkgs_list) && \ apt-get update && \ wget --progress=dot:giga -N \ -O chromium-chromedriver.deb \ - https://launchpad.net/~savoury1/+archive/ubuntu/chromium/+files/chromium-chromedriver_108.0.5359.124-0ubuntu0.22.04.1sav0_amd64.deb && \ + https://launchpad.net/~savoury1/+archive/ubuntu/chromium/+files/chromium-chromedriver_${CHROMIUM_VERSION}ubuntu0.22.04.1sav1_amd64.deb && \ wget --progress=dot:giga -N \ -O chromium-codecs-ffmpeg-extra.deb \ - https://launchpad.net/~savoury1/+archive/ubuntu/chromium/+files/chromium-codecs-ffmpeg-extra_108.0.5359.124-0ubuntu0.22.04.1sav0_amd64.deb && \ + https://launchpad.net/~savoury1/+archive/ubuntu/chromium/+files/chromium-codecs-ffmpeg-extra_${CHROMIUM_VERSION}ubuntu0.22.04.1sav1_amd64.deb && \ wget --progress=dot:giga -N \ -O chromium-browser.deb \ - https://launchpad.net/~savoury1/+archive/ubuntu/chromium/+files/chromium-browser_108.0.5359.124-0ubuntu0.22.04.1sav0_amd64.deb && \ + https://launchpad.net/~savoury1/+archive/ubuntu/chromium/+files/chromium-browser_${CHROMIUM_VERSION}ubuntu0.22.04.1sav1_amd64.deb && \ apt-get install --no-install-recommends -y ./chromium-chromedriver.deb \ ./chromium-codecs-ffmpeg-extra.deb \ ./chromium-browser.deb && \ @@ -46,6 +37,23 @@ RUN set -ex && \ adduser root pulse-access && \ mkdir -pv ~/.cache/xdgr +# Create unprivileged user to run the recorder process +RUN groupadd -r calls && useradd -mr -g calls -G audio,video,pulse-access calls + +# hadolint ignore=DL3006 +FROM ${GO_IMAGE} as builder + +#GO_BUILD_PLATFORMS holds the platforms that we will build the docker image against +ARG GO_BUILD_PLATFORMS=linux-amd64 + +# Setup directories structure and compile +COPY . /src +WORKDIR /src +RUN make go-build + +FROM base AS runner +COPY --from=builder /src/dist/calls-recorder-linux-amd64 /opt/calls-recorder/bin/calls-recorder + # copy binary COPY ./build/entrypoint.sh . diff --git a/build/entrypoint.sh b/build/entrypoint.sh index 84d2d26..0595451 100755 --- a/build/entrypoint.sh +++ b/build/entrypoint.sh @@ -11,13 +11,15 @@ pulseaudio -D --verbose --exit-idle-time=-1 --system --disallow-exit pactl load-module module-null-sink sink_name="grab" sink_properties=device.description="monitorOUT" # Forward signals to service -pid=0 +RECORDER_PID=0 +RECORDER_PID_FILE=/tmp/recorder.pid -# SIGTERM-handler +# SIGTERM handler term_handler() { - if [ $pid -ne 0 ]; then - kill -SIGTERM "$pid" - wait "$pid" + RECORDER_PID=`cat $RECORDER_PID_FILE` + if [ $RECORDER_PID -ne 0 ]; then + kill -SIGTERM "$RECORDER_PID" + tail --pid="$RECORDER_PID" -f /dev/null fi exit 143; # 128 + 15 -- SIGTERM } @@ -25,9 +27,23 @@ term_handler() { # On callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler trap 'kill ${!}; term_handler' SIGTERM -# Run service -XDG_RUNTIME_DIR=$PATH:~/.cache/xdgr /opt/calls-recorder/bin/calls-recorder & -pid="$!" +# DEV_MODE is optional so we need to check whether it's set before relaying it to the recorder app. +DEV="${DEV_MODE:-false}" + +RECORDER_USER=calls + +# Give permission to write recording files. +chown -R $RECORDER_USER:$RECORDER_USER /recs + +# Run service as unprivileged user. +runuser -l $RECORDER_USER -c \ + "SITE_URL=$SITE_URL \ + AUTH_TOKEN=$AUTH_TOKEN \ + CALL_ID=$CALL_ID \ + THREAD_ID=$THREAD_ID \ + DEV_MODE=$DEV \ + XDG_RUNTIME_DIR=/home/$RECORDER_USER/.cache/xdgr \ + /opt/calls-recorder/bin/calls-recorder" & # Wait forever wait ${!} diff --git a/build/generator.go b/build/generator.go new file mode 100644 index 0000000..179ad23 --- /dev/null +++ b/build/generator.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "strings" + "time" +) + +const ( + launchpadAPIBaseURL = "https://api.launchpad.net/1.0" + distroName = "ubuntu" + distroVersion = "jammy" + distroArch = "amd64" + + pkgsListPath = "./build/pkgs_list" + + requestTimeout = 5 * time.Second +) + +type Package struct { + Name string + Version string +} + +func GetPublishedPackages(c *http.Client, names []string) ([]Package, error) { + var pkgs []Package + + for _, pkgName := range names { + ctx, cancelFn := context.WithTimeout(context.Background(), requestTimeout) + defer cancelFn() + + url := fmt.Sprintf("%s/%s/+archive/primary", launchpadAPIBaseURL, distroName) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("request creation: %w", err) + } + + q := req.URL.Query() + q.Add("ws.op", "getPublishedBinaries") + q.Add("binary_name", pkgName) + q.Add("exact_match", "true") + q.Add("distro_arch_series", fmt.Sprintf("%s/%s/%s/%s", launchpadAPIBaseURL, distroName, distroVersion, distroArch)) + q.Add("status", "Published") + q.Add("order_by_date", "true") + req.URL.RawQuery = q.Encode() + + resp, err := c.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + + respData := map[string]any{} + if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + entries, ok := respData["entries"].([]any) + if !ok { + return nil, fmt.Errorf("failed to parse response") + } + + if len(entries) == 0 { + break + } + + entry, ok := entries[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("failed to parse entry") + } + + name, ok := entry["binary_package_name"].(string) + if !ok { + return nil, fmt.Errorf("failed to parse package name") + } + + version, ok := entry["binary_package_version"].(string) + if !ok { + return nil, fmt.Errorf("failed to parse package version") + } + + log.Printf("found %s=%s\n", name, version) + + pkgs = append(pkgs, Package{Name: name, Version: version}) + } + + return pkgs, nil +} + +func GenPinnedPackages(pkgsNames []string) error { + c := &http.Client{} + + pkgs, err := GetPublishedPackages(c, pkgsNames) + if err != nil { + return fmt.Errorf("failed to get packages: %w", err) + } + + outFile, err := os.OpenFile(pkgsListPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return fmt.Errorf("failed to open output file: %w", err) + } + defer outFile.Close() + + for _, pkg := range pkgs { + fmt.Fprintf(outFile, "%s=%s\n", pkg.Name, pkg.Version) + } + + return nil +} + +func parsePkgsList(data string) []string { + var pkgs []string + list := strings.Split(data, "\n") + for _, el := range list { + name, _, _ := strings.Cut(el, "=") + pkgs = append(pkgs, name) + } + return pkgs +} + +func main() { + data, err := os.ReadFile(pkgsListPath) + if err != nil { + log.Fatalf("failed to read packages file: %s", err) + } + + if err := GenPinnedPackages(parsePkgsList(string(data))); err != nil { + log.Fatalf("failed to generate pinned packages: %s", err) + } + + log.Printf("done") +} diff --git a/build/pkgs_list b/build/pkgs_list new file mode 100644 index 0000000..982ca8a --- /dev/null +++ b/build/pkgs_list @@ -0,0 +1,7 @@ +ca-certificates=20211016ubuntu0.22.04.1 +ffmpeg=7:4.4.2-0ubuntu0.22.04.1 +fonts-emojione=2.2.6+16.10.20160804-0ubuntu2 +pulseaudio=1:15.99.1+dfsg1-1ubuntu2 +unzip=6.0-26ubuntu3.1 +wget=1.21.2-2ubuntu1 +xvfb=2:21.1.3-2ubuntu2.5 diff --git a/cmd/recorder/main.go b/cmd/recorder/main.go index b7e8a0e..da42c1f 100644 --- a/cmd/recorder/main.go +++ b/cmd/recorder/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "os" "os/signal" @@ -10,6 +11,11 @@ import ( func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) + pid := os.Getpid() + if err := os.WriteFile("/tmp/recorder.pid", []byte(fmt.Sprintf("%d", pid)), 0666); err != nil { + log.Fatalf("failed to write pid file: %s", err) + } + cfg, err := loadConfig() if err != nil { log.Fatalf("failed to load config: %s", err) diff --git a/cmd/recorder/recorder.go b/cmd/recorder/recorder.go index d91e877..c9cc09f 100644 --- a/cmd/recorder/recorder.go +++ b/cmd/recorder/recorder.go @@ -45,11 +45,9 @@ func (rec *Recorder) runBrowser(recURL string) error { chromedp.NoFirstRun, chromedp.NoDefaultBrowserCheck, chromedp.DisableGPU, - chromedp.NoSandbox, // puppeteer default behavior chromedp.Flag("disable-infobars", true), - chromedp.Flag("enable-automation", true), chromedp.Flag("disable-background-networking", true), chromedp.Flag("enable-features", "NetworkService,NetworkServiceInProcess"), chromedp.Flag("disable-background-timer-throttling", true),