refactor: protect redis instance with a password

This commit is contained in:
Nicolas Meienberger 2023-08-24 18:40:38 +02:00 committed by Nicolas Meienberger
parent ead4ced154
commit e65461b146
15 changed files with 30 additions and 355 deletions

View file

@ -17,5 +17,6 @@ POSTGRES_USERNAME=tipi
POSTGRES_PASSWORD=postgres
POSTGRES_PORT=5432
REDIS_HOST=tipi-redis
REDIS_PASSWORD=redis
DEMO_MODE=false
LOCAL_DOMAIN=tipi.lan

View file

@ -1,8 +1,8 @@
version: '3.7'
services:
reverse-proxy:
container_name: reverse-proxy
tipi-reverse-proxy:
container_name: tipi-reverse-proxy
image: traefik:v2.8
restart: on-failure
ports:
@ -42,6 +42,7 @@ services:
container_name: tipi-redis
image: redis:alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
ports:
- 6379:6379
volumes:
@ -54,11 +55,11 @@ services:
networks:
- tipi_main_network
dashboard:
tipi-dashboard:
build:
context: .
dockerfile: Dockerfile.dev
container_name: dashboard
container_name: tipi-dashboard
depends_on:
tipi-db:
condition: service_healthy

View file

@ -1,121 +0,0 @@
version: '3.7'
services:
reverse-proxy:
container_name: reverse-proxy
image: traefik:v2.8
restart: unless-stopped
ports:
- ${NGINX_PORT-80}:80
- ${NGINX_PORT_SSL-443}:443
command: --providers.docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/traefik:/root/.config
- ${PWD}/traefik/shared:/shared
networks:
- tipi_main_network
tipi-db:
container_name: tipi-db
image: postgres:14
restart: unless-stopped
stop_grace_period: 1m
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: tipi
POSTGRES_DB: tipi
healthcheck:
test: ['CMD-SHELL', 'pg_isready -d tipi -U tipi']
interval: 5s
timeout: 10s
retries: 120
networks:
- tipi_main_network
tipi-redis:
container_name: tipi-redis
image: redis:alpine
restart: unless-stopped
volumes:
- ./data/redis:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 10s
retries: 120
networks:
- tipi_main_network
dashboard:
image: meienberger/runtipi:${DOCKER_TAG}
restart: unless-stopped
container_name: dashboard
networks:
- tipi_main_network
depends_on:
tipi-db:
condition: service_healthy
tipi-redis:
condition: service_healthy
environment:
NODE_ENV: production
INTERNAL_IP: ${INTERNAL_IP}
TIPI_VERSION: ${TIPI_VERSION}
JWT_SECRET: ${JWT_SECRET}
NGINX_PORT: ${NGINX_PORT}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USERNAME: ${POSTGRES_USERNAME}
POSTGRES_DBNAME: ${POSTGRES_DBNAME}
POSTGRES_HOST: ${POSTGRES_HOST}
APPS_REPO_ID: ${APPS_REPO_ID}
APPS_REPO_URL: ${APPS_REPO_URL}
DOMAIN: ${DOMAIN}
ARCHITECTURE: ${ARCHITECTURE}
REDIS_HOST: ${REDIS_HOST}
DEMO_MODE: ${DEMO_MODE}
LOCAL_DOMAIN: ${LOCAL_DOMAIN}
volumes:
- ${PWD}/state:/runtipi/state
- ${PWD}/repos:/runtipi/repos:ro
- ${PWD}/apps:/runtipi/apps
- ${PWD}/logs:/app/logs
- ${PWD}/traefik:/runtipi/traefik
- ${PWD}:/app/storage
labels:
# Main
traefik.enable: true
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https
traefik.http.services.dashboard.loadbalancer.server.port: 3000
# Local ip
traefik.http.routers.dashboard.rule: PathPrefix("/")
traefik.http.routers.dashboard.service: dashboard
traefik.http.routers.dashboard.entrypoints: web
# Websecure
traefik.http.routers.dashboard-insecure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-insecure.service: dashboard
traefik.http.routers.dashboard-insecure.entrypoints: web
traefik.http.routers.dashboard-insecure.middlewares: redirect-to-https
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-secure.service: dashboard
traefik.http.routers.dashboard-secure.entrypoints: websecure
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
# Local domain
traefik.http.routers.dashboard-local-insecure.rule: Host(`${LOCAL_DOMAIN}`)
traefik.http.routers.dashboard-local-insecure.entrypoints: web
traefik.http.routers.dashboard-local-insecure.service: dashboard
traefik.http.routers.dashboard-local-insecure.middlewares: redirect-to-https
traefik.http.routers.dashboard-local.rule: Host(`${LOCAL_DOMAIN}`)
traefik.http.routers.dashboard-local.entrypoints: websecure
traefik.http.routers.dashboard-local.tls: true
traefik.http.routers.dashboard-local.service: dashboard
networks:
tipi_main_network:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.21.21.0/24

