Skip to content

Минимальный базовый образ Golang на Astra Linux

License

Notifications You must be signed in to change notification settings

NGRsoftlab/Astra-golang-image

Repository files navigation

NGRSOFTLAB logo

Golang

Dive efficiency Made with love Powered by Docker NGR Team

Golang image

Ascii svg art by aasvg.

Description

Golang container image - это реализация легковесной сборки ЯП Golang на базе Astra Linux

Присоединяйтесь к нашим социальным сетям:

NGR Social Telegram       NGR Social Media

Contents

  • Docker >= 28.1.1 (возможно работает в предыдущих версиях, но мы не можем это гарантировать)
OS Golang Status
Astra 1.7 Golang 1.19 ✅ Fully supported
Astra 1.8 Golang 1.21
Golang 1.23
✅ Fully supported
Таблица 1. Поддерживаемые ОС-ы.

 

Go(a.k.a Golang) - язык программирования, впервые разработанный в Google. Это статически типизированный язык с синтаксисом, в некоторой степени производным от C, но с дополнительными функциями, такими как сборщик мусора, безопасность типов, некоторые возможности динамической типизации, дополнительные встроенные типы (например, массивы переменной длины и карты ключ-значение) и большая стандартная библиотека. Образ построен на основе отечественной ОС Astra Linux

Для начала работы необходимо установить pre-commit и хуки

$ pip install pre-commit
$ pre-commit --version

pre-commit 4.2.0

$ pre-commit install

pre-commit installed at .git/hooks/pre-commit
pre-commit installed at .git/hooks/commit-msg
pre-commit installed at .git/hooks/pre-push

Warning

Чтобы проверить свои изменения, воспользуйтесь командой pre-commit run --all-files. Чтобы проверить конкретную задачу, воспользуетесь командой pre-commit run <target> --all-files. Если Вы понимаете что творите и хотите пропустить проверку pre-commit-ом воспользуйтесь --no-verify, пример git commit -m "Добавил изменения и не хочу проверки" --no-verify

Существует несколько способов как можно взаимодействовать со сборкой образа. Благодаря скрипту1 может существовать 3 способа передачи аргумента в Dockerfile:

  1. Передача 'примерной' версии. В результате передачи данной строки, скрипт попытается найти точную версии, если таковой нет, то будет возвращена пустая строка

    ## Export Golang version for 1.7.5
    $ export GOLANG_VERSION='1.19-astra1.7.5-slim'
    
    ## Golang image: 512MB
    docker build \
      --progress=plain \
      --build-arg go_identity=1.19 \
      --build-arg image_version=1.7.5-slim \
      -t golang:"${GOLANG_VERSION}" \
      .
    
    .. build ...
  2. Передача точной версии

    ## Export Golang version for 1.8.2
    $ export GOLANG_VERSION='1.21-astra1.8.2-slim'
    
    ## Golang build utils image: 632MB
    docker build \
      --progress=plain \
      --build-arg go_identity='2:1.21~2.astra1+b1' \
      --build-arg image_version=1.8.2-slim \
      -t golang:"${GOLANG_VERSION}" \
      .
    
    .. build ...
  3. Передача ссылки, на заранее собранный из исходников Golang

    ## Export Golang version for 1.8.2
    $ export GOLANG_VERSION='1.23-astra1.8.2-slim'
    
    ## Golang build utils image: 645MB
    docker build \
      --progress=plain \
      --build-arg go_identity=https://example-registry.com/repository/golang/go-v1.23.5-linux-amd64.tar.gz \
      --build-arg image_version=1.8.2-slim \
      -t golang:"${GOLANG_VERSION}" \
      .
    
    .. build ...

Tip

Примеры просмотра версии пакета - apt show golang, apt-cache policy golang, apt-cache show golang

