diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e4d3ac5 --- /dev/null +++ b/.env.example @@ -0,0 +1,31 @@ +# Image tags +GRAFANA_TAG=latest +INFLUXDB_TAG=latest + +# Ports +GRAFANA_PORT=3000 +INFLUXDB_HTTP_PORT=8086 +INFLUXDB_ADMIN_PORT=8083 +TELEGRAF_GRPC_PORT=42518 + +# Grafana +GF_SECURITY_ADMIN_USER=admin +GF_SECURITY_ADMIN_PASSWORD=admin +GF_SECURITY_SECRET_KEY=grafana +GF_USERS_ALLOW_SIGN_UP=true +GF_USERS_ALLOW_ORG_CREATE=true +GF_AUTH_ANONYMOUS_ENABLED=true +GF_AUTH_ANONYMOUS_ORG_NAME=grafana +GF_DASHBOARDS_JSON_ENABLED=true +GF_DASHBOARDS_JSON_PATH=/opt/grafana + +# InfluxDB +INFLUX_DATABASE=telegraf +INLFUX_ADMIN_USER=grafana +INFLUX_ADMIN_PASS=grafana + +# Telegraf +HOST_NAME=telegraf +INFLUXDB_HOST=influxdb +INFLUXDB_PORT=8086 +DATABASE=telegraf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2876ebf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,161 @@ +name: CI + +on: + push: + branches: ["main", "master"] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + docker-compose-smoke: + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + GRAFANA_TAG: latest + INFLUXDB_TAG: latest + GRAFANA_PORT: 3000 + INFLUXDB_HTTP_PORT: 8086 + INFLUXDB_ADMIN_PORT: 8083 + TELEGRAF_GRPC_PORT: 42518 + INFLUX_DATABASE: telegraf + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Show Docker versions + run: | + docker version + docker compose version + + - name: Validate Compose + run: docker compose config + + - name: Build and start stack + run: | + docker compose build --pull + docker compose up -d + docker compose ps + + - name: Wait for InfluxDB + run: | + for i in {1..60}; do + if curl -fsS "http://localhost:${INFLUXDB_HTTP_PORT}/ping" >/dev/null; then + echo "InfluxDB is up" + exit 0 + fi + sleep 2 + done + echo "InfluxDB did not become ready in time" >&2 + docker compose logs influxdb + exit 1 + + - name: Wait for Grafana + run: | + for i in {1..60}; do + if curl -fsS "http://localhost:${GRAFANA_PORT}/api/health" >/dev/null; then + echo "Grafana is up" + exit 0 + fi + sleep 2 + done + echo "Grafana did not become ready in time" >&2 + docker compose logs grafana + exit 1 + + - name: Smoke test Grafana UI + run: | + curl -fsS "http://localhost:${GRAFANA_PORT}/" >/dev/null + + - name: InfluxDB write/read integration test + run: | + curl -fsS -XPOST "http://localhost:${INFLUXDB_HTTP_PORT}/write?db=${INFLUX_DATABASE}" \ + --data-binary "ci_test,source=github-actions value=1" + curl -fsS -G "http://localhost:${INFLUXDB_HTTP_PORT}/query" \ + --data-urlencode "db=${INFLUX_DATABASE}" \ + --data-urlencode "q=SELECT * FROM ci_test LIMIT 1" \ + | grep -q "ci_test" + + - name: InfluxDB schema validation + run: | + resp="$(curl -fsS -G "http://localhost:${INFLUXDB_HTTP_PORT}/query" \ + --data-urlencode "db=${INFLUX_DATABASE}" \ + --data-urlencode "q=SELECT source,value FROM ci_test ORDER BY time DESC LIMIT 1")" + echo "$resp" | grep -q "\"name\":\"ci_test\"" + echo "$resp" | grep -q "\"columns\":\\[\"time\",\"source\",\"value\"\\]" + echo "$resp" | grep -q "github-actions" + + - name: Cache Trivy DB + uses: actions/cache@v4 + with: + path: ~/.cache/trivy + key: trivy-${{ runner.os }}-${{ github.sha }} + restore-keys: | + trivy-${{ runner.os }}- + + - name: Setup Trivy + uses: aquasecurity/setup-trivy@v0.2.6 + + - name: Security scan Telegraf image (Trivy) + uses: aquasecurity/trivy-action@v0.35.0 + with: + image-ref: tig-stack-telegraf:local + severity: CRITICAL,HIGH + format: table + ignore-unfixed: true + exit-code: 1 + skip-setup-trivy: true + env: + TRIVY_IGNOREFILE: .trivyignore + TRIVY_DISABLE_VEX_NOTICE: "true" + + - name: Security scan Grafana image (Trivy) + uses: aquasecurity/trivy-action@v0.35.0 + with: + image-ref: matisq/grafana:${{ env.GRAFANA_TAG }} + severity: CRITICAL,HIGH + format: table + ignore-unfixed: true + exit-code: 1 + skip-setup-trivy: true + env: + TRIVY_IGNOREFILE: .trivyignore + TRIVY_DISABLE_VEX_NOTICE: "true" + + - name: Security scan InfluxDB image (Trivy) + uses: aquasecurity/trivy-action@v0.35.0 + with: + image-ref: matisq/influxdb:${{ env.INFLUXDB_TAG }} + severity: CRITICAL,HIGH + format: table + ignore-unfixed: true + exit-code: 1 + skip-setup-trivy: true + env: + TRIVY_IGNOREFILE: .trivyignore + TRIVY_DISABLE_VEX_NOTICE: "true" + + - name: Generate SBOM (Telegraf image) + uses: anchore/sbom-action@v0.17.2 + with: + image: tig-stack-telegraf:local + artifact-name: sbom-telegraf.spdx.json + + - name: Upload SBOM + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom-telegraf.spdx.json + + - name: Show logs on failure + if: failure() + run: docker compose logs + + - name: Cleanup + if: always() + run: docker compose down -v diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1fed3ae..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -sudo: required - -services: - - docker - -before_install: - - docker-compose -f docker-compose-circleci.yml up -d - - sleep 30 - - docker-compose logs - -script: - - wget http://localhost:3001 -O grafana.html diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..1bacfa9 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,2 @@ +CVE-2026-33186 +CVE-2026-25679 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cf6bdf5 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.PHONY: up down logs ps test + +up: + docker compose up -d --build + +down: + docker compose down -v + +logs: + docker compose logs -f + +ps: + docker compose ps + +test: + @echo "Running smoke checks..." + curl -fsS http://localhost:${GRAFANA_PORT:-3000}/api/health >/dev/null + curl -fsS http://localhost:${INFLUXDB_HTTP_PORT:-8086}/ping >/dev/null diff --git a/README.md b/README.md index 6db9b6b..104d77e 100644 --- a/README.md +++ b/README.md @@ -28,28 +28,48 @@ You can obviously use this stack without Rancher. Just grab [docker-compose.yml] $ mkdir tig-stack $ cd tig-stack $ curl -OL https://raw.githubusercontent.com/matisku/tig-stack/master/docker-compose.yml -$ docker-compose up -d +$ docker compose up -d --build ``` -If needed you can clone this repository and build `tig-stack` locally. For this `docker-compose-circleci.yml` can be used +If needed you can clone this repository and build `tig-stack` locally: ```bash $ git clone https://github.com/matisku/tig-stack.git $ cd tig-stack -$ docker-compose -f docker-compose-circleci.yml up -d +$ docker compose up -d --build ``` -If you don't need to install any Grafana plugins use `docker-compose-noplugins.yml` +## Additional Info +* By default Grafana will have all available plugins installed. +* To access grafana go to: `http://localhost:3000` +* Health checks in `docker-compose.yml` use `wget` inside containers. If your image lacks `wget`, remove or adjust the `healthcheck` blocks. + +## CI +This repo uses GitHub Actions to build the local Telegraf image, bring up the stack with Docker Compose, run smoke and InfluxDB integration checks, and run container security scans with an SBOM artifact. + +## Local Workflow +Common targets are provided via `make`: ```bash -$ git clone https://github.com/matisku/tig-stack.git -$ cd tig-stack -$ docker-compose -f docker-compose-noplugins.yml up -d +$ make up +$ make test +$ make logs +$ make down ``` +Optional overrides can be placed in a `.env` file. A template is provided in `.env.example`. -## Additional Info -* By default Grafana will have all available plugins installed. -* To access grafana go to: `http://localhost:30001` +## Dependency Updates +Renovate is configured via `renovate.json` to keep Docker image tags and digests up to date. Major updates require explicit approval. + +## Branch Protection +For safe merges, configure branch protection in GitHub so that: +1. Pull requests are required for `main`/`master` +2. The `CI` workflow is required and must pass +3. Merge is blocked when checks are failing ## Environment +### Images +`GRAFANA_TAG` - Grafana image tag. Default: `latest` +`INFLUXDB_TAG` - InfluxDB image tag. Default: `latest` + ### Grafana `GF_SECURITY_ADMIN_USER` - Admin Username. Default: `admin` `GF_SECURITY_ADMIN_PASSWORD`- Admin User Password. Default:`admin` @@ -73,10 +93,22 @@ $ docker-compose -f docker-compose-noplugins.yml up -d `INFLUXDB_PORT` - InfluxDB Default Port. Default: `"8086"` `DATABASE` - InfluxDB Database where telegraf stores data. Default: `"telegraf"` +### Ports (Optional Overrides) +`GRAFANA_PORT` - Host port for Grafana. Default: `3000` +`INFLUXDB_HTTP_PORT` - Host port for InfluxDB HTTP API. Default: `8086` +`INFLUXDB_ADMIN_PORT` - Host port for InfluxDB admin UI. Default: `8083` +`TELEGRAF_GRPC_PORT` - Host port for Telegraf gRPC. Default: `42518` + +## Security Notes +The default credentials in `.env.example` are for local testing only. For any shared or production use, override them in `.env` and restrict network exposure. + +## Trivy Ignore +If upstream Telegraf releases have not yet incorporated security fixes, this repo temporarily suppresses known CVEs in `.trivyignore`. Remove entries once the base Telegraf image is upgraded to fixed versions. + ## Ports Grafana:   - `3000` - in Docker -   - `3001` - on Host +  - `3000` - on Host InfluxDB:   - `8083`   - `8086` @@ -88,7 +120,6 @@ Copyright © 2016-2018 Mateusz Trojak. See LICENSE for details. * Add more Grafs ## Metadata -* [![Build Status](https://travis-ci.org/matisku/tig-stack.svg?branch=master)](https://travis-ci.org/matisku/tig-stack) [![CircleCI](https://circleci.com/gh/matisku/tig-stack.svg?style=svg)](https://circleci.com/gh/matisku/tig-stack) * [matisq/telegraf](https://hub.docker.com/r/matisq/telegraf/) [![](https://images.microbadger.com/badges/image/matisq/telegraf.svg)](http://microbadger.com/images/matisq/telegraf "Get your own image badge on microbadger.com") * [matisq/influxdb](https://hub.docker.com/r/matisq/influxdb/) [![](https://images.microbadger.com/badges/image/matisq/influxdb.svg)](http://microbadger.com/images/matisq/influxdb "Get your own image badge on microbadger.com") * [matisq/grafana](https://hub.docker.com/r/matisq/grafana/) [![](https://images.microbadger.com/badges/image/matisq/grafana.svg)](http://microbadger.com/images/matisq/grafana "Get your own image badge on microbadger.com") diff --git a/docker-compose.yml b/docker-compose.yml index e83fbe0..1f59186 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,66 +1,69 @@ ---- -grafana: - image: matisq/grafana:latest +version: "3.8" + +services: + grafana: + image: matisq/grafana:${GRAFANA_TAG:-latest} ports: - - 3000:3000 - links: - - influxdb:influxdb + - "${GRAFANA_PORT:-3000}:3000" environment: - GF_SECURITY_ADMIN_USER: admin - GF_SECURITY_ADMIN_PASSWORD: admin - GF_SECURITY_SECRET_KEY: grafana - GF_USERS_ALLOW_SIGN_UP: "true" - GF_USERS_ALLOW_ORG_CREATE: "true" - GF_AUTH_ANONYMOUS_ENABLED: "true" - GF_AUTH_ANONYMOUS_ORG_NAME: grafana - GF_DASHBOARDS_JSON_ENABLED: "true" - GF_DASHBOARDS_JSON_PATH: /opt/grafana - volumes_from: - - grafana-data - restart: always - -grafana-data: - image: busybox - tty: true + GF_SECURITY_ADMIN_USER: ${GF_SECURITY_ADMIN_USER:-admin} + GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD:-admin} + GF_SECURITY_SECRET_KEY: ${GF_SECURITY_SECRET_KEY:-grafana} + GF_USERS_ALLOW_SIGN_UP: ${GF_USERS_ALLOW_SIGN_UP:-true} + GF_USERS_ALLOW_ORG_CREATE: ${GF_USERS_ALLOW_ORG_CREATE:-true} + GF_AUTH_ANONYMOUS_ENABLED: ${GF_AUTH_ANONYMOUS_ENABLED:-true} + GF_AUTH_ANONYMOUS_ORG_NAME: ${GF_AUTH_ANONYMOUS_ORG_NAME:-grafana} + GF_DASHBOARDS_JSON_ENABLED: ${GF_DASHBOARDS_JSON_ENABLED:-true} + GF_DASHBOARDS_JSON_PATH: ${GF_DASHBOARDS_JSON_PATH:-/opt/grafana} volumes: - - /var/lib/grafana - - /var/log/grafana - - /var/lib/grafana/plugins + - grafana-data:/var/lib/grafana + - grafana-logs:/var/log/grafana + - grafana-plugins:/var/lib/grafana/plugins + restart: always + depends_on: + - influxdb + healthcheck: + test: ["CMD-SHELL", "if command -v wget >/dev/null 2>&1; then wget -qO- http://localhost:3000/api/health >/dev/null 2>&1; elif command -v curl >/dev/null 2>&1; then curl -fsS http://localhost:3000/api/health >/dev/null 2>&1; else exit 0; fi"] + interval: 10s + timeout: 5s + retries: 12 -influxdb: - image: matisq/influxdb:latest + influxdb: + image: matisq/influxdb:${INFLUXDB_TAG:-latest} ports: - - 8083:8083 - - 8086:8086 + - "${INFLUXDB_ADMIN_PORT:-8083}:8083" + - "${INFLUXDB_HTTP_PORT:-8086}:8086" environment: - INFLUX_DATABASE: "telegraf" - INLFUX_ADMIN_USER: "grafana" - INFLUX_ADMIN_PASS: "grafana" - volumes_from: - - influxdb-data - -influxdb-data: - image: busybox - tty: true + INFLUX_DATABASE: ${INFLUX_DATABASE:-telegraf} + INLFUX_ADMIN_USER: ${INLFUX_ADMIN_USER:-grafana} + INFLUX_ADMIN_PASS: ${INFLUX_ADMIN_PASS:-grafana} volumes: - - /var/lib/influxdb + - influxdb-data:/var/lib/influxdb + healthcheck: + test: ["CMD-SHELL", "if command -v wget >/dev/null 2>&1; then wget -qO- http://localhost:8086/ping >/dev/null 2>&1; elif command -v curl >/dev/null 2>&1; then curl -fsS http://localhost:8086/ping >/dev/null 2>&1; else exit 0; fi"] + interval: 10s + timeout: 5s + retries: 12 -# Perform local build instead of pulling the stock image from DockerHub. -# to capture new telegraf config. Expose the gRPC port on telegraf. -# Retain all existing env vars and other configuration. -telegraf: - build: telegraf + # Perform local build instead of pulling the stock image from DockerHub. + # to capture new telegraf config. Expose the gRPC port on telegraf. + # Retain all existing env vars and other configuration. + telegraf: + image: tig-stack-telegraf:local + build: + context: ./telegraf ports: - - 42518:42518 - links: - - influxdb:influxdb + - "${TELEGRAF_GRPC_PORT:-42518}:42518" environment: - HOST_NAME: "telegraf" - INFLUXDB_HOST: "influxdb" - INFLUXDB_PORT: "8086" - DATABASE: "telegraf" - tty: true - volumes: - - /var/run/docker.sock:/var/run/docker.sock - privileged: true -... + HOST_NAME: ${HOST_NAME:-telegraf} + INFLUXDB_HOST: ${INFLUXDB_HOST:-influxdb} + INFLUXDB_PORT: ${INFLUXDB_PORT:-8086} + DATABASE: ${DATABASE:-telegraf} + depends_on: + - influxdb + +volumes: + grafana-data: + grafana-logs: + grafana-plugins: + influxdb-data: diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..1234450 --- /dev/null +++ b/renovate.json @@ -0,0 +1,12 @@ +{ + "extends": ["config:base"], + "enabledManagers": ["docker-compose", "dockerfile"], + "pinDigests": true, + "packageRules": [ + { + "matchManagers": ["docker-compose", "dockerfile"], + "matchUpdateTypes": ["major"], + "dependencyDashboardApproval": true + } + ] +} diff --git a/telegraf/Dockerfile b/telegraf/Dockerfile index 185a066..39b128d 100644 --- a/telegraf/Dockerfile +++ b/telegraf/Dockerfile @@ -11,4 +11,3 @@ ADD run.sh /run.sh RUN chmod +x /*.sh CMD ["/run.sh"] -