View file

@ -1,106 +0,0 @@
version: '3.7'
services:
reverse-proxy:
container_name: reverse-proxy
image: traefik:v2.8
restart: unless-stopped
ports:
- ${NGINX_PORT-80}:80
- ${NGINX_PORT_SSL-443}:443
- 8080:8080
command: --providers.docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/traefik:/root/.config
- ${PWD}/traefik/shared:/shared
networks:
- tipi_main_network
tipi-db:
container_name: tipi-db
image: postgres:14
restart: unless-stopped
stop_grace_period: 1m
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: tipi
POSTGRES_DB: tipi
healthcheck:
test: ['CMD-SHELL', 'pg_isready -d tipi -U tipi']
interval: 5s
timeout: 10s
retries: 120
networks:
- tipi_main_network
tipi-redis:
container_name: tipi-redis
image: redis:alpine
restart: unless-stopped
volumes:
- ./data/redis:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 10s
retries: 120
networks:
- tipi_main_network
dashboard:
image: meienberger/runtipi:rc-${TIPI_VERSION}
container_name: dashboard
networks:
- tipi_main_network
depends_on:
tipi-db:
condition: service_healthy
tipi-redis:
condition: service_healthy
environment:
NODE_ENV: production
INTERNAL_IP: ${INTERNAL_IP}
TIPI_VERSION: ${TIPI_VERSION}
JWT_SECRET: ${JWT_SECRET}
NGINX_PORT: ${NGINX_PORT}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USERNAME: ${POSTGRES_USERNAME}
POSTGRES_DBNAME: ${POSTGRES_DBNAME}
POSTGRES_HOST: ${POSTGRES_HOST}
APPS_REPO_ID: ${APPS_REPO_ID}
APPS_REPO_URL: ${APPS_REPO_URL}
DOMAIN: ${DOMAIN}
ARCHITECTURE: ${ARCHITECTURE}
REDIS_HOST: ${REDIS_HOST}
DEMO_MODE: ${DEMO_MODE}
volumes:
- ${PWD}/.env:/runtipi/.env
- ${PWD}/state:/runtipi/state
- ${PWD}/repos:/runtipi/repos:ro
- ${PWD}/apps:/runtipi/apps
- ${PWD}/logs:/app/logs
- ${STORAGE_PATH}:/app/storage
labels:
traefik.enable: true
# Web
traefik.http.routers.dashboard.rule: PathPrefix("/")
traefik.http.routers.dashboard.service: dashboard
traefik.http.routers.dashboard.entrypoints: web
traefik.http.services.dashboard.loadbalancer.server.port: 3000
# Websecure
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-secure.service: dashboard-secure
traefik.http.routers.dashboard-secure.entrypoints: websecure
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
networks:
tipi_main_network:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.21.21.0/24

View file