Имя Значение по умолчанию Тип Описание
image_name astra string Имя образа.
image_registry '' string Адрес до реестра образа2.
image_version 1.8.2-slim string Версия образа.
go_registry_proxy '' string Переменная, для установки своего проксирующего репозитория.
go_identity 1.21 string Ожидаемая версия Golang1.
install_additional_tools '' string Дополнительные компоненты ОС, которые необходимо установить. Разделителем между компонентами должен быть строчный пробел: ldap-utils slapd.
Таблица 2. Переопределяемые аргументы для сборки образа.

 

В результате сборки базового образа идёт наполнение файла /etc/environment. В нём отражены общие переменные, которые могут использоваться в сборочных образах приложений

  1. Пример переменных для образа 1.23 установленного из удаленного и скомпилированного Golang

    $ cat /etc/environment
    
    GO_REVISION=Installed-from-URL
    BEGIN_BUILD_IN_EPOCH=1746645898
    GO_MAJOR_MINOR_PATCH_VERSION=1.23.4
    GO_MAJOR_MINOR_VERSION=1.23
  2. Пример переменных для образа 1.21 из репозиториев Astra Linux

    $ cat /etc/environment
    
    GO_REVISION=2:1.21~2.astra1+b1
    BEGIN_BUILD_IN_EPOCH=1746643737
    GO_MAJOR_MINOR_PATCH_VERSION=1.21.10
    GO_MAJOR_MINOR_VERSION=1.21
  3. Пример переменных для образа 1.19 из репозиториев Astra Linux

    $ cat /etc/environment
    
    GO_REVISION=2:1.19~1
    BEGIN_BUILD_IN_EPOCH=1746643927
    GO_MAJOR_MINOR_PATCH_VERSION=1.19.12
    GO_MAJOR_MINOR_VERSION=1.19

Для того чтобы начать использовать данный образ, создайте Dockerfile с простыми настройками

FROM golang:1.21-astra1.8.2-slim

WORKDIR /usr/src/app

# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN go build -v -o /usr/local/bin/app ./...

CMD ["app"]

Затем соберите и запустите полученный образ

$ docker build -t my-golang-app .
$ docker run -it --rm --name my-running-app my-golang-app

...run logic...

Для того, чтобы запустить компиляцию внутри докер контейнера

$ docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp golang:1.21-astra1.8.2-slim go build -v

...run logic...

Кросс-компиляция приложения внутри контейнера

$ docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp -e GOOS=windows -e GOARCH=386 golang:1.21-astra1.8.2-slim go build -v

...run logic...

Альтернативный подход для сборки всех платформ за один раз

$ docker run --rm -it -v "$PWD":/usr/src/myapp -w /usr/src/myapp golang:1.21-astra1.8.2-slim bash
$ for GOOS in darwin linux; do
>   for GOARCH in 386 amd64; do
>     export GOOS GOARCH
>     go build -v -o myapp-$GOOS-$GOARCH
>   done
> done

...run logic...

Простой тест:

docker run --rm golang:1.21-astra1.8.2 bash -c "go install github.com/cosmos72/gomacro@latest && gomacro -e 'import \"fmt\"' -e 'fmt.Println(\"Hello from Go\")'"

