Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c452c17583 | ||
|
|
d062133a27 | ||
|
|
d4d83345ee | ||
|
|
2a2de68337 | ||
|
|
2016f9671f | ||
|
|
578132e39b | ||
|
|
57eb9f32a8 | ||
|
|
6279ff8a3e |
70
.github/workflows/cve.yml
vendored
Normal file
70
.github/workflows/cve.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: cve
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 15 */2 * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cve:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: init / checkout
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: init / setup environment
|
||||||
|
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { existsSync, readFileSync } = require('node:fs');
|
||||||
|
const { resolve } = require('node:path');
|
||||||
|
const { inspect } = require('node:util');
|
||||||
|
const { Buffer } = require('node:buffer');
|
||||||
|
const inputs = `${{ toJSON(github.event.inputs) }}`;
|
||||||
|
const opt = {input:{}, dot:{}};
|
||||||
|
|
||||||
|
try{
|
||||||
|
if(inputs.length > 0){
|
||||||
|
opt.input = JSON.parse(inputs);
|
||||||
|
if(opt.input?.etc){
|
||||||
|
opt.input.etc = JSON.parse(Buffer.from(opt.input.etc, 'base64').toString('ascii'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
core.warning('could not parse github.event.inputs');
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
const path = resolve('.json');
|
||||||
|
if(existsSync(path)){
|
||||||
|
try{
|
||||||
|
opt.dot = JSON.parse(readFileSync(path).toString());
|
||||||
|
}catch(e){
|
||||||
|
throw new Error('could not parse .json');
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
throw new Error('.json does not exist');
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
core.setFailed(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(inspect(opt, {showHidden:false, depth:null, colors:true}));
|
||||||
|
|
||||||
|
core.exportVariable('WORKFLOW_IMAGE', `${opt.dot.image}:${(opt.dot?.semver?.version === undefined) ? 'rolling' : opt.dot.semver.version}`);
|
||||||
|
core.exportVariable('WORKFLOW_GRYPE_SEVERITY_CUTOFF', (opt.dot?.grype?.severity || 'high'));
|
||||||
|
|
||||||
|
|
||||||
|
- name: grype / scan
|
||||||
|
id: grype
|
||||||
|
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
|
||||||
|
with:
|
||||||
|
image: ${{ env.WORKFLOW_IMAGE }}
|
||||||
|
fail-build: true
|
||||||
|
severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }}
|
||||||
|
output-format: 'sarif'
|
||||||
|
by-cve: true
|
||||||
|
cache-db: true
|
||||||
10
.github/workflows/docker.yml
vendored
10
.github/workflows/docker.yml
vendored
@@ -101,7 +101,7 @@ jobs:
|
|||||||
const docker = {
|
const docker = {
|
||||||
image:{
|
image:{
|
||||||
name:opt.dot.image,
|
name:opt.dot.image,
|
||||||
arch:(opt.dot.arch || 'linux/amd64,linux/arm64'),
|
arch:(opt.input?.etc?.arch || opt.dot?.arch || 'linux/amd64,linux/arm64'),
|
||||||
prefix:((opt.input?.etc?.semverprefix) ? `${opt.input?.etc?.semverprefix}-` : ''),
|
prefix:((opt.input?.etc?.semverprefix) ? `${opt.input?.etc?.semverprefix}-` : ''),
|
||||||
suffix:((opt.input?.etc?.semversuffix) ? `-${opt.input?.etc?.semversuffix}` : ''),
|
suffix:((opt.input?.etc?.semversuffix) ? `-${opt.input?.etc?.semversuffix}` : ''),
|
||||||
description:(opt.dot?.readme?.description || ''),
|
description:(opt.dot?.readme?.description || ''),
|
||||||
@@ -228,7 +228,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
driver-opts: network=host
|
driver-opts: network=host
|
||||||
|
|
||||||
- name: docker / build & push & tag grype
|
- name: docker / build image locally
|
||||||
if: env.WORKFLOW_BUILD == 'true'
|
if: env.WORKFLOW_BUILD == 'true'
|
||||||
id: docker-build
|
id: docker-build
|
||||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
|
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
|
||||||
@@ -257,7 +257,7 @@ jobs:
|
|||||||
cache-db: true
|
cache-db: true
|
||||||
|
|
||||||
- name: grype / fail
|
- name: grype / fail
|
||||||
if: env.WORKFLOW_BUILD == 'true' && (failure() || steps.grype.outcome == 'failure')
|
if: env.WORKFLOW_BUILD == 'true' && (failure() || steps.grype.outcome == 'failure') && steps.docker-build.outcome == 'success'
|
||||||
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
|
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
|
||||||
with:
|
with:
|
||||||
image: ${{ env.DOCKER_CACHE_GRYPE }}
|
image: ${{ env.DOCKER_CACHE_GRYPE }}
|
||||||
@@ -267,7 +267,7 @@ jobs:
|
|||||||
by-cve: true
|
by-cve: true
|
||||||
cache-db: true
|
cache-db: true
|
||||||
|
|
||||||
- name: docker / build & push
|
- name: docker / build image from cache and push to registries
|
||||||
if: env.WORKFLOW_BUILD == 'true'
|
if: env.WORKFLOW_BUILD == 'true'
|
||||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
|
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
|
||||||
with:
|
with:
|
||||||
@@ -419,7 +419,7 @@ jobs:
|
|||||||
if [ -f LICENSE ]; then
|
if [ -f LICENSE ]; then
|
||||||
git add LICENSE
|
git add LICENSE
|
||||||
fi
|
fi
|
||||||
git commit -m "github-actions[bot]: update README.md"
|
git commit -m "docs: auto update README.md"
|
||||||
git push origin HEAD:master
|
git push origin HEAD:master
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
.json
1
.json
@@ -2,7 +2,6 @@
|
|||||||
"image": "11notes/netbird",
|
"image": "11notes/netbird",
|
||||||
"name": "netbird",
|
"name": "netbird",
|
||||||
"root": "/netbird",
|
"root": "/netbird",
|
||||||
"arch": "linux/amd64,linux/arm64,linux/arm/v7",
|
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "0.50.3"
|
"version": "0.50.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
# GLOBAL
|
# GLOBAL
|
||||||
ARG APP_UID=1000 \
|
ARG APP_UID=1000 \
|
||||||
APP_GID=1000 \
|
APP_GID=1000 \
|
||||||
BUILD_ROOT="/go/netbird/management /go/netbird/relay /go/netbird/signal"
|
BUILD_SRC=https://github.com/netbirdio/netbird.git \
|
||||||
|
BUILD_ROOT="/go/netbird/management /go/netbird/relay /go/netbird/signal" \
|
||||||
|
GO_VERSION=1.24
|
||||||
|
|
||||||
# :: FOREIGN IMAGES
|
# :: FOREIGN IMAGES
|
||||||
FROM 11notes/nginx:stable AS distroless-nginx
|
FROM 11notes/nginx:stable AS distroless-nginx
|
||||||
@@ -15,77 +17,51 @@
|
|||||||
# ║ BUILD ║
|
# ║ BUILD ║
|
||||||
# ╚═════════════════════════════════════════════════════╝
|
# ╚═════════════════════════════════════════════════════╝
|
||||||
# :: netbird
|
# :: netbird
|
||||||
FROM golang:1.24-alpine AS build
|
FROM 11notes/go:${GO_VERSION} AS build
|
||||||
COPY --from=util /usr/local/bin /usr/local/bin
|
|
||||||
ARG APP_VERSION \
|
ARG APP_VERSION \
|
||||||
BUILD_ROOT \
|
BUILD_SRC \
|
||||||
BUILD_BIN
|
BUILD_ROOT
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
apk --update --no-cache add \
|
git clone ${BUILD_SRC} -b v${APP_VERSION}; \
|
||||||
build-base \
|
sed -i 's/"development"/"v'${APP_VERSION}'"/' /go/netbird/version/version.go;
|
||||||
upx \
|
|
||||||
git;
|
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
git clone https://github.com/netbirdio/netbird -b v${APP_VERSION};
|
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
mkdir -p /distroless/usr/local/bin; \
|
|
||||||
for BUILD in ${BUILD_ROOT}; do \
|
for BUILD in ${BUILD_ROOT}; do \
|
||||||
cd ${BUILD}; \
|
cd ${BUILD}; \
|
||||||
BIN="${BUILD}/$(echo ${BUILD} | awk -F '/' '{print $4}')"; \
|
BUILD_BIN="${BUILD}/$(echo ${BUILD} | awk -F '/' '{print $4}')"; \
|
||||||
go build -ldflags="-extldflags=-static" -o ${BIN} main.go; \
|
go mod tidy; \
|
||||||
eleven checkStatic ${BIN}; \
|
eleven go build ${BUILD_BIN} main.go; \
|
||||||
eleven strip ${BIN}; \
|
eleven distroless ${BUILD_BIN}; \
|
||||||
cp ${BIN} /distroless/usr/local/bin; \
|
|
||||||
done; \
|
done; \
|
||||||
mv /distroless/usr/local/bin/management /distroless/usr/local/bin/netbird;
|
mv /distroless/usr/local/bin/management /distroless/usr/local/bin/netbird;
|
||||||
|
|
||||||
# :: management
|
# :: management
|
||||||
FROM golang:1.24-alpine AS management
|
FROM 11notes/go:${GO_VERSION} AS management
|
||||||
COPY --from=util /usr/local/bin /usr/local/bin
|
|
||||||
COPY ./build/go/management /go/management
|
COPY ./build/go/management /go/management
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ARG BIN=/go/management/management
|
ARG BUILD_BIN=/go/management/management
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
apk --update --no-cache add \
|
|
||||||
build-base \
|
|
||||||
upx;
|
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
cd /go/management; \
|
cd /go/management; \
|
||||||
go build -ldflags="-extldflags=-static" -o ${BIN} main.go; \
|
eleven go build ${BUILD_BIN} main.go; \
|
||||||
mkdir -p /distroless/usr/local/bin; \
|
eleven distroless ${BUILD_BIN};
|
||||||
eleven checkStatic ${BIN}; \
|
|
||||||
eleven strip ${BIN}; \
|
|
||||||
cp ${BIN} /distroless/usr/local/bin;
|
|
||||||
|
|
||||||
# :: dashboard
|
# :: dashboard
|
||||||
FROM golang:1.24-alpine AS dashboard
|
FROM 11notes/go:${GO_VERSION} AS dashboard
|
||||||
COPY --from=util /usr/local/bin /usr/local/bin
|
|
||||||
COPY ./build/go/dashboard /go/dashboard
|
COPY ./build/go/dashboard /go/dashboard
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ARG BIN=/go/dashboard/dashboard
|
ARG BUILD_BIN=/go/dashboard/dashboard
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
apk --update --no-cache add \
|
apk --update --no-cache add \
|
||||||
build-base \
|
|
||||||
upx \
|
|
||||||
git \
|
|
||||||
nodejs \
|
nodejs \
|
||||||
npm;
|
npm;
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
cd /go/dashboard; \
|
cd /go/dashboard; \
|
||||||
go build -ldflags="-extldflags=-static" -o ${BIN} main.go; \
|
eleven go build ${BUILD_BIN} main.go; \
|
||||||
mkdir -p /distroless/usr/local/bin; \
|
eleven distroless ${BUILD_BIN};
|
||||||
eleven checkStatic ${BIN}; \
|
|
||||||
eleven strip ${BIN}; \
|
|
||||||
cp ${BIN} /distroless/usr/local/bin;
|
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
git clone https://github.com/netbirdio/dashboard /dashboard;
|
git clone https://github.com/netbirdio/dashboard /dashboard;
|
||||||
@@ -100,7 +76,7 @@
|
|||||||
|
|
||||||
# :: file system
|
# :: file system
|
||||||
FROM alpine AS file-system
|
FROM alpine AS file-system
|
||||||
COPY --from=util /usr/local/bin /usr/local/bin
|
COPY --from=util / /
|
||||||
ARG APP_ROOT
|
ARG APP_ROOT
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
|
|||||||
39
compose.yaml
39
compose.yaml
@@ -1,4 +1,9 @@
|
|||||||
name: "netbird"
|
name: "netbird"
|
||||||
|
|
||||||
|
x-image-netbird: &image
|
||||||
|
image: "11notes/netbird:0.50.2"
|
||||||
|
read_only: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: "11notes/postgres:16"
|
image: "11notes/postgres:16"
|
||||||
@@ -6,22 +11,22 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
TZ: "Europe/Zurich"
|
TZ: "Europe/Zurich"
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
# make a full and compressed database backup each day at 03:00
|
||||||
|
POSTGRES_BACKUP_SCHEDULE: "0 3 * * *"
|
||||||
volumes:
|
volumes:
|
||||||
- "db.etc:/postgres/etc"
|
- "db.etc:/postgres/etc"
|
||||||
- "db.var:/postgres/var"
|
- "db.var:/postgres/var"
|
||||||
- "db.backup:/postgres/backup"
|
- "db.backup:/postgres/backup"
|
||||||
# used for optional cron container to create automatic backups
|
|
||||||
- "db.cmd:/run/cmd"
|
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- "/run/postgresql:uid=1000,gid=1000"
|
# needed for read-only
|
||||||
|
- "/postgres/run:uid=1000,gid=1000"
|
||||||
- "/postgres/log:uid=1000,gid=1000"
|
- "/postgres/log:uid=1000,gid=1000"
|
||||||
networks:
|
networks:
|
||||||
backend:
|
backend:
|
||||||
restart: "always"
|
restart: "always"
|
||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
image: "11notes/netbird:0.48.0"
|
<<: *image
|
||||||
read_only: true
|
|
||||||
environment:
|
environment:
|
||||||
NETBIRD_MGMT_API_ENDPOINT: "https://${NETBIRD_FQDN}"
|
NETBIRD_MGMT_API_ENDPOINT: "https://${NETBIRD_FQDN}"
|
||||||
NETBIRD_MGMT_GRPC_API_ENDPOINT: "https://${NETBIRD_FQDN}"
|
NETBIRD_MGMT_GRPC_API_ENDPOINT: "https://${NETBIRD_FQDN}"
|
||||||
@@ -54,8 +59,7 @@ services:
|
|||||||
db:
|
db:
|
||||||
condition: "service_healthy"
|
condition: "service_healthy"
|
||||||
restart: true
|
restart: true
|
||||||
image: "11notes/netbird:0.48.0"
|
<<: *image
|
||||||
read_only: true
|
|
||||||
env_file: '.env'
|
env_file: '.env'
|
||||||
environment:
|
environment:
|
||||||
TZ: "Europe/Zurich"
|
TZ: "Europe/Zurich"
|
||||||
@@ -82,7 +86,7 @@ services:
|
|||||||
restart: "always"
|
restart: "always"
|
||||||
|
|
||||||
signal:
|
signal:
|
||||||
image: "11notes/netbird:0.48.0"
|
<<: *image
|
||||||
environment:
|
environment:
|
||||||
TZ: "Europe/Zurich"
|
TZ: "Europe/Zurich"
|
||||||
entrypoint: ["/usr/local/bin/signal"]
|
entrypoint: ["/usr/local/bin/signal"]
|
||||||
@@ -100,7 +104,7 @@ services:
|
|||||||
restart: "always"
|
restart: "always"
|
||||||
|
|
||||||
relay:
|
relay:
|
||||||
image: "11notes/netbird:0.48.0"
|
<<: *image
|
||||||
environment:
|
environment:
|
||||||
TZ: "Europe/Zurich"
|
TZ: "Europe/Zurich"
|
||||||
NB_LISTEN_ADDRESS: ":33080"
|
NB_LISTEN_ADDRESS: ":33080"
|
||||||
@@ -113,22 +117,6 @@ services:
|
|||||||
- "33080:33080/tcp"
|
- "33080:33080/tcp"
|
||||||
restart: "always"
|
restart: "always"
|
||||||
|
|
||||||
# optional images
|
|
||||||
cron:
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: "service_healthy"
|
|
||||||
restart: true
|
|
||||||
image: "11notes/cron:4.6"
|
|
||||||
environment:
|
|
||||||
TZ: "Europe/Zurich"
|
|
||||||
# create daily full backup at 3 o'clock
|
|
||||||
CRONTAB: |-
|
|
||||||
0 3 * * * cmd-socket '{"bin":"backup"}' > /proc/1/fd/1
|
|
||||||
volumes:
|
|
||||||
- "db.cmd:/run/cmd"
|
|
||||||
restart: "always"
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
management.etc:
|
management.etc:
|
||||||
management.var:
|
management.var:
|
||||||
@@ -137,7 +125,6 @@ volumes:
|
|||||||
db.etc:
|
db.etc:
|
||||||
db.var:
|
db.var:
|
||||||
db.backup:
|
db.backup:
|
||||||
db.cmd:
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
frontend:
|
frontend:
|
||||||
|
|||||||
24
project.md
24
project.md
@@ -2,15 +2,19 @@ ${{ content_synopsis }} This image will run netbird from a single image (not mul
|
|||||||
|
|
||||||
The init binary **management** will replace all variables in the format ```${VARIABLE}``` with all environment variables present in the service.
|
The init binary **management** will replace all variables in the format ```${VARIABLE}``` with all environment variables present in the service.
|
||||||
|
|
||||||
${{ github:> [!IMPORTANT] }}
|
${{ content_uvp }} Good question! Because ...
|
||||||
${{ github:> }}* This image runs as 1000:1000 by default, most other images run everything as root
|
|
||||||
${{ github:> }}* This image has no shell since it is distroless, most other images run on a distro like Debian or Alpine with full shell access (security)
|
|
||||||
${{ github:> }}* This image does not ship with any critical or high rated CVE and is automatically maintained via CI/CD, most other images mostly have no CVE scanning or code quality tools in place
|
|
||||||
${{ github:> }}* This image is created via a secure, pinned CI/CD process and immune to upstream attacks, most other images have upstream dependencies that can be exploited
|
|
||||||
${{ github:> }}* This image works as read-only, most other images need to write files to the image filesystem
|
|
||||||
${{ github:> }}* This image is a lot smaller than most other images
|
|
||||||
|
|
||||||
If you value security, simplicity and the ability to interact with the maintainer and developer of an image. Using my images is a great start in that direction.
|
${{ github:> [!IMPORTANT] }}
|
||||||
|
${{ github:> }}* ... this image runs [rootless](https://github.com/11notes/RTFM/blob/main/linux/container/image/rootless.md) as 1000:1000
|
||||||
|
${{ github:> }}* ... this image has no shell since it is [distroless](https://github.com/11notes/RTFM/blob/main/linux/container/image/distroless.md)
|
||||||
|
${{ github:> }}* ... this image is auto updated to the latest version via CI/CD
|
||||||
|
${{ github:> }}* ... this image has a health check
|
||||||
|
${{ github:> }}* ... this image runs read-only
|
||||||
|
${{ github:> }}* ... this image is automatically scanned for CVEs before and after publishing
|
||||||
|
${{ github:> }}* ... this image is created via a secure and pinned CI/CD process
|
||||||
|
${{ github:> }}* ... this image is very small
|
||||||
|
|
||||||
|
If you value security, simplicity and optimizations to the extreme, then this image might be for you.
|
||||||
|
|
||||||
# COMPARISON 🏁
|
# COMPARISON 🏁
|
||||||
Below you find a comparison between this image and the most used or original one.
|
Below you find a comparison between this image and the most used or original one.
|
||||||
@@ -24,7 +28,7 @@ Below you find a comparison between this image and the most used or original one
|
|||||||
|
|
||||||
${{ title_volumes }}
|
${{ title_volumes }}
|
||||||
* **${{ json_root }}/etc** - Directory of your management.json config
|
* **${{ json_root }}/etc** - Directory of your management.json config
|
||||||
* **${{ json_root }}/var** - Directory of dynamic data from differnet init systems (relay, signal, management)
|
* **${{ json_root }}/var** - Directory of dynamic data from different init systems (relay, signal, management)
|
||||||
|
|
||||||
# EXAMPLE ENV FILE 📑
|
# EXAMPLE ENV FILE 📑
|
||||||
```ini
|
```ini
|
||||||
@@ -63,4 +67,4 @@ ${{ content_tips }}
|
|||||||
|
|
||||||
${{ title_caution }}
|
${{ title_caution }}
|
||||||
${{ github:> [!CAUTION] }}
|
${{ github:> [!CAUTION] }}
|
||||||
${{ github:> }}* Because this image is distroless, it only works with PostgreSQL, not SQLite. The GeoLocation middleware is also disabled because of this!
|
${{ github:> }}* Because this image is distroless, it only works with PostgreSQL, **not SQLite**. The GeoLocation middleware is also disabled because of this!
|
||||||
Reference in New Issue
Block a user