@ -1,113 +0,0 @@
version: '3.7'
services:
reverse-proxy:
container_name: reverse-proxy
image: traefik:v2.8
restart: unless-stopped
ports:
- ${NGINX_PORT-80}:80
- ${NGINX_PORT_SSL-443}:443
command: --providers.docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/traefik:/root/.config
- ${PWD}/traefik/shared:/shared
networks:
- tipi_main_network
tipi-db:
container_name: tipi-db
image: postgres:14
restart: unless-stopped
stop_grace_period: 1m
ports:
- 5432:5432
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: tipi
POSTGRES_DB: tipi
healthcheck:
test: ['CMD-SHELL', 'pg_isready -d tipi -U tipi']
interval: 5s
timeout: 10s
retries: 120
networks:
- tipi_main_network
tipi-redis:
container_name: tipi-redis
image: redis:alpine
restart: unless-stopped
volumes:
- ./data/redis:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 10s
retries: 120
networks:
- tipi_main_network
dashboard:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
container_name: dashboard
networks:
- tipi_main_network
depends_on:
tipi-db:
condition: service_healthy
tipi-redis:
condition: service_healthy
environment:
NODE_ENV: production
INTERNAL_IP: ${INTERNAL_IP}
TIPI_VERSION: ${TIPI_VERSION}
JWT_SECRET: ${JWT_SECRET}
NGINX_PORT: ${NGINX_PORT}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USERNAME: ${POSTGRES_USERNAME}
POSTGRES_DBNAME: ${POSTGRES_DBNAME}
POSTGRES_HOST: ${POSTGRES_HOST}
APPS_REPO_ID: ${APPS_REPO_ID}
APPS_REPO_URL: ${APPS_REPO_URL}
DOMAIN: ${DOMAIN}
ARCHITECTURE: ${ARCHITECTURE}
REDIS_HOST: ${REDIS_HOST}
DEMO_MODE: ${DEMO_MODE}
volumes:
- ${PWD}/.env:/runtipi/.env
- ${PWD}/state:/runtipi/state
- ${PWD}/repos:/runtipi/repos:ro
- ${PWD}/apps:/runtipi/apps
- ${PWD}/logs:/app/logs
- ${STORAGE_PATH}:/app/storage
labels:
traefik.enable: true
# Web
traefik.http.routers.dashboard.rule: PathPrefix("/")
traefik.http.routers.dashboard.service: dashboard
traefik.http.routers.dashboard.entrypoints: web
traefik.http.services.dashboard.loadbalancer.server.port: 3000
# Websecure
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-secure.service: dashboard-secure
traefik.http.routers.dashboard-secure.entrypoints: websecure
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
networks:
tipi_main_network:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.21.21.0/24
volumes:
pgdata:

View file

@ -1,8 +1,8 @@
version: '3.7'
services:
reverse-proxy:
container_name: reverse-proxy
tipi-reverse-proxy:
container_name: tipi-reverse-proxy
image: traefik:v2.8
restart: on-failure
ports:
@ -21,6 +21,8 @@ services:
image: postgres:14
restart: on-failure
stop_grace_period: 1m
ports:
- 5432:5432
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
@ -37,8 +39,9 @@ services:
tipi-redis:
container_name: tipi-redis
image: redis:alpine
image: redis:7.2.0-alpine
restart: on-failure
command: redis-server --requirepass ${REDIS_PASSWORD}
ports:
- 6379:6379
volumes:
@ -51,10 +54,10 @@ services:
networks:
- tipi_main_network
dashboard:
tipi-dashboard:
image: meienberger/runtipi:${TIPI_VERSION}
restart: on-failure
container_name: dashboard
container_name: tipi-dashboard
networks:
- tipi_main_network
depends_on:

View file