Данный раздел будет обеспечивать краткие вводные для того, чтобы Вы в дальнейшем могли проектировать свои Scratch сборки, на примере небольшой утилиты. Все, что демонстрируется, также подкреплено и всеми задействованными сборочными скриптами или специализированными для сборки. Все манипуляции поделены на определенное количество 'шагов' для которых будут даны краткие комментарии:

  1. Первый этап - установка базовых компонентов для сборки

    RUN \
        --mount=type=bind,source=./scripts,target=/usr/local/sbin,readonly \
        upx-install.sh \
        && apt-install.sh \
            tzdata \
            zip \
            ca-certificates \
            build-essential \
        && apt-remove.sh
  2. Второй этап - упаковка минимального набора компонентов для переноса в Scratch

    RUN \
        mkdir -p \
            /base/etc/ssl/certs \
            /base/sbin \
            /base/usr/src/app \
        && echo 'root:x:0:' > /base/etc/group \
        && echo "${user}:x:${gid}:" >> /base/etc/group \
        && echo 'root:x:0:0:root:/root:/sbin/nologin' > /base/etc/passwd \
        && echo "${user}:x:${uid}:${gid}:${user}:/nonexistent:/sbin/nologin" >> /base/etc/passwd \
        && echo 'int main() { return 1; }' > nologin.c \
        && gcc -Os -no-pie -static -std=gnu99 -s -Wall -Werror -o /base/sbin/nologin nologin.c \
        && cp /etc/ssl/certs/ca-certificates.crt /base/etc/ssl/certs/ca-certificates.crt
    
    WORKDIR /usr/share/zoneinfo
    
    RUN zip -q -r -0 /base/zoneinfo.zip .
  3. Третий этап - сборка и оптимизация приложения при помощи UPX

    WORKDIR /opt
    
    COPY scratch/go.* .
    RUN go mod download
    
    COPY scratch/main.go .
    
    RUN \
        go build \
            -v \
            -ldflags "-extldflags '-static' -w -s -X 'main.AppVersion=${version}'" \
            -installsuffix=static \
            -o curl-uri main.go
    
    RUN \
        echo "Original binary size: $(du -hs curl-uri)" \
        && upx --best --lzma -o curl_uri_compressed curl-uri \
        && echo "Compressed binary size: $(du -hs curl_uri_compressed)"
    
    RUN \
        cp curl_uri_compressed /base/usr/src/app/curl-uri \
        && chmod 755 /base/usr/src/app/curl-uri
  4. Заключительный этап - миграция в скретч образ и запуск приложения

    COPY --from=base-stage /base/ /
    
    USER anonymous:anonymous
    
    ENV \
        ZONEINFO=/zoneinfo.zip \
        PATH="/usr/src/app" \
        SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
        REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
    
    WORKDIR /usr/src/app
    
    EXPOSE 8080
    
    ENTRYPOINT [ "curl-uri" ]

Сборка: docker build --progress=plain -f Dockerfile-scratch -t curl-uri:1.0.0 .

Запуск: docker run --rm --network=host --dns 8.8.8.8 -e IPINFO_TOKEN=4426e4d4334a8d curl-uri:1.0.0

Warning

Если IPINFO_TOKEN не определяется, то зарегистрируйте новый на ipinfo.io

Примеры использования:

  1. Route / - вывод версии приложения

    $ curl http://localhost:8080
    
    Golang UPX Demo v1.0.0
  2. Route /location - запрос информации о своём местонахождении

    $ curl http://localhost:8080/location
    
     ____                   __                             Welcome to the bloated Golang server!
    /\  _'\                /\ \
    \ \ \L\_\   ___   _____\ \ \___      __  _ __  ____    [+] Server IP: 8.8.8.8
     \ \ \L_L  / __'\/\ '__'\ \  _ '\  /'__'/\''__/',__\   [+] Country:   ZN
      \ \ \/, /\ \L\ \ \ \L\ \ \ \ \ \/\  __\ \ \/\__, '\  [+] Region:    Amsterdam
       \ \____\ \____/\ \ ,__/\ \_\ \_\ \____\ \_\/\____/  [+] City:      Amsterdam
        \/___/ \/___/  \ \ \/  \/_/\/_/\/____/\/_/\/___/   [+] ISP:       Google cloud
                        \ \_\                              [+] Lat/Lon:   11.1111, 11.1111
                         \/_/                              [+] Timezone:  Europe/Amsterdam
    
                                                           Server started at: Wed May  7 23:27:27 MSK 2025
                                                           Current time:      Wed May  7 23:27:33 MSK 2025
  3. Route /stream - последовательно выводит framerate из Ascii артов

    $ curl http://localhost:8080/stream
    
     ____                   __
    /\  _'\                /\ \
    \ \ \L\_\   ___   _____\ \ \___      __  _ __  ____
     \ \ \L_L  / __'\/\ '__'\ \  _ '\  /'__'/\''__/',__\
      \ \ \/, /\ \L\ \ \ \L\ \ \ \ \ \/\  __\ \ \/\__, '\
       \ \____\ \____/\ \ ,__/\ \_\ \_\ \____\ \_\/\____/
        \/___/ \/___/  \ \ \/  \/_/\/_/\/____/\/_/\/___/
                        \ \_\
                         \/_/
    
    Uptime: 1s
  4. Route /live - выводит text/plain(по умолчанию) проверку работоспособности сервера и функционала. Также поддерживает работу с query

    $ curl http://localhost:8080/live
    OK
    
    $ curl http://localhost:8080/live
    failed to fetch public IP
    
    $ curl http://localhost:8080/live?format=json
    {"error":"failed to fetch public IP","status":"error"}
  5. Route /healthz - выводит text/plain(по умолчанию) проверку сервера на его способность принимать запросы. Также поддерживает работу с query

    $ curl http://localhost:8080/healthz
    OK
    
    $ curl http://localhost:8080/healthz?format=json
    {"status":"ok"}
  6. Route /metrics - выводит text/plain Prometheus метрики

    $ curl http://localhost:8080/metrics
    
    ...
    # HELP upx_server_requests_total Total number of HTTP requests.
    # TYPE upx_server_requests_total counter
    upx_server_requests_total{handler="location",method="GET"} 1
    # HELP upx_server_up Whether the server is up and responding (1 = yes, 0 = no)
    # TYPE upx_server_up gauge
    upx_server_up 1

Tip

Полный Dockerfile

Для обеспечения РБПО будут разобраны некоторые нюансы

При помощи утилиты checksec можно проверить свойства исполняемых файлов

Пример установки:

dpkg_arch="$(dpkg --print-architecture)"
checksec_base="https://github.com/slimm609/checksec"
checksec_version=$(curl -Ls -o /dev/null -w '%{url_effective}' "${checksec_base}/releases/latest")
checksec_version="${checksec_version##*/}"
checksec_version="${checksec_version#v*}"
checksec_url="${checksec_base}/releases/download/${checksec_version}/checksec_${checksec_version}_${dpkg_arch}.deb"

## Download
curl -sLO "${checksec_url}"

## Install
sudo apt install "./${checksec_url##*/}"

## Remove installer
rm -f "./${checksec_url##*/}"

## Check version
checksec -v

Попробуем собрать экспериментальное приложение, чтобы увидеть эффект сборки с CGO

docker build \
    --no-cache \
    --progress=plain \
    --target=base-stage \
    -t curl-uri:checksec-stage \
    -f Dockerfile-debug \
    .

docker run -it --rm -w /test curl-uri:checksec-stage test-checksec-app

Результаты, после запуска тестов при помощи скрипта:

  1. Результаты для приложения собранного без безопасных опций

    ========================
    TEST FOR '/usr/bin/app_unprotected' ONLY
    ========================
    ====>Checking for RELRO support
    /usr/bin/app_unprotected: Partial RELRO
    
    ====>Checking for NX support
    /usr/bin/app_unprotected: NX enabled
    
    ====>Checking for PIE support
    /usr/bin/app_unprotected: No PIE
    
    ====>Checking for rpath
    /usr/bin/app_unprotected: No RPATH
    
    ====>Checking for run path
    /usr/bin/app_unprotected: No RUNPATH
    
    ====>Checking for stack canaries
    /usr/bin/app_unprotected: 0 (NO CANARIES)
    
    ====>Checking for FORTIFY
    
    ====>Check Debug symbols
    /usr/bin/app_unprotected: Not found debug symbols
    
    ====>Checksec: /usr/bin/app_unprotected
    {
      "canary": "No Canary Found",
      "cfi": "NO SHSTK & NO IBT",
      "fortified": "0",
      "fortify_source": "No",
      "fortifyable": "5",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "Partial RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>/usr/bin/app_unprotected size: 1.5M  /usr/bin/app_unprotected
    
    ====>/usr/bin/app_unprotected interpreter check
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
    
    ====>/usr/bin/app_unprotected lib check
      linux-vdso.so.1 (0x00007ffca19f4000)
      libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007681bc691000)
      /lib64/ld-linux-x86-64.so.2 (0x00007681bc877000)
    
    ====>/usr/bin/app_unprotected check type
    /usr/bin/app_unprotected: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=fc5db8b318dec0c33195892561ab31ad2119e87b, for GNU/Linux 4.11.0, stripped
    
  2. Результаты для приложения собранного с безопасными опциями

    ========================
    TEST FOR '/usr/bin/app_protected' ONLY
    ========================
    ====>Checking for RELRO support
    /usr/bin/app_protected: Full RELRO
    
    ====>Checking for NX support
    /usr/bin/app_protected: NX enabled
    
    ====>Checking for PIE support
    /usr/bin/app_protected: PIE enabled
    
    ====>Checking for rpath
    /usr/bin/app_protected: No RPATH
    
    ====>Checking for run path
    /usr/bin/app_protected: No RUNPATH
    
    ====>Checking for stack canaries
    /usr/bin/app_protected: 0 (NO CANARIES)
    
    ====>Checking for FORTIFY
    
    ====>Check Debug symbols
    /usr/bin/app_protected: Not found debug symbols
    
    ====>Checksec: /usr/bin/app_protected
    {
      "canary": "Canary Found",
      "cfi": "NO SHSTK & NO IBT",
      "fortified": "4",
      "fortify_source": "Yes",
      "fortifyable": "5",
      "nx": "NX enabled",
      "pie": "PIE Enabled",
      "relro": "Full RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>/usr/bin/app_protected size: 1.7M  /usr/bin/app_protected
    
    ====>/usr/bin/app_protected interpreter check
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
    
    ====>/usr/bin/app_protected lib check
      linux-vdso.so.1 (0x00007ffe04fe3000)
      libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000079c9f3f7f000)
      /lib64/ld-linux-x86-64.so.2 (0x000079c9f4339000)
    
    ====>/usr/bin/app_protected check type
    /usr/bin/app_protected: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5dfdc0fe3896e7566ce3b6f96d83f5ebbf68a8c0, for GNU/Linux 4.11.0, stripped
    

    По результатам обзорной демонстрации и сравнения тестов(пункт 1 и пункт 2), между приложением собранным с безопасными опциями и без безопасных опций, приходим к выводу:

    • Сборка с -buildmode=pie обеспечивает безопасное покрытие (позиционно-независимый исполняемый файл - требует динамической перелокации), но "приковывает" нас к общим объектам
    • CGO_ENABLED=1 обеспечивает покрытие функции перемещения данных только для чтения(RELRO - требует ld.so для BIND_NOW)
    • Защитные опции и теги дают прибавку к весу приложения, но значительно улучшает защищенность приложения, хотя оно и становиться динамически линкованным
  3. Результат компрессии при помощи UPX и strip

    ========================
    CHECK INFO FOR COMPRESSED BINARY
    ========================
    ====>Compressed via UPX binary size: 536K  /usr/bin/app_unprotected_compressed
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via UPX binary size with protection options: 552K  /usr/bin/app_protected_compressed
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Enabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via strip binary size: 1.5M  /usr/bin/app_unprotected_strip
    {
      "canary": "No Canary Found",
      "cfi": "NO SHSTK & NO IBT",
      "fortified": "0",
      "fortify_source": "No",
      "fortifyable": "5",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "Partial RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via strip binary size with protection options: 1.7M  /usr/bin/app_protected_strip
    {
      "canary": "Canary Found",
      "cfi": "NO SHSTK & NO IBT",
      "fortified": "4",
      "fortify_source": "Yes",
      "fortifyable": "5",
      "nx": "NX enabled",
      "pie": "PIE Enabled",
      "relro": "Full RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    

    В рамках данного контекста использование UPX может предоставлять опасность, т.к. UPX модифицирует структуру ELF-файла:

    • Сжимает .text, .data и другие секции
    • Добавляет свой загрузчик (unpack stub) в начало файла
    • Изменяет права доступа к сегментам памяти (например, делает .text записываемым во время распаковки)
    • Нарушает оригинальную организацию секций и таблиц динамической линковки
  4. Соотношение размеров обычного собранного приложения и последующей "оптимизации" и компрессии, на примере приложения собранного с защитными опциями

    ====>/usr/bin/app_protected size: 1.7M  /usr/bin/app_protected
    
    ====>Compressed via UPX binary size with protection options: 552K  /usr/bin/app_protected_compressed
    
    ====>Compressed via strip binary size with protection options: 1.7M  /usr/bin/app_protected_strip
    

    Приходим к выводу, что флаги -s -w в -ldflags при компиляции делают то же самое, что и strip:

    • -s - удаляет символьную таблицу(.symtab)
    • -w - удаляет отладочную информацию(DWARF, .debug_* секции)
  5. Результаты тестов с демонстрацией переполнение стека

    ====>STACK OVERFLOW
    
    Testing UNPROTECTED binary
    Expected: SIGSEGV or silent corruption
    Copied: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    SIGSEGV: segmentation violation
    PC=0x49d144 m=0 sigcode=128 addr=0x0
    signal arrived during cgo execution
    
    goroutine 1 gp=0xc000002380 m=0 mp=0x577e40 [syscall]:
    ...
    /usr/bin/app_unprotected: exit code(2)
    
    Testing PROTECTED binary
    Expected: Aborted with 'stack smashing detected'
    *** buffer overflow detected ***: terminated
    SIGABRT: abort
    PC=0x7b0390cc88fc m=0 sigcode=18446744073709551610
    signal arrived during cgo execution
    
    goroutine 1 gp=0xc000002380 m=0 mp=0x5ad6d453cca0 [syscall]:
    ...
    /usr/bin/app_protected: exit code(2)
    

    Наблюдаем:

    • У приложения без защищенных опций неопределённое поведение, т.к. переполнение 64-байтного буфера 100 байтами - повреждение стека
    • У приложения с защищенными опциями ожидается 'stack smashing detected', т.к. стековая канарейка обнаружила перезапись защитного значения
  6. Результаты тестов с демонстрацией переполнение буфера в куче

    ====>HEAP OVERFLOW
    
    Testing UNPROTECTED binary
    Expected: SIGSEGV or silent corruption
    Read 839 bytes
    SIGSEGV: segmentation violation
    PC=0x49d1b2 m=0 sigcode=128 addr=0x0
    signal arrived during cgo execution
    
    goroutine 1 gp=0xc000002380 m=0 mp=0x577e40 [syscall]:
    ...
    /usr/bin/app_unprotected: exit code(2)
    
    ====>Testing PROTECTED binary FORTIFY_SOURCE
    Expected: Aborted with 'buffer overflow detected'
    *** buffer overflow detected ***: terminated
    SIGABRT: abort
    PC=0x73ec99ac18fc m=0 sigcode=18446744073709551610
    signal arrived during cgo execution
    
    goroutine 1 gp=0xc000002380 m=0 mp=0x601f43243ca0 [syscall]:
    ...
    /usr/bin/app_protected: exit code(2)
    

    Наблюдаем:

    • У приложения без защищенных опций повреждение кучи или тихий крах, т.к. чтение 1024 байт в 256-байтный буфер - перезапись смежных данных
    • У приложения с защищенными опциями ожидается 'buffer overflow detected', т.к. FORTIFY_SOURCE заменил fread() на __fread_chk() который проверил размер буфера во время выполнения
  7. Результаты тестов с демонстрацией безопасной операции

    ====>SAFE CONTROL TEST
    
    ====>Testing UNPROTECTED binary with safe option
    Safe copied: Hello
    
    ====>Testing PROTECTED binary with safe option
    Safe copied: Hello
    

    Наблюдаем:

    • У приложения без защищенных опций успешное выполнение
    • У приложения с защищенными опциями успешное выполнение
    • Ну а что вы хотели, контрольный тест же

Создадим образ из Dockerfile для проверки всех вариантов, после компиляции, свойств исполняемых файлов на примере Golang приложения без CGO, но используя опции сборки для CGO:

docker build \
    --no-cache \
    --progress=plain \
    -t curl-uri:debug \
    -f Dockerfile-debug \
    .

## Варианты вызова разных вариаций приложений для демонстрации стабильной работы: <app>
# curl_uri
# curl_uri_secup
# curl_uri_compressed
# curl_uri_compressed_secup
# curl_uri_strip
# curl_uri_strip_secup
docker run --rm --network=host --dns 8.8.8.8 -e IPINFO_TOKEN=4426e4d4334a8d curl-uri:debug <app>
  1. Результаты тестов приложений после сборки для приложения без безопасных опций и с CGO_ENABLED=0:

    ### CGO_ENABLED=0 (UNPROTECTED APP)
    
    ====>Original binary size: 9.0M curl_uri
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via UPX binary size: 2.7M curl_uri_compressed
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via strip binary size: 9.0M curl_uri_strip
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
  2. Результаты тестов приложений после сборки для приложения c безопасными опциями и с CGO_ENABLED=1:

    ### CGO_ENABLED=1 (PROTECTED APP)
    
    ====>Original binary size with protection options: 9.9M curl_uri_secup
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via UPX binary size with protection options: 2.8M curl_uri_compressed_secup
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    
    ====>Compressed via strip binary size with protection options: 9.9M curl_uri_strip_secup
    {
      "canary": "No Canary Found",
      "cfi": "Unknown",
      "fortified": "0",
      "fortify_source": "N/A",
      "fortifyable": "0",
      "nx": "NX enabled",
      "pie": "PIE Disabled",
      "relro": "No RELRO",
      "rpath": "No RPATH",
      "runpath": "No RUNPATH",
      "symbols": "No Symbols"
    }
    

    Снова убеждаемся, что использование strip не целесообразно, т.е. пользы нет по сжатию. При статической линковке отличия от небезопасной сборки с использованием -extldflags не даёт никакой разницы с противоположной сборки

    Переменные не имеют смысла, если нет CGO:

    • CGO_CFLAGS="-O2 -D_FORTIFY_SOURCE=2 -fstack-protector-strong"
    • CGO_LDFLAGS="-Wl,-z,relro,-z,now"

    Пояснение по отдельным тестам:

    • Почему No Canary Found - стековые канарейки не используются из-за архитектуры runtime. Если использовать CGO, то будет эффект
    • Почему No RELRO и PIE Disabled - для сборки в Scratch и при запуске ядро пытается загрузить динамический линковщик(glibc, libpthread и др.), а там нет никаких системных библиотек. Поэтому необходимо собирать приложение с -extldflags '-static'

Вывод по результатам тестов - при портировании приложения в Scratch, кажется, что можно использовать UPX, ибо разницы нет. При желании в защитные опции - использовать облегченные образы, например Distroless

Дополнительный ресурсы:

В некоторых сборках, можно заметить теги

Что делают теги и зачем они используются:

  • netgo - использует чистую Go-реализацию сетевого стека вместо системных вызовов через CGO(getaddrinfo, socket и др.)
  • osusergo - чистая Go реализация работы с пользователями(без CGO, но ограничения - LDAP)
  • production - отключение отладочных фич(//go:build !debug)

Пример дополнительных тегов:

  • timetzdata - встраивает базу часовых поясов в бинарник для статических бинарников без /usr/share/zoneinfo
  • purego - отключает все ассемблерные оптимизации для аудита кода или верификации
  • math_big_pure_go - чистая Go-реализация big.Int для воспроизводимости вычислений
  • static - принудительная статическая линковка(CGO_ENABLED=1 go build -tags static)
  • musl - оптимизации под musl libc для сборки под Alpine
  • debug - включение расширенного логгирования
  • msan - MemorySanitizer
  • asan - AddressSanitizer

Дополнительный ресурсы:

Чтобы переопределить/переназначить проектную область на свои источники в зависимостях, можно воспользоваться следующей инструкцией:

  1. Создать форк необходимого проекта
  2. Изменить go.mod источник на имя нового форка, зафиксировать и отправить изменения
  3. Добавить в go.mod директиву replace. Пример содержимого:
module <yourname>

go 1.18

require (
    github.com/versent/saml2aws/v2 v2.35.0
)

replace github.com/versent/saml2aws/v2 v2.35.0 => github.com/marcottedan/saml2aws/v2 master

Более подробно можно ознакомиться тут

Команда go поддерживает базовую аутентификацию HTTP при обмене данными с прокси-серверами

Учетные данные могут быть указаны в файле .netrc. Например, файл .netrc, содержащий следующие строки, будет настраивать команду go для подключения к машине proxy.corp.example.com с заданными именем пользователя и паролем

machine proxy.corp.example.com
login user
password password

Местоположение файла может быть установлено с помощью переменной среды NETRC. Если NETRC не установлен, команда go будет читать $HOME/.netrc на UNIX-подобных платформах или %USERPROFILE%\_netrc в Windows

Поля в .netrc разделяются пробелами, табуляциями и символами новой строки. К сожалению, эти символы нельзя использовать в именах пользователей или паролях. Также обратите внимание, что имя компьютера не может быть полным URL-адресом, поэтому невозможно указать разные имена пользователей и пароли для разных путей на одном компьютере

В качестве альтернативы учетные данные могут быть указаны непосредственно в URL-адресах GOPROXY. Например:

GOPROXY=https://user:password@proxy.corp.example.com

Альтернативная опция при помощи git-a при CI/CD

# От пользователя

git config --global credential.helper store
git credential fill <<! | git credential approve
url=https://proxy.corp.example.com
username=user
password=password
!

Warning

Соблюдайте осторожность при использовании этого подхода: переменные среды могут отображаться в истории оболочки и в журналах

Дополнительная статья об оптимизации Golang приложений

Лого для проекта создано при помощи aasvg проекта. Вы можете создать такое же и/или модифицировать имеющееся. Для этого воспользуйтесь сайтом или установите figlet. Если Вы используете способ с установкой figlet, то вдобавок необходимо сказать необходимый шрифт, например я использую Doom. Далее, необходимо воспользоваться aasvg и конвертировать ascii арт в svg. Обратите внимание - по умолчанию будет svg в красном цвете, чтобы изменить цвет, необходимо изменить его определение тут

$ curl 'http://www.figlet.org/fonts/doom.flf' -o /usr/share/figlet/doom.flf
$ curl 'http://www.figlet.org/fonts/larry3d.flf' -o /usr/share/figlet/larry3d.flf
$ figlet -f doom 'Golang'

 _____       _
|  __ \     | |
| |  \/ ___ | | __ _ _ __   __ _
| | __ / _ \| |/ _` | '_ \ / _` |
| |_\ | (_) | | (_| | | | | (_| |
 \____/\___/|_|\__,_|_| |_|\__, |
                            __/ |
                           |___/

$ aasvg --source --embed < docs/ascii.txt > docs/images/logo.svg
Gopher
Gopher mascot.

Footnotes

  1. 🛠️ За счёт скрипта go-install-approximately.sh нас может не волновать полная версия Golang, мы можем передавать лишь приблизительно желаемую версию, а скрипт позаботится чтобы была выбрана последняя и актуальная из списка 2

  2. 🛠️ Например можно использовать свой приватный реестр образов: --build-arg image_registry=my-container-registry:1111/