@ -159,8 +159,8 @@ export class SystemExecutors {
spinner.start();
await execAsync('docker rm -f tipi-db');
await execAsync('docker rm -f tipi-redis');
await execAsync('docker rm -f dashboard');
await execAsync('docker rm -f reverse-proxy');
await execAsync('docker rm -f tipi-dashboard');
await execAsync('docker rm -f tipi-reverse-proxy');
spinner.done('Containers stopped and removed');
// Pull images

View file

@ -30,6 +30,7 @@ type EnvKeys =
| 'POSTGRES_PASSWORD'
| 'POSTGRES_USERNAME'
| 'REDIS_HOST'
| 'REDIS_PASSWORD'
| 'LOCAL_DOMAIN'
| 'DEMO_MODE'
// eslint-disable-next-line @typescript-eslint/ban-types
@ -148,6 +149,7 @@ export const generateSystemEnvFile = async () => {
const jwtSecret = envMap.get('JWT_SECRET') || (await deriveEntropy('jwt_secret'));
const repoId = getRepoHash(data.appsRepoUrl || DEFAULT_REPO_URL);
const postgresPassword = envMap.get('POSTGRES_PASSWORD') || (await deriveEntropy('postgres_password'));
const redisPassword = envMap.get('REDIS_PASSWORD') || (await deriveEntropy('redis_password'));
const version = await fs.promises.readFile(path.join(rootFolder, 'VERSION'), 'utf-8');
@ -170,6 +172,7 @@ export const generateSystemEnvFile = async () => {
envMap.set('POSTGRES_PASSWORD', postgresPassword);
envMap.set('POSTGRES_PORT', String(5432));
envMap.set('REDIS_HOST', 'tipi-redis');
envMap.set('REDIS_PASSWORD', redisPassword);
envMap.set('DEMO_MODE', String(data.demoMode || 'false'));
envMap.set('LOCAL_DOMAIN', data.localDomain || 'tipi.lan');
envMap.set('NODE_ENV', 'production');

View file

@ -3,6 +3,7 @@ import { Worker } from 'bullmq';
import { exec } from 'child_process';
import { promisify } from 'util';
import { AppExecutors, RepoExecutors, SystemExecutors } from '@/executors';
import { getEnv } from '@/utils/environment/environment';
const execAsync = promisify(exec);
@ -101,7 +102,7 @@ export const startWorker = async () => {
return { success, stdout: message };
},
{ connection: { host: '127.0.0.1', port: 6379 } },
{ connection: { host: '127.0.0.1', port: 6379, password: getEnv().redisPassword } },
);
worker.on('ready', () => {

View file

@ -15,9 +15,10 @@ const environmentSchema = z
ARCHITECTURE: z.enum(['arm64', 'amd64']),
INTERNAL_IP: z.string().ip().or(z.literal('localhost')),
TIPI_VERSION: z.string(),
REDIS_PASSWORD: z.string(),
})
.transform((env) => {
const { STORAGE_PATH, ARCHITECTURE, ROOT_FOLDER_HOST, APPS_REPO_ID, INTERNAL_IP, TIPI_VERSION, ...rest } = env;
const { STORAGE_PATH, ARCHITECTURE, ROOT_FOLDER_HOST, APPS_REPO_ID, INTERNAL_IP, TIPI_VERSION, REDIS_PASSWORD, ...rest } = env;
return {
storagePath: STORAGE_PATH,
@ -26,6 +27,7 @@ const environmentSchema = z
arch: ARCHITECTURE,
tipiVersion: TIPI_VERSION,
internalIp: INTERNAL_IP,
redisPassword: REDIS_PASSWORD,
...rest,
};
});

View file

@ -10,6 +10,7 @@ export type Architecture = (typeof ARCHITECTURES)[keyof typeof ARCHITECTURES];
export const envSchema = z.object({
NODE_ENV: z.union([z.literal('development'), z.literal('production'), z.literal('test')]),
REDIS_HOST: z.string(),
redisPassword: z.string(),
status: z.union([z.literal('RUNNING'), z.literal('UPDATING'), z.literal('RESTARTING')]),
architecture: z.nativeEnum(ARCHITECTURES),
dnsIp: z.string().ip().trim(),

View file

@ -17,8 +17,8 @@ class EventDispatcher {
private queueEvents;
constructor() {
this.queue = new Queue('events', { connection: { host: getConfig().REDIS_HOST, port: 6379 } });
this.queueEvents = new QueueEvents('events', { connection: { host: getConfig().REDIS_HOST, port: 6379 } });
this.queue = new Queue('events', { connection: { host: getConfig().REDIS_HOST, port: 6379, password: getConfig().redisPassword } });
this.queueEvents = new QueueEvents('events', { connection: { host: getConfig().REDIS_HOST, port: 6379, password: getConfig().redisPassword } });
}
public async cleanRepeatableJobs() {

View file

@ -12,6 +12,7 @@ class TipiCache {
constructor() {
const client = createClient({
url: `redis://${getConfig().REDIS_HOST}:6379`,
password: getConfig().redisPassword,
});
client.on('error', (err) => {

View file

@ -27,6 +27,7 @@ export class TipiConfig {
postgresPassword: conf.POSTGRES_PASSWORD,
postgresPort: Number(conf.POSTGRES_PORT || 5432),
REDIS_HOST: conf.REDIS_HOST,
redisPassword: conf.REDIS_PASSWORD,
NODE_ENV: conf.NODE_ENV,
architecture: conf.ARCHITECTURE || 'amd64',
rootFolder: '/runtipi',

View file

@ -6,6 +6,7 @@ import { getConfig } from '../core/TipiConfig';
// Initialize client.
const redisClient = createClient({
url: `redis://${getConfig().REDIS_HOST}:6379`,
password: getConfig().redisPassword,
});
redisClient.connect();